diff options
author | Timothy Pearson <[email protected]> | 2011-11-08 12:31:36 -0600 |
---|---|---|
committer | Timothy Pearson <[email protected]> | 2011-11-08 12:31:36 -0600 |
commit | d796c9dd933ab96ec83b9a634feedd5d32e1ba3f (patch) | |
tree | 6e3dcca4f77e20ec8966c666aac7c35bd4704053 /src/network | |
download | tqt3-d796c9dd933ab96ec83b9a634feedd5d32e1ba3f.tar.gz tqt3-d796c9dd933ab96ec83b9a634feedd5d32e1ba3f.zip |
Test conversion to TQt3 from Qt3 8c6fc1f8e35fd264dd01c582ca5e7549b32ab731
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/qdns.cpp | 2692 | ||||
-rw-r--r-- | src/network/qdns.h | 170 | ||||
-rw-r--r-- | src/network/qftp.cpp | 2419 | ||||
-rw-r--r-- | src/network/qftp.h | 202 | ||||
-rw-r--r-- | src/network/qhostaddress.cpp | 453 | ||||
-rw-r--r-- | src/network/qhostaddress.h | 101 | ||||
-rw-r--r-- | src/network/qhttp.cpp | 2384 | ||||
-rw-r--r-- | src/network/qhttp.h | 277 | ||||
-rw-r--r-- | src/network/qnetwork.cpp | 71 | ||||
-rw-r--r-- | src/network/qnetwork.h | 60 | ||||
-rw-r--r-- | src/network/qserversocket.cpp | 297 | ||||
-rw-r--r-- | src/network/qserversocket.h | 94 | ||||
-rw-r--r-- | src/network/qsocket.cpp | 1546 | ||||
-rw-r--r-- | src/network/qsocket.h | 156 | ||||
-rw-r--r-- | src/network/qsocketdevice.cpp | 576 | ||||
-rw-r--r-- | src/network/qsocketdevice.h | 170 | ||||
-rw-r--r-- | src/network/qsocketdevice_unix.cpp | 1099 | ||||
-rw-r--r-- | src/network/qt_network.pri | 23 |
18 files changed, 12790 insertions, 0 deletions
diff --git a/src/network/qdns.cpp b/src/network/qdns.cpp new file mode 100644 index 000000000..65deec7f6 --- /dev/null +++ b/src/network/qdns.cpp @@ -0,0 +1,2692 @@ +/**************************************************************************** +** +** Implementation of TQDns class. +** +** Created : 991122 +** +** Copyright (C) 1999-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qplatformdefs.h" + +// 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 "qdns.h" + +#ifndef QT_NO_DNS + +#include "qdatetime.h" +#include "qdict.h" +#include "qptrlist.h" +#include "qstring.h" +#include "qtimer.h" +#include "qapplication.h" +#include "qptrvector.h" +#include "qstrlist.h" +#include "qptrdict.h" +#include "qfile.h" +#include "qtextstream.h" +#include "qsocketdevice.h" +#include "qcleanuphandler.h" +#include <limits.h> +#ifdef Q_OS_MAC +#include "../3rdparty/dlcompat/dlfcn.h" +#endif + +//#define TQDNS_DEBUG + +static Q_UINT16 id; // ### seeded started by now() + + +static TQDateTime * originOfTime = 0; + +static TQCleanupHandler<TQDateTime> qdns_cleanup_time; + +static Q_UINT32 now() +{ + if ( originOfTime ) + return originOfTime->secsTo( TQDateTime::currentDateTime() ); + + originOfTime = new TQDateTime( TQDateTime::currentDateTime() ); + ::id = originOfTime->time().msec() * 60 + originOfTime->time().second(); + qdns_cleanup_time.add( &originOfTime ); + return 0; +} + + +static TQPtrList<TQHostAddress> * ns = 0; +static TQStrList * domains = 0; +static bool ipv6support = FALSE; + +static int qdns_res_init() +{ +#ifdef Q_OS_MAC + typedef int (*PtrRes_init)(); + static PtrRes_init ptrRes_init = 0; + if (!ptrRes_init) + ptrRes_init = (PtrRes_init)DL_PREFIX(dlsym)(RTLD_NEXT, "res_init"); + if (ptrRes_init) + return (*ptrRes_init)(); + else + return -1; +#elif defined(Q_OS_UNIX) + return res_init(); +#else + return 0; // not called at all on Windows. +#endif +} + + +class TQDnsPrivate { +public: + TQDnsPrivate() : 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 + } + ~TQDnsPrivate() + { + delete queryTimer; + } +private: + TQTimer * queryTimer; + bool noNames; +#if defined(Q_DNS_SYNCHRONOUS) + bool noEventLoop; +#endif + + friend class TQDns; + friend class TQDnsAnswer; +}; + + +class TQDnsRR; +class TQDnsDomain; + + + +// TQDnsRR is the class used to store a single RR. TQDnsRR can store +// all of the supported RR types. a TQDnsRR is always cached. + +// TQDnsRR is mostly constructed from the outside. a but hacky, but +// permissible since the entire class is internal. + +class TQDnsRR { +public: + TQDnsRR( const TQString & label ); + ~TQDnsRR(); + +public: + TQDnsDomain * domain; + TQDns::RecordType t; + bool nxdomain; + bool current; + Q_UINT32 expireTime; + Q_UINT32 deleteTime; + // somewhat space-wasting per-type data + // a / aaaa + TQHostAddress address; + // cname / mx / srv / ptr + TQString target; + // mx / srv + Q_UINT16 priority; + // srv + Q_UINT16 weight; + Q_UINT16 port; + // txt + TQString text; // could be overloaded into target... +private: + +}; + + +class TQDnsDomain { +public: + TQDnsDomain( const TQString & label ); + ~TQDnsDomain(); + + static void add( const TQString & label, TQDnsRR * ); + static TQPtrList<TQDnsRR> * cached( const TQDns * ); + + void take( TQDnsRR * ); + + void sweep( Q_UINT32 thisSweep ); + + bool isEmpty() const { return rrs == 0 || rrs->isEmpty(); } + + TQString name() const { return l; } + +public: + TQString l; + TQPtrList<TQDnsRR> * rrs; +}; + + +class TQDnsQuery: public TQTimer { // this inheritance is a very evil hack +public: + TQDnsQuery(): + id( 0 ), t( TQDns::None ), step(0), started(0), + dns( new TQPtrDict<void>(17) ) {} + ~TQDnsQuery() { delete dns; } + Q_UINT16 id; + TQDns::RecordType t; + TQString l; + + uint step; + Q_UINT32 started; + + TQPtrDict<void> * dns; +}; + + + +class TQDnsAnswer { +public: + TQDnsAnswer( TQDnsQuery * ); + TQDnsAnswer( const TQByteArray &, TQDnsQuery * ); + ~TQDnsAnswer(); + + void parse(); + void notify(); + + bool ok; + +private: + TQDnsQuery * query; + + Q_UINT8 * answer; + int size; + int pp; + + TQPtrList<TQDnsRR> * rrs; + + // convenience + int next; + int ttl; + TQString label; + TQDnsRR * rr; + + TQString readString(bool multipleLabels = TRUE); + void parseA(); + void parseAaaa(); + void parseMx(); + void parseSrv(); + void parseCname(); + void parsePtr(); + void parseTxt(); + void parseNs(); +}; + + +TQDnsRR::TQDnsRR( const TQString & label ) + : domain( 0 ), t( TQDns::None ), + nxdomain( FALSE ), current( FALSE ), + expireTime( 0 ), deleteTime( 0 ), + priority( 0 ), weight( 0 ), port( 0 ) +{ + TQDnsDomain::add( label, this ); +} + + +// not supposed to be deleted except by TQDnsDomain +TQDnsRR::~TQDnsRR() +{ + // nothing is necessary +} + + +// this one just sticks in a NXDomain +TQDnsAnswer::TQDnsAnswer( TQDnsQuery * query_ ) +{ + ok = TRUE; + + answer = 0; + size = 0; + query = query_; + pp = 0; + rrs = new TQPtrList<TQDnsRR>; + rrs->setAutoDelete( FALSE ); + next = size; + ttl = 0; + label = TQString::null; + rr = 0; + + TQDnsRR * newrr = new TQDnsRR( 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 ); +} + + +TQDnsAnswer::TQDnsAnswer( const TQByteArray& answer_, + TQDnsQuery * query_ ) +{ + ok = TRUE; + + answer = (Q_UINT8 *)(answer_.data()); + size = (int)answer_.size(); + query = query_; + pp = 0; + rrs = new TQPtrList<TQDnsRR>; + rrs->setAutoDelete( FALSE ); + next = size; + ttl = 0; + label = TQString::null; + rr = 0; +} + + +TQDnsAnswer::~TQDnsAnswer() +{ + if ( !ok && rrs ) { + TQPtrListIterator<TQDnsRR> it( *rrs ); + TQDnsRR * tmprr; + while( (tmprr=it.current()) != 0 ) { + ++it; + tmprr->t = TQDns::None; // will be deleted soonish + } + } + delete rrs; +} + + +TQString TQDnsAnswer::readString(bool multipleLabels) +{ + int p = pp; + TQString r = TQString::null; + 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() ? TQString( "." ) : r; + } + + // Read a label of size 'b' characters + if ( !r.isNull() ) + r += '.'; + while( b-- > 0 ) { + r += TQChar( answer[p] ); + 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 TQString::null; +} + + + +void TQDnsAnswer::parseA() +{ + if ( next != pp + 4 ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %d bytes long IN A for %s", + next - pp, label.ascii() ); +#endif + return; + } + + rr = new TQDnsRR( label ); + rr->t = TQDns::A; + rr->address = TQHostAddress( ( answer[pp+0] << 24 ) + + ( answer[pp+1] << 16 ) + + ( answer[pp+2] << 8 ) + + ( answer[pp+3] ) ); +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %s IN A %s (ttl %d)", label.ascii(), + rr->address.toString().ascii(), ttl ); +#endif +} + + +void TQDnsAnswer::parseAaaa() +{ + if ( next != pp + 16 ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %d bytes long IN Aaaa for %s", + next - pp, label.ascii() ); +#endif + return; + } + + rr = new TQDnsRR( label ); + rr->t = TQDns::Aaaa; + rr->address = TQHostAddress( answer+pp ); +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %s IN Aaaa %s (ttl %d)", label.ascii(), + rr->address.toString().ascii(), ttl ); +#endif +} + + + +void TQDnsAnswer::parseMx() +{ + if ( next < pp + 2 ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %d bytes long IN MX for %s", + next - pp, label.ascii() ); +#endif + return; + } + + rr = new TQDnsRR( label ); + rr->priority = (answer[pp] << 8) + answer[pp+1]; + pp += 2; + rr->target = readString().lower(); + if ( !ok ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw bad string in MX for %s", label.ascii() ); +#endif + return; + } + rr->t = TQDns::Mx; +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %s IN MX %d %s (ttl %d)", label.ascii(), + rr->priority, rr->target.ascii(), ttl ); +#endif +} + + +void TQDnsAnswer::parseSrv() +{ + if ( next < pp + 6 ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %d bytes long IN SRV for %s", + next - pp, label.ascii() ); +#endif + return; + } + + rr = new TQDnsRR( 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(TQDNS_DEBUG) + qDebug( "TQDns: saw bad string in SRV for %s", label.ascii() ); +#endif + return; + } + rr->t = TQDns::Srv; +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %s IN SRV %d %d %d %s (ttl %d)", label.ascii(), + rr->priority, rr->weight, rr->port, rr->target.ascii(), ttl ); +#endif +} + + +void TQDnsAnswer::parseCname() +{ + TQString target = readString().lower(); + if ( !ok ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw bad cname for for %s", label.ascii() ); +#endif + return; + } + + rr = new TQDnsRR( label ); + rr->t = TQDns::Cname; + rr->target = target; +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %s IN CNAME %s (ttl %d)", label.ascii(), + rr->target.ascii(), ttl ); +#endif +} + + +void TQDnsAnswer::parseNs() +{ + TQString target = readString().lower(); + if ( !ok ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw bad cname for for %s", label.ascii() ); +#endif + return; + } + + // parse, but ignore + +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %s IN NS %s (ttl %d)", label.ascii(), + target.ascii(), ttl ); +#endif +} + + +void TQDnsAnswer::parsePtr() +{ + TQString target = readString().lower(); + if ( !ok ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw bad PTR for for %s", label.ascii() ); +#endif + return; + } + + rr = new TQDnsRR( label ); + rr->t = TQDns::Ptr; + rr->target = target; +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %s IN PTR %s (ttl %d)", label.ascii(), + rr->target.ascii(), ttl ); +#endif +} + + +void TQDnsAnswer::parseTxt() +{ + TQString text = readString(FALSE); + if ( !ok ) { +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw bad TXT for for %s", label.ascii() ); +#endif + return; + } + + rr = new TQDnsRR( label ); + rr->t = TQDns::Txt; + rr->text = text; +#if defined(TQDNS_DEBUG) + qDebug( "TQDns: saw %s IN TXT \"%s\" (ttl %d)", label.ascii(), + rr->text.ascii(), ttl ); +#endif +} + + +void TQDnsAnswer::parse() +{ + // okay, do the work... + if ( (answer[2] & 0x78) != 0 ) { +#if defined(TQDNS_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(TQDNS_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(TQDNS_DEBUG) + qDebug( "DNS Manager: saw NXDomain for %s", query->l.ascii() ); +#endif + // NXDomain. cache that for one minute. + rr = new TQDnsRR( 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(TQDNS_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(TQDNS_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(TQDNS_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(TQDNS_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(TQDNS_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(); + TQDict<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 == TQDns::A || rr->t == TQDns::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 ) { + TQPtrList<TQDnsRR> * drrs = rr->domain->rrs; + drrs->first(); + TQDnsRR * 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(TQDNS_DEBUG) + qDebug( "killing off old %d for %s, expire was %d", + older->t, older->domain->name().latin1(), + rr->expireTime ); +#endif + older->t = TQDns::None; + rr->expireTime = TQMAX( older->expireTime, rr->expireTime ); + rr->deleteTime = TQMAX( older->deleteTime, rr->deleteTime ); + older->deleteTime = 0; +#if defined(TQDNS_DEBUG) + qDebug( " adjusted expire is %d", rr->expireTime ); +#endif + } + drrs->next(); + } + } + } + +#if defined(TQDNS_DEBUG) + //qDebug( "DNS Manager: ()" ); +#endif +} + + +class TQDnsUgleHack: public TQDns { +public: + void ugle( bool emitAnyway=FALSE ); +}; + + +void TQDnsAnswer::notify() +{ + if ( !rrs || !ok || !query || !query->dns ) + return; + + TQPtrDict<void> notified; + notified.setAutoDelete( FALSE ); + + TQPtrDictIterator<void> it( *query->dns ); + TQDns * dns; + it.toFirst(); + while( (dns=(TQDns*)(it.current())) != 0 ) { + ++it; + if ( notified.find( (void*)dns ) == 0 ) { + notified.insert( (void*)dns, (void*)42 ); + if ( rrs->count() == 0 ) { +#if defined(TQDNS_DEBUG) + qDebug( "DNS Manager: found no answers!" ); +#endif + dns->d->noNames = TRUE; + ((TQDnsUgleHack*)dns)->ugle( TRUE ); + } else { + TQStringList n = dns->qualifiedNames(); + if ( query && n.contains(query->l) ) + ((TQDnsUgleHack*)dns)->ugle(); +#if defined(TQDNS_DEBUG) + else + qDebug( "DNS Manager: DNS thing %s not notified for %s", + dns->label().ascii(), query->l.ascii() ); +#endif + } + } + } +} + + +// +// +// TQDnsManager +// +// + + +class TQDnsManager: public TQDnsSocket { +private: +public: // just to silence the moronic g++. + TQDnsManager(); + ~TQDnsManager(); +public: + static TQDnsManager * manager(); + + TQDnsDomain * domain( const TQString & ); + + void transmitQuery( TQDnsQuery * ); + void transmitQuery( int ); + + // reimplementation of the slots + void cleanCache(); + void retransmit(); + void answer(); + +public: + TQPtrVector<TQDnsQuery> queries; + TQDict<TQDnsDomain> cache; + TQSocketDevice * ipv4Socket; +#if !defined (QT_NO_IPV6) + TQSocketDevice * ipv6Socket; +#endif +}; + + + +static TQDnsManager * globalManager = 0; + +static void cleanupDns() +{ + delete globalManager; + globalManager = 0; +} + +TQDnsManager * TQDnsManager::manager() +{ + if ( !globalManager ) { + qAddPostRoutine(cleanupDns); + new TQDnsManager(); + } + return globalManager; +} + + +void TQDnsUgleHack::ugle( bool emitAnyway) +{ + if ( emitAnyway || !isWorking() ) { +#if defined(TQDNS_DEBUG) + qDebug( "DNS Manager: status change for %s (type %d)", + label().ascii(), recordType() ); +#endif + emit resultsReady(); + } +} + + +TQDnsManager::TQDnsManager() + : TQDnsSocket( qApp, "Internal DNS manager" ), + queries( TQPtrVector<TQDnsQuery>( 0 ) ), + cache( TQDict<TQDnsDomain>( 83, FALSE ) ), + ipv4Socket( new TQSocketDevice( TQSocketDevice::Datagram, TQSocketDevice::IPv4, 0 ) ) +#if !defined (QT_NO_IPV6) + , ipv6Socket( new TQSocketDevice( TQSocketDevice::Datagram, TQSocketDevice::IPv6, 0 ) ) +#endif +{ + cache.setAutoDelete( TRUE ); + globalManager = this; + + TQTimer * sweepTimer = new TQTimer( this ); + sweepTimer->start( 1000 * 60 * 3 ); + connect( sweepTimer, SIGNAL(timeout()), + this, SLOT(cleanCache()) ); + + TQSocketNotifier * rn4 = new TQSocketNotifier( ipv4Socket->socket(), + TQSocketNotifier::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 ) { + TQSocketNotifier * rn6 = new TQSocketNotifier( ipv6Socket->socket(), + TQSocketNotifier::Read, + this, "dns IPv6 socket watcher" ); + + ipv6support = TRUE; + ipv6Socket->setAddressReusable( FALSE ); + ipv6Socket->setBlocking( FALSE ); + connect( rn6, SIGNAL(activated(int)), SLOT(answer()) ); + } +#endif + + if ( !ns ) + TQDns::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. + TQPtrList<TQHostAddress> * ns = new TQPtrList<TQHostAddress>; + + ::ns->first(); + TQHostAddress * h; + while( (h=::ns->current()) != 0 ) { + ns->first(); + while( ns->current() != 0 && !(*ns->current() == *h) ) + ns->next(); + if ( !ns->current() ) { + ns->append( new TQHostAddress(*h) ); +#if defined(TQDNS_DEBUG) + qDebug( "using name server %s", h->toString().latin1() ); + } else { + qDebug( "skipping address %s", h->toString().latin1() ); +#endif + } + ::ns->next(); + } + + delete ::ns; + ::ns = ns; + ::ns->setAutoDelete( TRUE ); + + TQStrList * domains = new TQStrList( TRUE ); + + ::domains->first(); + const char * s; + while( (s=::domains->current()) != 0 ) { + domains->first(); + while( domains->current() != 0 && qstrcmp( domains->current(), s ) ) + domains->next(); + if ( !domains->current() ) { + domains->append( s ); +#if defined(TQDNS_DEBUG) + qDebug( "searching domain %s", s ); + } else { + qDebug( "skipping domain %s", s ); +#endif + } + ::domains->next(); + } + + delete ::domains; + ::domains = domains; + ::domains->setAutoDelete( TRUE ); +} + + +TQDnsManager::~TQDnsManager() +{ + 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 TQDnsManager::cleanCache() +{ + bool again = FALSE; + TQDictIterator<TQDnsDomain> it( cache ); + TQDnsDomain * d; + Q_UINT32 thisSweep = now(); +#if defined(TQDNS_DEBUG) + qDebug( "TQDnsManager::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 TQDnsManager::retransmit() +{ + const TQObject * 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 TQDnsManager::answer() +{ + TQByteArray a( 16383 ); // large enough for anything, one suspects + + int r; +#if defined (QT_NO_IPV6) + r = ipv4Socket->readBlock(a.data(), a.size()); +#else + if (((TQSocketNotifier *)sender())->socket() == ipv4Socket->socket()) + r = ipv4Socket->readBlock(a.data(), a.size()); + else + r = ipv6Socket->readBlock(a.data(), a.size()); +#endif +#if defined(TQDNS_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(TQDNS_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(TQDNS_DEBUG) + qDebug( "DNS Manager: received a query" ); +#endif + return; + } + + TQDnsQuery * q = queries[i]; + TQDnsAnswer answer( a, q ); + answer.parse(); + if ( answer.ok ) { + queries.take( i ); + answer.notify(); + delete q; + } +} + + +void TQDnsManager::transmitQuery( TQDnsQuery * 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 TQDnsManager::transmitQuery( int i ) +{ + if ( i < 0 || i >= (int)queries.size() ) + return; + TQDnsQuery * 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... + TQDnsAnswer answer( q ); + answer.notify(); + + if (globalManager == 0) + return; + + // and then get rid of the query + queries.take( i ); +#if defined(TQDNS_DEBUG) + qDebug( "DNS Manager: giving up on query 0x%04x", q->id ); +#endif + delete q; + TQTimer::singleShot( 0, TQDnsManager::manager(), SLOT(cleanCache()) ); + // and don't process anything more + return; + } + + if ( q && !q->dns || q->dns->isEmpty() ) + // noone 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; + + TQByteArray 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 < q->l.length() ) { + int le = q->l.find( '.', lp ); + if ( le < 0 ) + le = q->l.length(); + TQString 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 TQDns::A: + p[pp++] = 1; + break; + case TQDns::Aaaa: + p[pp++] = 28; + break; + case TQDns::Mx: + p[pp++] = 15; + break; + case TQDns::Srv: + p[pp++] = 33; + break; + case TQDns::Cname: + p[pp++] = 5; + break; + case TQDns::Ptr: + p[pp++] = 12; + break; + case TQDns::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 ( !ns || ns->isEmpty() ) + TQDns::doResInit(); + + if ( !ns || ns->isEmpty() ) { + // we don't find any name servers. We fake an NXDomain + // with a very short life time... + TQDnsAnswer answer( q ); + answer.notify(); + // and then get rid of the query + queries.take( i ); +#if defined(TQDNS_DEBUG) + qDebug( "DNS Manager: no DNS server found on query 0x%04x", q->id ); +#endif + delete q; + TQTimer::singleShot( 1000*10, TQDnsManager::manager(), SLOT(cleanCache()) ); + // and don't process anything more + return; + } + + TQHostAddress receiver = *ns->at( q->step % ns->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(TQDNS_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 ( ns->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; + TQHostAddress * server; + while( (server=ns->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(TQDNS_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 < ns->count() ? 800 : 1500, TRUE ); +} + + +TQDnsDomain * TQDnsManager::domain( const TQString & label ) +{ + TQDnsDomain * d = cache.find( label ); + if ( !d ) { + d = new TQDnsDomain( label ); + cache.insert( label, d ); + } + return d; +} + + +// +// +// the TQDnsDomain class looks after and coordinates queries for TQDnsRRs for +// each domain, and the cached TQDnsRRs. (A domain, in DNS terminology, is +// a node in the DNS. "no", "trolltech.com" and "lupinella.troll.no" are +// all domains.) +// +// + + +// this is ONLY to be called by TQDnsManager::domain(). noone else. +TQDnsDomain::TQDnsDomain( const TQString & label ) +{ + l = label; + rrs = 0; +} + + +TQDnsDomain::~TQDnsDomain() +{ + delete rrs; + rrs = 0; +} + + +void TQDnsDomain::add( const TQString & label, TQDnsRR * rr ) +{ + TQDnsDomain * d = TQDnsManager::manager()->domain( label ); + if ( !d->rrs ) { + d->rrs = new TQPtrList<TQDnsRR>; + d->rrs->setAutoDelete( TRUE ); + } + d->rrs->append( rr ); + rr->domain = d; +} + + +TQPtrList<TQDnsRR> * TQDnsDomain::cached( const TQDns * r ) +{ + TQPtrList<TQDnsRR> * l = new TQPtrList<TQDnsRR>; + + // test at first if you have to start a query at all + if ( r->recordType() == TQDns::A ) { + if ( r->label().lower() == "localhost" ) { + // undocumented hack. ipv4-specific. also, may be a memory + // leak? not sure. would be better to do this in doResInit(), + // anyway. + TQDnsRR *rrTmp = new TQDnsRR( r->label() ); + rrTmp->t = TQDns::A; + rrTmp->address = TQHostAddress( 0x7f000001 ); + rrTmp->current = TRUE; + l->append( rrTmp ); + return l; + } + TQHostAddress tmp; + if ( tmp.setAddress( r->label() ) ) { + TQDnsRR *rrTmp = new TQDnsRR( r->label() ); + if ( tmp.isIPv4Address() ) { + rrTmp->t = TQDns::A; + rrTmp->address = tmp; + rrTmp->current = TRUE; + l->append( rrTmp ); + } else { + rrTmp->nxdomain = TRUE; + } + return l; + } + } + if ( r->recordType() == TQDns::Aaaa ) { + TQHostAddress tmp; + if ( tmp.setAddress(r->label()) ) { + TQDnsRR *rrTmp = new TQDnsRR( r->label() ); + if ( tmp.isIPv6Address() ) { + rrTmp->t = TQDns::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 + TQDnsManager * m = TQDnsManager::manager(); + TQStringList n = r->qualifiedNames(); + TQValueListIterator<TQString> it = n.begin(); + TQValueListIterator<TQString> end = n.end(); + bool nxdomain; + int cnamecount = 0; + while( it != end ) { + TQString s = *it++; + nxdomain = FALSE; +#if defined(TQDNS_DEBUG) + qDebug( "looking at cache for %s (%s %d)", + s.ascii(), r->label().ascii(), r->recordType() ); +#endif + TQDnsDomain * d = m->domain( s ); +#if defined(TQDNS_DEBUG) + qDebug( " - found %d RRs", d && d->rrs ? d->rrs->count() : 0 ); +#endif + if ( d->rrs ) + d->rrs->first(); + TQDnsRR * rr; + bool answer = FALSE; + while( d->rrs && (rr=d->rrs->current()) != 0 ) { + if ( rr->t == TQDns::Cname && r->recordType() != TQDns::Cname && + !rr->nxdomain && cnamecount < 16 ) { + // cname. if the code is ugly, that may just + // possibly be because the concept is. +#if defined(TQDNS_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 = end; + // 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. + TQDnsQuery * query = new TQDnsQuery; + query->started = now(); + query->id = ++::id; + 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 = ns->count(); + TQObject::connect( query, SIGNAL(timeout()), + TQDnsManager::manager(), + SLOT(retransmit()) ); + TQDnsManager::manager()->transmitQuery( query ); + } + } + d->rrs->next(); + } + } + // if we found a positive result, return tquickly + if ( answer && l->count() ) { +#if defined(TQDNS_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(TQDNS_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( '.' ) >= 0 || + l->count() >= n.count()-1 ) ) { + TQDnsQuery * query = new TQDnsQuery; + query->started = now(); + query->id = ++::id; + query->t = r->recordType(); + query->l = s; + query->dns->replace( (void*)r, (void*)r ); + TQObject::connect( query, SIGNAL(timeout()), + TQDnsManager::manager(), SLOT(retransmit()) ); + TQDnsManager::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 TQDnsDomain::sweep( Q_UINT32 thisSweep ) +{ + if ( !rrs ) + return; + + TQDnsRR * rr; + rrs->first(); + while( (rr=rrs->current()) != 0 ) { + if ( !rr->deleteTime ) + rr->deleteTime = thisSweep; // will hit next time around + +#if defined(TQDNS_DEBUG) + qDebug( "TQDns::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 == TQDns::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. + + +TQDnsSocket::TQDnsSocket( TQObject * parent, const char * name ) + : TQObject( parent, name ) +{ + // nothing +} + + +TQDnsSocket::~TQDnsSocket() +{ + // nothing +} + + +void TQDnsSocket::cleanCache() +{ + // nothing +} + + +void TQDnsSocket::retransmit() +{ + // nothing +} + + +void TQDnsSocket::answer() +{ + // nothing +} + + +/*! + \class TQDns qdns.h + \brief The TQDns class provides asynchronous DNS lookups. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \module network + \ingroup io + + 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. + + TQDns 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); TQSocket provides much easier to use + TCP connection facilities. The aim of TQDns 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 TQDns 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 TQSocket +*/ + +/*! + Constructs a DNS query object with invalid settings for both the + label and the search type. +*/ + +TQDns::TQDns() +{ + d = new TQDnsPrivate; + 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. +*/ + +TQDns::TQDns( const TQString & label, RecordType rr ) +{ + d = new TQDnsPrivate; + 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. +*/ + +TQDns::TQDns( const TQHostAddress & address, RecordType rr ) +{ + d = new TQDnsPrivate; + t = rr; + setLabel( address ); + setStartQueryTimer(); // start query the next time we enter event loop +} + + + + +/*! + Destroys the DNS query object and frees its allocated resources. +*/ + +TQDns::~TQDns() +{ + if ( globalManager ) { + uint q = 0; + TQDnsManager * m = globalManager; + while( q < m->queries.size() ) { + TQDnsQuery * 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 TQDns::setLabel( const TQString & label ) +{ + l = label; + d->noNames = FALSE; + + // construct a list of qualified names + n.clear(); + if ( l.length() > 1 && l[(int)l.length()-1] == '.' ) { + 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] == '.' ) + dots++; + } + if ( dots < maxDots ) { + (void)TQDnsManager::manager(); // create a TQDnsManager, if it is not already there + TQStrListIterator it( *domains ); + const char * dom; + while( (dom=it.current()) != 0 ) { + ++it; + n.append( l.lower() + "." + 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(TQDNS_DEBUG) + qDebug( "TQDns::setLabel: %d address(es) for %s", n.count(), l.ascii() ); + int i = 0; + for( i = 0; i < (int)n.count(); i++ ) + qDebug( "TQDns::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 TQDns::setLabel( const TQHostAddress & address ) +{ + setLabel( toInAddrArpaDomain( address ) ); +} + + +/*! + \fn TQStringList TQDns::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. + \code + TQStringList list = myDns.qualifiedNames(); + TQStringList::Iterator it = list.begin(); + while( it != list.end() ) { + myProcessing( *it ); + ++it; + } + \endcode + +*/ + + +/*! + \fn TQString TQDns::label() const + + Returns the domain name for which this object returns information. + + \sa setLabel() +*/ + +/*! + \enum TQDns::RecordType + + This enum type defines the record types TQDns 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 TQDns 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 + \link http://www.dns.net/dnsrd/rfc/rfc2535.html RFC-2535 \endlink + 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 TQDns::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 TQDns::startQuery() +{ + // isWorking() starts the query (if necessary) + if ( !isWorking() ) + emit resultsReady(); +} + +/*! + The three functions TQDns::TQDns(TQString, RecordType), + TQDns::setLabel() and TQDns::setRecordType() may start a DNS lookup. + This function handles setting up the single shot timer. +*/ +void TQDns::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 TQTimer( 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. +*/ +TQString TQDns::toInAddrArpaDomain( const TQHostAddress &address ) +{ + TQString 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 = "ip6.arpa"; + uint b = 0; + while( b < 16 ) { + s = TQString::number( i.c[b]%16, 16 ) + "." + + TQString::number( i.c[b]/16, 16 ) + "." + s; + b++; + } + } + return s; +} + + +/*! + \fn TQDns::RecordType TQDns::recordType() const + + Returns the record type of this DNS query object. + + \sa setRecordType() RecordType +*/ + +/*! + \fn void TQDns::resultsReady() + + This signal is emitted when results are available for one of the + qualifiedNames(). +*/ + +/*! + Returns TRUE if TQDns is doing a lookup for this object (i.e. if it + does not already have the necessary information); otherwise + returns FALSE. + + TQDns emits the resultsReady() signal when the status changes to FALSE. +*/ + +bool TQDns::isWorking() const +{ +#if defined(TQDNS_DEBUG) + qDebug( "TQDns::isWorking (%s, %d)", l.ascii(), t ); +#endif + if ( t == None ) + return FALSE; + +#if defined(Q_DNS_SYNCHRONOUS) + if ( d->noEventLoop ) + return TRUE; +#endif + + TQPtrList<TQDnsRR> * ll = TQDnsDomain::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 TQDns object + has a recordType() of \c TQDns::A or \c TQDns::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. + \code + TQValueList<TQHostAddress> list = myDns.addresses(); + TQValueList<TQHostAddress>::Iterator it = list.begin(); + while( it != list.end() ) { + myProcessing( *it ); + ++it; + } + \endcode + +*/ + +TQValueList<TQHostAddress> TQDns::addresses() const +{ +#if defined(TQDNS_DEBUG) + qDebug( "TQDns::addresses (%s)", l.ascii() ); +#endif + TQValueList<TQHostAddress> result; + if ( t != A && t != Aaaa ) + return result; + + TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this ); + + TQDnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain ) + result.append( rr->address ); + cached->next(); + } + delete cached; + return result; +} + + +/*! + \class TQDns::MailServer + \brief The TQDns::MailServer class is described in TQDns::mailServers(). +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + + \internal +*/ + +/*! + Returns a list of mail servers if the record type is \c Mx. The + class \c TQDns::MailServer contains the following public variables: + \list + \i TQString TQDns::MailServer::name + \i Q_UINT16 TQDns::MailServer::priority + \endlist + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \code + TQValueList<TQDns::MailServer> list = myDns.mailServers(); + TQValueList<TQDns::MailServer>::Iterator it = list.begin(); + while( it != list.end() ) { + myProcessing( *it ); + ++it; + } + \endcode + +*/ +TQValueList<TQDns::MailServer> TQDns::mailServers() const +{ +#if defined(TQDNS_DEBUG) + qDebug( "TQDns::mailServers (%s)", l.ascii() ); +#endif + TQValueList<TQDns::MailServer> result; + if ( t != Mx ) + return result; + + TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this ); + + TQDnsRR * 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 TQDns::Server + \brief The TQDns::Server class is described in TQDns::servers(). +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + + \internal +*/ + +/*! + Returns a list of servers if the record type is \c Srv. The class + \c TQDns::Server contains the following public variables: + \list + \i TQString TQDns::Server::name + \i Q_UINT16 TQDns::Server::priority + \i Q_UINT16 TQDns::Server::weight + \i Q_UINT16 TQDns::Server::port + \endlist + + Note that if you want to iterate over the list, you should iterate + over a copy, e.g. + \code + TQValueList<TQDns::Server> list = myDns.servers(); + TQValueList<TQDns::Server>::Iterator it = list.begin(); + while( it != list.end() ) { + myProcessing( *it ); + ++it; + } + \endcode +*/ +TQValueList<TQDns::Server> TQDns::servers() const +{ +#if defined(TQDNS_DEBUG) + qDebug( "TQDns::servers (%s)", l.ascii() ); +#endif + TQValueList<TQDns::Server> result; + if ( t != Srv ) + return result; + + TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this ); + + TQDnsRR * 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. + \code + TQStringList list = myDns.hostNames(); + TQStringList::Iterator it = list.begin(); + while( it != list.end() ) { + myProcessing( *it ); + ++it; + } + \endcode + +*/ +TQStringList TQDns::hostNames() const +{ +#if defined(TQDNS_DEBUG) + qDebug( "TQDns::hostNames (%s)", l.ascii() ); +#endif + TQStringList result; + if ( t != Ptr ) + return result; + + TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this ); + + TQDnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain ) { + TQString 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. + \code + TQStringList list = myDns.texts(); + TQStringList::Iterator it = list.begin(); + while( it != list.end() ) { + myProcessing( *it ); + ++it; + } + \endcode +*/ +TQStringList TQDns::texts() const +{ +#if defined(TQDNS_DEBUG) + qDebug( "TQDns::texts (%s)", l.ascii() ); +#endif + TQStringList result; + if ( t != Txt ) + return result; + + TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( this ); + + TQDnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain ) { + TQString 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 TQDns is + "trolltech.com", then the canonical name for all of "lillian", + "l", "lillian.troll.no." and "l.trolltech.com" is + "lillian.troll.no.". +*/ + +TQString TQDns::canonicalName() const +{ + // the cname should work regardless of the recordType(), so set the record + // type temporarily to cname when you look at the cache + TQDns *that = (TQDns*) this; // mutable function + RecordType oldType = t; + that->t = Cname; + TQPtrList<TQDnsRR> * cached = TQDnsDomain::cached( that ); + that->t = oldType; + + TQDnsRR * rr; + while( (rr=cached->current()) != 0 ) { + if ( rr->current && !rr->nxdomain && rr->domain ) { + delete cached; + return rr->target; + } + cached->next(); + } + delete cached; + return TQString::null; +} + +#if defined(Q_DNS_SYNCHRONOUS) +/*! \reimp +*/ +void TQDns::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 TQDns::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 TQString getWindowsRegString( HKEY key, const TQString &subKey ) +{ + TQString 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 = TQString::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 = 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 = 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 = ptr; + delete [] ptr; + } + } ); + return s; +} + +static bool getDnsParamsFromRegistry( const TQString &path, + TQString *domainName, TQString *nameServer, TQString *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, + 0, KEY_READ, &k ); + } ); + + if ( r == ERROR_SUCCESS ) { + *domainName = getWindowsRegString( k, "DhcpDomain" ); + if ( domainName->isEmpty() ) + *domainName = getWindowsRegString( k, "Domain" ); + + *nameServer = getWindowsRegString( k, "DhcpNameServer" ); + if ( nameServer->isEmpty() ) + *nameServer = getWindowsRegString( k, "NameServer" ); + + *searchList = getWindowsRegString( k, "SearchList" ); + } + RegCloseKey( k ); + return r == ERROR_SUCCESS; +} + +void TQDns::doResInit() +{ + char separator = 0; + + if ( ns ) + delete ns; + ns = new TQPtrList<TQHostAddress>; + ns->setAutoDelete( TRUE ); + domains = new TQStrList( TRUE ); + domains->setAutoDelete( TRUE ); + + TQString domainName, nameServer, searchList; + + bool gotNetworkParams = FALSE; + // try the API call GetNetworkParams() first and use registry lookup only + // as a fallback +#ifdef Q_OS_TEMP + HINSTANCE hinstLib = LoadLibraryW( L"iphlpapi" ); +#else + HINSTANCE hinstLib = LoadLibraryA( "iphlpapi" ); +#endif + if ( hinstLib != 0 ) { +#ifdef Q_OS_TEMP + 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 = finfo->DomainName; + nameServer = ""; + IP_ADDR_STRING *dnsServer = &finfo->DnsServerList; + while ( dnsServer != 0 ) { + nameServer += dnsServer->IpAddress.String; + dnsServer = dnsServer->Next; + if ( dnsServer != 0 ) + nameServer += " "; + } + searchList = ""; + separator = ' '; + gotNetworkParams = TRUE; + } + delete[] finfo; + } + } + FreeLibrary( hinstLib ); + } + if ( !gotNetworkParams ) { + if ( getDnsParamsFromRegistry( + TQString( "System\\CurrentControlSet\\Services\\Tcpip\\Parameters" ), + &domainName, &nameServer, &searchList )) { + // for NT + separator = ' '; + } else if ( getDnsParamsFromRegistry( + TQString( "System\\CurrentControlSet\\Services\\VxD\\MSTCP" ), + &domainName, &nameServer, &searchList )) { + // for 95/98 + separator = ','; + } else { + // Could not access the TCP/IP parameters + domainName = ""; + nameServer = "127.0.0.1"; + searchList = ""; + separator = ' '; + } + } + + nameServer = nameServer.simplifyWhiteSpace(); + int first, last; + if ( !nameServer.isEmpty() ) { + first = 0; + do { + last = nameServer.find( separator, first ); + if ( last < 0 ) + last = nameServer.length(); + TQDns tmp( nameServer.mid( first, last-first ), TQDns::A ); + TQValueList<TQHostAddress> address = tmp.addresses(); + Q_LONG i = address.count(); + while( i ) + ns->append( new TQHostAddress(address[--i]) ); + first = last+1; + } while( first < (int)nameServer.length() ); + } + + searchList = searchList + " " + domainName; + searchList = searchList.simplifyWhiteSpace().lower(); + first = 0; + do { + last = searchList.find( separator, first ); + if ( last < 0 ) + last = searchList.length(); + domains->append( qstrdup( searchList.mid( first, last-first ) ) ); + first = last+1; + } while( first < (int)searchList.length() ); +} + +#elif defined(Q_OS_UNIX) + +#if defined(Q_DNS_SYNCHRONOUS) +void TQDns::doSynchronousLookup() +{ + if ( t!=None && !l.isEmpty() ) { + TQValueListIterator<TQString> it = n.begin(); + TQValueListIterator<TQString> end = n.end(); + int type; + switch( t ) { + case TQDns::A: + type = 1; + break; + case TQDns::Aaaa: + type = 28; + break; + case TQDns::Mx: + type = 15; + break; + case TQDns::Srv: + type = 33; + break; + case TQDns::Cname: + type = 5; + break; + case TQDns::Ptr: + type = 12; + break; + case TQDns::Txt: + type = 16; + break; + default: + type = (char)255; // any + break; + } + while( it != end ) { + TQString s = *it; + it++; + TQByteArray ba( 512 ); + int len = res_search( s.latin1(), 1, type, (uchar*)ba.data(), ba.size() ); + if ( len > 0 ) { + ba.resize( len ); + + TQDnsQuery * query = new TQDnsQuery; + query->started = now(); + query->id = ++::id; + query->t = t; + query->l = s; + TQDnsAnswer 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 +#else +#endif + +void TQDns::doResInit() +{ + if ( ns ) + return; + ns = new TQPtrList<TQHostAddress>; + ns->setAutoDelete( TRUE ); + domains = new TQStrList( TRUE ); + domains->setAutoDelete( TRUE ); + + // read resolv.conf manually. + TQFile resolvConf("/etc/resolv.conf"); + if (resolvConf.open(IO_ReadOnly)) { + TQTextStream stream( &resolvConf ); + TQString line; + + while ( !stream.atEnd() ) { + line = stream.readLine(); + TQStringList list = TQStringList::split( " ", line ); + if ( line.startsWith( "#" ) || list.count() < 2 ) + continue; + const TQString type = list[0].lower(); + + if ( type == "nameserver" ) { + TQHostAddress *address = new TQHostAddress(); + if ( address->setAddress( TQString(list[1]) ) ) { + // only add ipv6 addresses from resolv.conf if + // this host supports ipv6. + if ( address->isIPv4Address() || ipv6support ) + ns->append( address ); + else + delete address; + } else { + delete address; + } + } else if ( type == "search" ) { + TQStringList srch = TQStringList::split( " ", list[1] ); + for ( TQStringList::Iterator i = srch.begin(); i != srch.end(); ++i ) + domains->append( (*i).lower() ); + + } else if ( type == "domain" ) { + domains->append( list[1].lower() ); + } + } + } + + if (ns->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++ ) + ns->append( new TQHostAddress( 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]) ) + domains->append( TQString::fromLatin1( res.dnsrch[i] ).lower() ); + else + break; + } +# endif + if ( *res.defdname ) + domains->append( TQString::fromLatin1( res.defdname ).lower() ); +#else + qdns_res_init(); + int i; + // find the name servers to use + for( i=0; i < MAXNS && i < _res.nscount; i++ ) + ns->append( new TQHostAddress( 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]) ) + domains->append( TQString::fromLatin1( _res.dnsrch[i] ).lower() ); + else + break; + } +# endif + if ( *_res.defdname ) + domains->append( TQString::fromLatin1( _res.defdname ).lower() ); +#endif + + // the code above adds "0.0.0.0" as a name server at the slightest + // hint of trouble. so remove those again. + ns->first(); + while( ns->current() ) { + if ( ns->current()->isNull() ) + delete ns->take(); + else + ns->next(); + } + } + + TQFile hosts( TQString::fromLatin1( "/etc/hosts" ) ); + if ( hosts.open( IO_ReadOnly ) ) { + // read the /etc/hosts file, creating long-life A and PTR RRs + // for the things we find. + TQTextStream i( &hosts ); + TQString line; + while( !i.atEnd() ) { + line = i.readLine().simplifyWhiteSpace().lower(); + uint n = 0; + while( n < line.length() && line[(int)n] != '#' ) + n++; + line.truncate( n ); + n = 0; + while( n < line.length() && !line[(int)n].isSpace() ) + n++; + TQString ip = line.left( n ); + TQHostAddress a; + a.setAddress( ip ); + if ( ( a.isIPv4Address() || a.isIPv6Address() ) && !a.isNull() ) { + bool first = TRUE; + line = line.mid( n+1 ); + n = 0; + while( n < line.length() && !line[(int)n].isSpace() ) + n++; + TQString hostname = line.left( n ); + // ### in case of bad syntax, hostname is invalid. do we care? + if ( n ) { + TQDnsRR * rr = new TQDnsRR( hostname ); + if ( a.isIPv4Address() ) + rr->t = TQDns::A; + else + rr->t = TQDns::Aaaa; + rr->address = a; + rr->deleteTime = UINT_MAX; + rr->expireTime = UINT_MAX; + rr->current = TRUE; + if ( first ) { + first = FALSE; + TQDnsRR * ptr = new TQDnsRR( TQDns::toInAddrArpaDomain( a ) ); + ptr->t = TQDns::Ptr; + ptr->target = hostname; + ptr->deleteTime = UINT_MAX; + ptr->expireTime = UINT_MAX; + ptr->current = TRUE; + } + } + } + } + } +} + +#endif + +#endif // QT_NO_DNS diff --git a/src/network/qdns.h b/src/network/qdns.h new file mode 100644 index 000000000..b75833ef9 --- /dev/null +++ b/src/network/qdns.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Definition of TQDns class. +** +** Created : 991122 +** +** Copyright (C) 1999-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#ifndef TQDNS_H +#define TQDNS_H + +#ifndef QT_H +#include "qobject.h" +#include "qhostaddress.h" +#include "qsocketnotifier.h" +#include "qstringlist.h" +#endif // QT_H + +#if !defined( QT_MODULE_NETWORK ) || defined( QT_LICENSE_PROFESSIONAL ) || defined( QT_INTERNAL_NETWORK ) +#define TQM_EXPORT_DNS +#else +#define TQM_EXPORT_DNS Q_EXPORT +#endif + +#ifndef QT_NO_DNS + +//#define Q_DNS_SYNCHRONOUS + +class TQDnsPrivate; + +class TQM_EXPORT_DNS TQDns: public TQObject { + Q_OBJECT +public: + enum RecordType { + None, + A, Aaaa, + Mx, Srv, + Cname, + Ptr, + Txt + }; + + TQDns(); + TQDns( const TQString & label, RecordType rr = A ); + TQDns( const TQHostAddress & address, RecordType rr = Ptr ); + virtual ~TQDns(); + + // to set/change the query + virtual void setLabel( const TQString & label ); + virtual void setLabel( const TQHostAddress & address ); + TQString 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 + TQValueList<TQHostAddress> addresses() const; + + class TQM_EXPORT_DNS MailServer { + public: + MailServer( const TQString & n=TQString::null, Q_UINT16 p=0 ) + :name(n), priority(p) {} + TQString name; + Q_UINT16 priority; + Q_DUMMY_COMPARISON_OPERATOR(MailServer) + }; + TQValueList<MailServer> mailServers() const; + + class TQM_EXPORT_DNS Server { + public: + Server(const TQString & n=TQString::null, Q_UINT16 p=0, Q_UINT16 w=0, Q_UINT16 po=0 ) + : name(n), priority(p), weight(w), port(po) {} + TQString name; + Q_UINT16 priority; + Q_UINT16 weight; + Q_UINT16 port; + Q_DUMMY_COMPARISON_OPERATOR(Server) + }; + TQValueList<Server> servers() const; + + TQStringList hostNames() const; + + TQStringList texts() const; + + TQString canonicalName() const; // ### real-world but uncommon: TQStringList + + TQStringList qualifiedNames() const { return n; } + +#if defined(Q_DNS_SYNCHRONOUS) +protected: + void connectNotify( const char *signal ); +#endif + +signals: + void resultsReady(); + +private slots: + void startQuery(); + +private: + static void doResInit(); + void setStartQueryTimer(); + static TQString toInAddrArpaDomain( const TQHostAddress &address ); +#if defined(Q_DNS_SYNCHRONOUS) + void doSynchronousLookup(); +#endif + + TQString l; + TQStringList n; + RecordType t; + TQDnsPrivate * d; + + friend class TQDnsAnswer; + friend class TQDnsManager; +}; + + +// TQDnsSocket are sockets that are used for DNS lookup + +class TQDnsSocket: public TQObject { + Q_OBJECT + // note: Private not public. This class contains NO public API. +protected: + TQDnsSocket( TQObject *, const char * ); + virtual ~TQDnsSocket(); + +private slots: + virtual void cleanCache(); + virtual void retransmit(); + virtual void answer(); +}; + +#endif // QT_NO_DNS + +#endif // TQDNS_H diff --git a/src/network/qftp.cpp b/src/network/qftp.cpp new file mode 100644 index 000000000..560917e34 --- /dev/null +++ b/src/network/qftp.cpp @@ -0,0 +1,2419 @@ +/**************************************************************************** +** +** Implementation of TQFtp class. +** +** Created : 970521 +** +** Copyright (C) 1997-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qftp.h" + +#ifndef QT_NO_NETWORKPROTOCOL_FTP + +#include "qsocket.h" +#include "qsocketdevice.h" +#include "qurlinfo.h" +#include "qurloperator.h" +#include "qstringlist.h" +#include "qregexp.h" +#include "qtimer.h" +#include "qfileinfo.h" +#include "qptrdict.h" // binary compatibility + +#ifndef QT_NO_TEXTCODEC +#include "qtextcodec.h" +#endif + +//#define TQFTPPI_DEBUG +//#define TQFTPDTP_DEBUG + + +class TQFtpPI; + +class TQFtpDTP : public TQObject +{ + Q_OBJECT + +public: + enum ConnectState { + CsHostFound, + CsConnected, + CsClosed, + CsHostNotFound, + CsConnectionRefused + }; + + TQFtpDTP( TQFtpPI *p, TQObject *parent=0, const char *name=0 ); + + void setData( TQByteArray * ); + void setDevice( TQIODevice * ); + void writeData(); + + void setBytesTotal( int bytes ) + { + bytesTotal = bytes; + bytesDone = 0; + emit dataTransferProgress( bytesDone, bytesTotal ); + } + + bool hasError() const; + TQString errorMessage() const; + void clearError(); + + void connectToHost( const TQString & host, Q_UINT16 port ) + { socket.connectToHost( host, port ); } + + TQSocket::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; + } + + TQByteArray readAll() + { + TQByteArray tmp = socket.readAll(); + bytesDone += tmp.size(); + return tmp; + } + + void abortConnection(); + + static bool parseDir( const TQString &buffer, const TQString &userName, TQUrlInfo *info ); + +signals: + void listInfo( const TQUrlInfo& ); + 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; + } + + TQSocket socket; + TQFtpPI *pi; + TQString 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 { + TQByteArray *ba; + TQIODevice *dev; + } data; + bool is_ba; +}; + +class TQFtpPI : public TQObject +{ + Q_OBJECT + +public: + TQFtpPI( TQObject *parent = 0 ); + + void connectToHost( const TQString &host, Q_UINT16 port ); + + bool sendCommands( const TQStringList &cmds ); + bool sendCommand( const TQString &cmd ) + { return sendCommands( TQStringList( cmd ) ); } + + void clearPendingCommands(); + void abort(); + + TQString currentCommand() const + { return currentCmd; } + + bool rawCommand; + + TQFtpDTP 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 TQString& ); + void error( int, const TQString& ); + void rawFtpReply( int, const TQString& ); + +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(); + + TQSocket commandSocket; + TQString replyText; + char replyCode[3]; + State state; + AbortState abortState; + TQStringList pendingCommands; + TQString currentCmd; + + bool waitForDtpToConnect; + bool waitForDtpToClose; +}; + +/********************************************************************** + * + * TQFtpCommand implemenatation + * + *********************************************************************/ +class TQFtpCommand +{ +public: + TQFtpCommand( TQFtp::Command cmd, TQStringList raw ); + TQFtpCommand( TQFtp::Command cmd, TQStringList raw, const TQByteArray &ba ); + TQFtpCommand( TQFtp::Command cmd, TQStringList raw, TQIODevice *dev ); + ~TQFtpCommand(); + + int id; + TQFtp::Command command; + TQStringList rawCmds; + + // If is_ba is TRUE, ba is used; ba is never 0. + // Otherwise dev is used; dev can be 0 or not. + union { + TQByteArray *ba; + TQIODevice *dev; + } data; + bool is_ba; + + static int idCounter; +}; + +int TQFtpCommand::idCounter = 0; + +TQFtpCommand::TQFtpCommand( TQFtp::Command cmd, TQStringList raw ) + : command(cmd), rawCmds(raw), is_ba(FALSE) +{ + id = ++idCounter; + data.dev = 0; +} + +TQFtpCommand::TQFtpCommand( TQFtp::Command cmd, TQStringList raw, const TQByteArray &ba ) + : command(cmd), rawCmds(raw), is_ba(TRUE) +{ + id = ++idCounter; + data.ba = new TQByteArray( ba ); +} + +TQFtpCommand::TQFtpCommand( TQFtp::Command cmd, TQStringList raw, TQIODevice *dev ) + : command(cmd), rawCmds(raw), is_ba(FALSE) +{ + id = ++idCounter; + data.dev = dev; +} + +TQFtpCommand::~TQFtpCommand() +{ + if ( is_ba ) + delete data.ba; +} + +/********************************************************************** + * + * TQFtpDTP implemenatation + * + *********************************************************************/ +TQFtpDTP::TQFtpDTP( TQFtpPI *p, TQObject *parent, const char *name ) : + TQObject( parent, name ), + socket( 0, "TQFtpDTP_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 TQFtpDTP::setData( TQByteArray *ba ) +{ + is_ba = TRUE; + data.ba = ba; +} + +void TQFtpDTP::setDevice( TQIODevice *dev ) +{ + is_ba = FALSE; + data.dev = dev; +} + +void TQFtpDTP::writeData() +{ + if ( is_ba ) { +#if defined(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::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(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::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 TQFtpDTP::hasError() const +{ + return !err.isNull(); +} + +inline TQString TQFtpDTP::errorMessage() const +{ + return err; +} + +inline void TQFtpDTP::clearError() +{ + err = TQString::null; +} + +void TQFtpDTP::abortConnection() +{ +#if defined(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::abortConnection" ); +#endif + callWriteData = FALSE; + clearData(); + + socket.clearPendingData(); + socket.close(); +} + +bool TQFtpDTP::parseDir( const TQString &buffer, const TQString &userName, TQUrlInfo *info ) +{ + TQStringList lst = TQStringList::split( " ", buffer ); + + if ( lst.count() < 9 ) + return FALSE; + + TQString tmp; + + // permissions + tmp = lst[ 0 ]; + + if ( tmp[ 0 ] == TQChar( 'd' ) ) { + info->setDir( TRUE ); + info->setFile( FALSE ); + info->setSymLink( FALSE ); + } else if ( tmp[ 0 ] == TQChar( '-' ) ) { + info->setDir( FALSE ); + info->setFile( TRUE ); + info->setSymLink( FALSE ); + } else if ( tmp[ 0 ] == TQChar( '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 ] == 'r'); + perms[0][1] = (tmp[ 2 ] == 'w'); + perms[0][2] = (tmp[ 3 ] == 'x'); + perms[1][0] = (tmp[ 4 ] == 'r'); + perms[1][1] = (tmp[ 5 ] == 'w'); + perms[1][2] = (tmp[ 6 ] == 'x'); + perms[2][0] = (tmp[ 7 ] == 'r'); + perms[2][1] = (tmp[ 8 ] == 'w'); + perms[2][2] = (tmp[ 9 ] == '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 |= TQUrlInfo::ReadOwner; + if ( perms[ user ][ writable ] ) + p |= TQUrlInfo::WriteOwner; + if ( perms[ user ][ executable ] ) + p |= TQUrlInfo::ExeOwner; + if ( perms[ group ][ readable ] ) + p |= TQUrlInfo::ReadGroup; + if ( perms[ group ][ writable ] ) + p |= TQUrlInfo::WriteGroup; + if ( perms[ group ][ executable ] ) + p |= TQUrlInfo::ExeGroup; + if ( perms[ other ][ readable ] ) + p |= TQUrlInfo::ReadOther; + if ( perms[ other ][ writable ] ) + p |= TQUrlInfo::WriteOther; + if ( perms[ other ][ executable ] ) + p |= TQUrlInfo::ExeOther; + info->setPermissions( p ); + + // size + tmp = lst[ 4 ]; + info->setSize( tmp.toInt() ); + + // date and time + TQTime time; + TQString dateStr; + dateStr += "Sun "; + lst[ 5 ][ 0 ] = lst[ 5 ][ 0 ].upper(); + dateStr += lst[ 5 ]; + dateStr += ' '; + dateStr += lst[ 6 ]; + dateStr += ' '; + + if ( lst[ 7 ].contains( ":" ) ) { + time = TQTime( lst[ 7 ].left( 2 ).toInt(), lst[ 7 ].right( 2 ).toInt() ); + dateStr += TQString::number( TQDate::currentDate().year() ); + } else { + dateStr += lst[ 7 ]; + } + + TQDate date = TQDate::fromString( dateStr ); + info->setLastModified( TQDateTime( date, time ) ); + + if ( lst[ 7 ].contains( ":" ) ) { + const int futureTolerance = 600; + if( info->lastModified().secsTo( TQDateTime::currentDateTime() ) < -futureTolerance ) { + TQDateTime dt = info->lastModified(); + TQDate 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 { + TQString n; + for ( uint i = 8; i < lst.count(); ++i ) + n += lst[ i ] + " "; + n = n.stripWhiteSpace(); + info->setName( n ); + } + return TRUE; +} + +void TQFtpDTP::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(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::connectState( CsConnected )" ); +#endif + emit connectState( TQFtpDTP::CsConnected ); +} + +void TQFtpDTP::socketReadyRead() +{ + if ( pi->currentCommand().isEmpty() ) { + socket.close(); +#if defined(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::connectState( CsClosed )" ); +#endif + emit connectState( TQFtpDTP::CsClosed ); + return; + } + + if ( pi->currentCommand().startsWith("LIST") ) { + while ( socket.canReadLine() ) { + TQUrlInfo i; + TQString line = socket.readLine(); +#if defined(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP read (list): '%s'", line.latin1() ); +#endif + if ( parseDir( line, "", &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( "No such file or directory\r\n" ) ) + err = line; + } + } + } else { + if ( !is_ba && data.dev ) { + TQByteArray 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(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP 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(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP readyRead: %d bytes available (total %d bytes read)", (int)bytesAvailable(), bytesDone ); +#endif + emit dataTransferProgress( bytesDone+socket.bytesAvailable(), bytesTotal ); + emit readyRead(); + } + } +} + +void TQFtpDTP::socketError( int e ) +{ + if ( e == TQSocket::ErrHostNotFound ) { +#if defined(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::connectState( CsHostNotFound )" ); +#endif + emit connectState( TQFtpDTP::CsHostNotFound ); + } else if ( e == TQSocket::ErrConnectionRefused ) { +#if defined(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::connectState( CsConnectionRefused )" ); +#endif + emit connectState( TQFtpDTP::CsConnectionRefused ); + } +} + +void TQFtpDTP::socketConnectionClosed() +{ + if ( !is_ba && data.dev ) { + clearData(); + } +#if defined(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::connectState( CsClosed )" ); +#endif + emit connectState( TQFtpDTP::CsClosed ); +} + +void TQFtpDTP::socketBytesWritten( int bytes ) +{ + bytesDone += bytes; +#if defined(TQFTPDTP_DEBUG) + qDebug( "TQFtpDTP::bytesWritten( %d )", bytesDone ); +#endif + emit dataTransferProgress( bytesDone, bytesTotal ); + if ( callWriteData ) + writeData(); +} + +/********************************************************************** + * + * TQFtpPI implemenatation + * + *********************************************************************/ +TQFtpPI::TQFtpPI( TQObject *parent ) : + TQObject( parent ), + rawCommand(FALSE), + dtp( this ), + commandSocket( 0, "TQFtpPI_socket" ), + state( Begin ), abortState( None ), + currentCmd( TQString::null ), + 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 TQFtpPI::connectToHost( const TQString &host, Q_UINT16 port ) +{ + emit connectState( TQFtp::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 TQFtpPI::sendCommands( const TQStringList &cmds ) +{ + if ( !pendingCommands.isEmpty() ) + return FALSE; + + if ( commandSocket.state()!=TQSocket::Connected || state!=Idle ) { + emit error( TQFtp::NotConnected, TQFtp::tr( "Not connected" ) ); + return TRUE; // there are no pending commands + } + + pendingCommands = cmds; + startNextCmd(); + return TRUE; +} + +void TQFtpPI::clearPendingCommands() +{ + pendingCommands.clear(); + dtp.abortConnection(); + currentCmd = TQString::null; + state = Idle; +} + +void TQFtpPI::abort() +{ + pendingCommands.clear(); + + if ( abortState != None ) + // ABOR already sent + return; + + abortState = AbortStarted; +#if defined(TQFTPPI_DEBUG) + qDebug( "TQFtpPI send: ABOR" ); +#endif + commandSocket.writeBlock( "ABOR\r\n", 6 ); + + if ( currentCmd.startsWith("STOR ") ) + dtp.abortConnection(); +} + +void TQFtpPI::hostFound() +{ + emit connectState( TQFtp::Connecting ); +} + +void TQFtpPI::connected() +{ + state = Begin; +#if defined(TQFTPPI_DEBUG) +// qDebug( "TQFtpPI state: %d [connected()]", state ); +#endif + emit connectState( TQFtp::Connected ); +} + +void TQFtpPI::connectionClosed() +{ + commandSocket.close(); + emit connectState( TQFtp::Unconnected ); +} + +void TQFtpPI::delayedCloseFinished() +{ + emit connectState( TQFtp::Unconnected ); +} + +void TQFtpPI::error( int e ) +{ + if ( e == TQSocket::ErrHostNotFound ) { + emit connectState( TQFtp::Unconnected ); + emit error( TQFtp::HostNotFound, + TQFtp::tr( "Host %1 not found" ).arg( commandSocket.peerName() ) ); + } else if ( e == TQSocket::ErrConnectionRefused ) { + emit connectState( TQFtp::Unconnected ); + emit error( TQFtp::ConnectionRefused, + TQFtp::tr( "Connection refused to host %1" ).arg( commandSocket.peerName() ) ); + } +} + +void TQFtpPI::readyRead() +{ + if ( waitForDtpToClose ) + return; + + while ( commandSocket.canReadLine() ) { + // read line with respect to line continuation + TQString line = 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; + } + } + } + TQString endOfMultiLine; + endOfMultiLine[0] = '0' + replyCode[0]; + endOfMultiLine[1] = '0' + replyCode[1]; + endOfMultiLine[2] = '0' + replyCode[2]; + endOfMultiLine[3] = ' '; + TQString lineCont( endOfMultiLine ); + lineCont[3] = '-'; + TQString lineLeft4 = line.left(4); + + while ( lineLeft4 != endOfMultiLine ) { + if ( lineLeft4 == lineCont ) + replyText += line.mid( 4 ); // strip 'xyz-' + else + replyText += line; + if ( !commandSocket.canReadLine() ) + return; + line = commandSocket.readLine(); + lineLeft4 = line.left(4); + } + replyText += line.mid( 4 ); // strip reply code 'xyz ' + if ( replyText.endsWith("\r\n") ) + replyText.truncate( replyText.length()-2 ); + + if ( processReply() ) + replyText = ""; + } +} + +/* + 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 TQFtpPI::processReply() +{ +#if defined(TQFTPPI_DEBUG) +// qDebug( "TQFtpPI state: %d [processReply() begin]", state ); + if ( replyText.length() < 400 ) + qDebug( "TQFtpPI recv: %d %s", 100*replyCode[0]+10*replyCode[1]+replyCode[2], replyText.latin1() ); + else + qDebug( "TQFtpPI 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() != TQSocket::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( TQFtp::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 + state = table[ replyCode[0] - 1 ]; + break; + default: + // ### spontaneous message + return TRUE; + } +#if defined(TQFTPPI_DEBUG) +// qDebug( "TQFtpPI 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. + TQRegExp addrPortPattern("(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)"); + if (addrPortPattern.search(replyText) == -1) { +#if defined(TQFTPPI_DEBUG) + qDebug( "TQFtp: bad 227 response -- address and port information missing" ); +#endif + // ### error handling + } else { + TQStringList lst = addrPortPattern.capturedTexts(); + TQString host = lst[1] + "." + lst[2] + "." + lst[3] + "." + 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("USER ") && pendingCommands.count()>0 && + pendingCommands.first().startsWith("PASS ") ) { + // no need to send the PASS -- we are already logged in + pendingCommands.pop_front(); + } + // 230 User logged in, proceed. + emit connectState( TQFtp::LoggedIn ); + } else if ( replyCodeInt == 213 ) { + // 213 File status. + if ( currentCmd.startsWith("SIZE ") ) + dtp.setBytesTotal( replyText.simplifyWhiteSpace().toInt() ); + } else if ( replyCode[0]==1 && currentCmd.startsWith("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( TQFtp::UnknownError, dtp.errorMessage() ); + dtp.clearError(); + } + startNextCmd(); + break; + case Waiting: + // ### do nothing + break; + case Failure: + emit error( TQFtp::UnknownError, replyText ); + state = Idle; + startNextCmd(); + break; + } +#if defined(TQFTPPI_DEBUG) +// qDebug( "TQFtpPI state: %d [processReply() end]", state ); +#endif + return TRUE; +} + +#ifndef QT_NO_TEXTCODEC +TQM_EXPORT_FTP TQTextCodec *qt_ftp_filename_codec = 0; +#endif + +/* + Starts next pending command. Returns FALSE if there are no pending commands, + otherwise it returns TRUE. +*/ +bool TQFtpPI::startNextCmd() +{ + if ( waitForDtpToConnect ) + // don't process any new commands until we are connected + return TRUE; + +#if defined(TQFTPPI_DEBUG) + if ( state != Idle ) + qDebug( "TQFtpPI startNextCmd: Internal error! TQFtpPI called in non-Idle state %d", state ); +#endif + if ( pendingCommands.isEmpty() ) { + currentCmd = TQString::null; + emit finished( replyText ); + return FALSE; + } + currentCmd = pendingCommands.first(); + pendingCommands.pop_front(); +#if defined(TQFTPPI_DEBUG) + qDebug( "TQFtpPI send: %s", currentCmd.left( currentCmd.length()-2 ).latin1() ); +#endif + state = Waiting; +#ifndef QT_NO_TEXTCODEC + if ( qt_ftp_filename_codec ) { + int len = 0; + TQCString enc = qt_ftp_filename_codec->fromUnicode(currentCmd,len); + commandSocket.writeBlock( enc.data(), len ); + } else +#endif + { + commandSocket.writeBlock( currentCmd.latin1(), currentCmd.length() ); + } + return TRUE; +} + +void TQFtpPI::dtpConnectState( int s ) +{ + switch ( s ) { + case TQFtpDTP::CsClosed: + if ( waitForDtpToClose ) { + // there is an unprocessed reply + if ( processReply() ) + replyText = ""; + else + return; + } + waitForDtpToClose = FALSE; + readyRead(); + return; + case TQFtpDTP::CsConnected: + waitForDtpToConnect = FALSE; + startNextCmd(); + return; + case TQFtpDTP::CsHostNotFound: + case TQFtpDTP::CsConnectionRefused: + emit error( TQFtp::ConnectionRefused, + TQFtp::tr( "Connection refused for data connection" ) ); + startNextCmd(); + return; + default: + return; + } +} + +/********************************************************************** + * + * TQFtpPrivate + * + *********************************************************************/ +class TQFtpPrivate +{ +public: + TQFtpPrivate() : + close_waitForStateChange(FALSE), + state( TQFtp::Unconnected ), + error( TQFtp::NoError ), + npWaitForLoginDone( FALSE ) + { pending.setAutoDelete( TRUE ); } + + TQFtpPI pi; + TQPtrList<TQFtpCommand> pending; + bool close_waitForStateChange; + TQFtp::State state; + TQFtp::Error error; + TQString errorString; + + bool npWaitForLoginDone; +}; + +static TQPtrDict<TQFtpPrivate> *d_ptr = 0; +static void cleanup_d_ptr() +{ + delete d_ptr; + d_ptr = 0; +} +static TQFtpPrivate* d( const TQFtp* foo ) +{ + if ( !d_ptr ) { + d_ptr = new TQPtrDict<TQFtpPrivate>; + d_ptr->setAutoDelete( TRUE ); + qAddPostRoutine( cleanup_d_ptr ); + } + TQFtpPrivate* ret = d_ptr->find( (void*)foo ); + if ( ! ret ) { + ret = new TQFtpPrivate; + d_ptr->replace( (void*) foo, ret ); + } + return ret; +} + +static void delete_d( const TQFtp* foo ) +{ + if ( d_ptr ) + d_ptr->remove( (void*) foo ); +} + +/********************************************************************** + * + * TQFtp implementation + * + *********************************************************************/ +/*! + \class TQFtp qftp.h + \brief The TQFtp class provides an implementation of the FTP protocol. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + This class provides two different interfaces: one is the + TQNetworkProtocol interface that allows you to use FTP through the + TQUrlOperator 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 TQFtp with the TQNetworkProtocol interface, you + do not use it directly, but rather through a TQUrlOperator, for + example: + + \code + TQUrlOperator op( "ftp://ftp.trolltech.com" ); + op.listChildren(); // Asks the server to provide a directory listing + \endcode + + This code will only work if the TQFtp class is registered; to + register the class, you must call qInitNetworkProtocols() before + using a TQUrlOperator with TQFtp. + + 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: + + \code + TQFtp *ftp = new TQFtp( this ); // this is an optional TQObject parent + ftp->connectToHost( "ftp.trolltech.com" ); + ftp->login(); + \endcode + + 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: + + \code + ftp->connectToHost( "ftp.trolltech.com" ); // id == 1 + ftp->login(); // id == 2 + ftp->cd( "qt" ); // id == 3 + ftp->get( "INSTALL" ); // id == 4 + ftp->close(); // id == 5 + \endcode + + For this example the following sequence of signals is emitted + (with small variations, depending on network traffic, etc.): + + \code + start( 1 ) + stateChanged( HostLookup ) + stateChanged( Connecting ) + stateChanged( Connected ) + finished( 1, FALSE ) + + start( 2 ) + stateChanged( LoggedIn ) + finished( 2, FALSE ) + + start( 3 ) + finished( 3, FALSE ) + + start( 4 ) + dataTransferProgress( 0, 3798 ) + dataTransferProgress( 2896, 3798 ) + readyRead() + dataTransferProgress( 3798, 3798 ) + readyRead() + finished( 4, FALSE ) + + start( 5 ) + stateChanged( Closing ) + stateChanged( Unconnected ) + finished( 5, FALSE ) + + done( FALSE ) + \endcode + + The dataTransferProgress() signal in the above example is useful + if you want to show a \link TQProgressBar progressbar \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: + + \code + start( 1 ) + stateChanged( HostLookup ) + stateChanged( Connecting ) + stateChanged( Connected ) + finished( 1, FALSE ) + + start( 2 ) + finished( 2, TRUE ) + + done( TRUE ) + \endcode + + 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 + TQUrlOperator() 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 \link network.html TQt Network Documentation \endlink TQNetworkProtocol, TQUrlOperator TQHttp +*/ + +/*! + Constructs a TQFtp object. +*/ +TQFtp::TQFtp() : TQNetworkProtocol() +{ + init(); +} + +/*! + Constructs a TQFtp object. The \a parent and \a name parameters + are passed to the TQObject constructor. +*/ +TQFtp::TQFtp( TQObject *parent, const char *name ) : TQNetworkProtocol() +{ + if ( parent ) + parent->insertChild( this ); + setName( name ); + init(); +} + +void TQFtp::init() +{ + TQFtpPrivate *d = ::d( this ); + d->errorString = tr( "Unknown error" ); + + connect( &d->pi, SIGNAL(connectState(int)), + SLOT(piConnectState(int)) ); + connect( &d->pi, SIGNAL(finished(const TQString&)), + SLOT(piFinished(const TQString&)) ); + connect( &d->pi, SIGNAL(error(int,const TQString&)), + SLOT(piError(int,const TQString&)) ); + connect( &d->pi, SIGNAL(rawFtpReply(int,const TQString&)), + SLOT(piFtpReply(int,const TQString&)) ); + + 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(const TQUrlInfo&)), + SIGNAL(listInfo(const TQUrlInfo&)) ); +} + +/*! + \enum TQFtp::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 TQFtp::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 TQFtp::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 TQFtp::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 TQFtp::listInfo( const TQUrlInfo &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 TQFtp::commandStarted( int id ) + + This signal is emitted when processing the command identified by + \a id starts. + + \sa commandFinished() done() +*/ + +/*! + \fn void TQFtp::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 TQFtp::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 TQFtp::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 TQFtp::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 TQFtp 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 TQProgressBar, 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() TQProgressBar::setProgress() +*/ + +/*! + \fn void TQFtp::rawCommandReply( int replyCode, const TQString &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 TQFtp::connectToHost( const TQString &host, Q_UINT16 port ) +{ + TQStringList cmds; + cmds << host; + cmds << TQString::number( (uint)port ); + return addCommand( new TQFtpCommand( 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 TQFtp::login( const TQString &user, const TQString &password ) +{ + TQStringList cmds; + cmds << ( TQString("USER ") + ( user.isNull() ? TQString("anonymous") : user ) + "\r\n" ); + cmds << ( TQString("PASS ") + ( password.isNull() ? TQString("anonymous@") : password ) + "\r\n" ); + return addCommand( new TQFtpCommand( 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 TQFtp::close() +{ + return addCommand( new TQFtpCommand( Close, TQStringList("TQUIT\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 TQFtp::list( const TQString &dir ) +{ + TQStringList cmds; + cmds << "TYPE A\r\n"; + cmds << "PASV\r\n"; + if ( dir.isEmpty() ) + cmds << "LIST\r\n"; + else + cmds << ( "LIST " + dir + "\r\n" ); + return addCommand( new TQFtpCommand( 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 TQFtp::cd( const TQString &dir ) +{ + return addCommand( new TQFtpCommand( Cd, TQStringList("CWD "+dir+"\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 TQFtp::get( const TQString &file, TQIODevice *dev ) +{ + TQStringList cmds; + cmds << ( "SIZE " + file + "\r\n" ); + cmds << "TYPE I\r\n"; + cmds << "PASV\r\n"; + cmds << ( "RETR " + file + "\r\n" ); + if ( dev ) + return addCommand( new TQFtpCommand( Get, cmds, dev ) ); + return addCommand( new TQFtpCommand( 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 TQFtp::put( const TQByteArray &data, const TQString &file ) +{ + TQStringList cmds; + cmds << "TYPE I\r\n"; + cmds << "PASV\r\n"; + cmds << ( "ALLO " + TQString::number(data.size()) + "\r\n" ); + cmds << ( "STOR " + file + "\r\n" ); + return addCommand( new TQFtpCommand( 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 TQFtp::put( TQIODevice *dev, const TQString &file ) +{ + TQStringList cmds; + cmds << "TYPE I\r\n"; + cmds << "PASV\r\n"; + if ( !dev->isSequentialAccess() ) + cmds << ( "ALLO " + TQString::number(dev->size()) + "\r\n" ); + cmds << ( "STOR " + file + "\r\n" ); + return addCommand( new TQFtpCommand( 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 TQFtp::remove( const TQString &file ) +{ + return addCommand( new TQFtpCommand( Remove, TQStringList("DELE "+file+"\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 TQFtp::mkdir( const TQString &dir ) +{ + return addCommand( new TQFtpCommand( Mkdir, TQStringList("MKD "+dir+"\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 TQFtp::rmdir( const TQString &dir ) +{ + return addCommand( new TQFtpCommand( Rmdir, TQStringList("RMD "+dir+"\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 TQFtp::rename( const TQString &oldname, const TQString &newname ) +{ + TQStringList cmds; + cmds << ( "RNFR " + oldname + "\r\n" ); + cmds << ( "RNTO " + newname + "\r\n" ); + return addCommand( new TQFtpCommand( 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 TQFtp 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 TQFtp::rawCommand( const TQString &command ) +{ + TQString cmd = command.stripWhiteSpace() + "\r\n"; + return addCommand( new TQFtpCommand( RawCommand, TQStringList(cmd) ) ); +} + +/*! + Returns the number of bytes that can be read from the data socket + at the moment. + + \sa get() readyRead() readBlock() readAll() +*/ +Q_ULONG TQFtp::bytesAvailable() const +{ + TQFtpPrivate *d = ::d( 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 TQFtp::readBlock( char *data, Q_ULONG maxlen ) +{ + TQFtpPrivate *d = ::d( 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() +*/ +TQByteArray TQFtp::readAll() +{ + TQFtpPrivate *d = ::d( 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 TQFtp::abort() +{ + TQFtpPrivate *d = ::d( 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 TQFtp::currentId() const +{ + TQFtpPrivate *d = ::d( this ); + TQFtpCommand *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() +*/ +TQFtp::Command TQFtp::currentCommand() const +{ + TQFtpPrivate *d = ::d( this ); + TQFtpCommand *c = d->pending.getFirst(); + if ( c == 0 ) + return None; + return c->command; +} + +/*! + Returns the TQIODevice 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 TQIODevice in the slot connected to + the commandFinished() signal. + + \sa get() put() +*/ +TQIODevice* TQFtp::currentDevice() const +{ + TQFtpPrivate *d = ::d( this ); + TQFtpCommand *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 TQFtp::hasPendingCommands() const +{ + TQFtpPrivate *d = ::d( 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 this as well, use abort(). + + \sa hasPendingCommands() abort() +*/ +void TQFtp::clearPendingCommands() +{ + TQFtpPrivate *d = ::d( this ); + TQFtpCommand *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() +*/ +TQFtp::State TQFtp::state() const +{ + TQFtpPrivate *d = ::d( 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. +*/ +TQFtp::Error TQFtp::error() const +{ + TQFtpPrivate *d = ::d( 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 TQt, the string has already passed through + tr(). +*/ +TQString TQFtp::errorString() const +{ + TQFtpPrivate *d = ::d( this ); + return d->errorString; +} + +int TQFtp::addCommand( TQFtpCommand *cmd ) +{ + TQFtpPrivate *d = ::d( this ); + d->pending.append( cmd ); + + if ( d->pending.count() == 1 ) + // don't emit the commandStarted() signal before the id is returned + TQTimer::singleShot( 0, this, SLOT(startNextCommand()) ); + + return cmd->id; +} + +void TQFtp::startNextCommand() +{ + TQFtpPrivate *d = ::d( this ); + + TQFtpCommand *c = d->pending.getFirst(); + if ( c == 0 ) + return; + + d->error = NoError; + d->errorString = 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(IO_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 = TQFtp::Closing; + emit stateChanged( d->state ); + } + if ( !d->pi.sendCommands( c->rawCmds ) ) { + // ### error handling (this case should not happen) + } + } +} + +void TQFtp::piFinished( const TQString& ) +{ + TQFtpPrivate *d = ::d( this ); + TQFtpCommand *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 != TQFtp::Unconnected ) { + d->close_waitForStateChange = TRUE; + return; + } + } + emit commandFinished( c->id, FALSE ); + + d->pending.removeFirst(); + if ( d->pending.isEmpty() ) { + emit done( FALSE ); + } else { + startNextCommand(); + } +} + +void TQFtp::piError( int errorCode, const TQString &text ) +{ + TQFtpPrivate *d = ::d( this ); + TQFtpCommand *c = d->pending.getFirst(); + + // non-fatal errors + if ( c->command==Get && d->pi.currentCommand().startsWith("SIZE ") ) { + d->pi.dtp.setBytesTotal( -1 ); + return; + } else if ( c->command==Put && d->pi.currentCommand().startsWith("ALLO ") ) { + return; + } + + d->error = (Error)errorCode; + switch ( currentCommand() ) { + case ConnectToHost: + d->errorString = tr( "Connecting to host failed:\n%1" ).arg( text ); + break; + case Login: + d->errorString = tr( "Login failed:\n%1" ).arg( text ); + break; + case List: + d->errorString = tr( "Listing directory failed:\n%1" ).arg( text ); + break; + case Cd: + d->errorString = tr( "Changing directory failed:\n%1" ).arg( text ); + break; + case Get: + d->errorString = tr( "Downloading file failed:\n%1" ).arg( text ); + break; + case Put: + d->errorString = tr( "Uploading file failed:\n%1" ).arg( text ); + break; + case Remove: + d->errorString = tr( "Removing file failed:\n%1" ).arg( text ); + break; + case Mkdir: + d->errorString = tr( "Creating directory failed:\n%1" ).arg( text ); + break; + case Rmdir: + d->errorString = 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 TQFtp::piConnectState( int state ) +{ + TQFtpPrivate *d = ::d( this ); + d->state = (State)state; + emit stateChanged( d->state ); + if ( d->close_waitForStateChange ) { + d->close_waitForStateChange = FALSE; + piFinished( tr( "Connection closed" ) ); + } +} + +void TQFtp::piFtpReply( int code, const TQString &text ) +{ + if ( currentCommand() == RawCommand ) { + TQFtpPrivate *d = ::d( this ); + d->pi.rawCommand = TRUE; + emit rawCommandReply( code, text ); + } +} + +/*! + Destructor. +*/ +TQFtp::~TQFtp() +{ + abort(); + close(); + delete_d( this ); +} + +/********************************************************************** + * + * TQFtp implementation of the TQNetworkProtocol interface + * + *********************************************************************/ +/*! \reimp +*/ +void TQFtp::operationListChildren( TQNetworkOperation *op ) +{ + op->setState( StInProgress ); + + cd( ( url()->path().isEmpty() ? TQString( "/" ) : url()->path() ) ); + list(); + emit start( op ); +} + +/*! \reimp +*/ +void TQFtp::operationMkDir( TQNetworkOperation *op ) +{ + op->setState( StInProgress ); + + mkdir( op->arg( 0 ) ); +} + +/*! \reimp +*/ +void TQFtp::operationRemove( TQNetworkOperation *op ) +{ + op->setState( StInProgress ); + + cd( ( url()->path().isEmpty() ? TQString( "/" ) : url()->path() ) ); + remove( TQUrl( op->arg( 0 ) ).path() ); +} + +/*! \reimp +*/ +void TQFtp::operationRename( TQNetworkOperation *op ) +{ + op->setState( StInProgress ); + + cd( ( url()->path().isEmpty() ? TQString( "/" ) : url()->path() ) ); + rename( op->arg( 0 ), op->arg( 1 )); +} + +/*! \reimp +*/ +void TQFtp::operationGet( TQNetworkOperation *op ) +{ + op->setState( StInProgress ); + + TQUrl u( op->arg( 0 ) ); + get( u.path() ); +} + +/*! \reimp +*/ +void TQFtp::operationPut( TQNetworkOperation *op ) +{ + op->setState( StInProgress ); + + TQUrl u( op->arg( 0 ) ); + put( op->rawArg(1), u.path() ); +} + +/*! \reimp +*/ +bool TQFtp::checkConnection( TQNetworkOperation *op ) +{ + TQFtpPrivate *d = ::d( this ); + if ( state() == Unconnected && !d->npWaitForLoginDone ) { + connect( this, SIGNAL(listInfo(const TQUrlInfo&)), + this, SLOT(npListInfo(const TQUrlInfo&)) ); + 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: + { + TQUrl 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; + } + TQString user = url()->user().isEmpty() ? TQString( "anonymous" ) : url()->user(); + TQString pass = url()->password().isEmpty() ? TQString( "anonymous@" ) : url()->password(); + login( user, pass ); + } + + if ( state() == LoggedIn ) + return TRUE; + return FALSE; +} + +/*! \reimp +*/ +int TQFtp::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 TQFtp::parseDir( const TQString &buffer, TQUrlInfo &info ) +{ + TQFtpDTP::parseDir( buffer, url()->user(), &info ); +} + +void TQFtp::npListInfo( const TQUrlInfo & i ) +{ + if ( url() ) { + TQRegExp filt( url()->nameFilter(), FALSE, TRUE ); + if ( i.isDir() || filt.search( i.name() ) != -1 ) { + emit newChild( i, operationInProgress() ); + } + } else { + emit newChild( i, operationInProgress() ); + } +} + +void TQFtp::npDone( bool err ) +{ + TQFtpPrivate *d = ::d( this ); + + bool emitFinishedSignal = FALSE; + TQNetworkOperation *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: + { + TQUrlInfo inf( op->arg( 0 ), 0, "", "", 0, TQDateTime(), + TQDateTime(), 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(const TQUrlInfo&)), + this, SLOT(npListInfo(const TQUrlInfo&)) ); + 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 TQFtp::npStateChanged( int state ) +{ + if ( url() ) { + if ( state == Connecting ) + emit connectionStateChanged( ConHostFound, tr( "Host %1 found" ).arg( url()->host() ) ); + else if ( state == Connected ) + emit connectionStateChanged( ConConnected, tr( "Connected to host %1" ).arg( url()->host() ) ); + else if ( state == Unconnected ) + emit connectionStateChanged( ConClosed, tr( "Connection to %1 closed" ).arg( url()->host() ) ); + } else { + if ( state == Connecting ) + emit connectionStateChanged( ConHostFound, tr( "Host found" ) ); + else if ( state == Connected ) + emit connectionStateChanged( ConConnected, tr( "Connected to host" ) ); + else if ( state == Unconnected ) + emit connectionStateChanged( ConClosed, tr( "Connection closed" ) ); + } +} + +void TQFtp::npDataTransferProgress( int bDone, int bTotal ) +{ + emit TQNetworkProtocol::dataTransferProgress( bDone, bTotal, operationInProgress() ); +} + +void TQFtp::npReadyRead() +{ + emit data( readAll(), operationInProgress() ); +} + +// ### unused -- delete in TQt 4.0 +/*! \internal +*/ +void TQFtp::hostFound() +{ +} +/*! \internal +*/ +void TQFtp::connected() +{ +} +/*! \internal +*/ +void TQFtp::closed() +{ +} +/*! \internal +*/ +void TQFtp::dataHostFound() +{ +} +/*! \internal +*/ +void TQFtp::dataConnected() +{ +} +/*! \internal +*/ +void TQFtp::dataClosed() +{ +} +/*! \internal +*/ +void TQFtp::dataReadyRead() +{ +} +/*! \internal +*/ +void TQFtp::dataBytesWritten( int ) +{ +} +/*! \internal +*/ +void TQFtp::error( int ) +{ +} + +#include "qftp.moc" + +#endif // QT_NO_NETWORKPROTOCOL_FTP diff --git a/src/network/qftp.h b/src/network/qftp.h new file mode 100644 index 000000000..ee615db79 --- /dev/null +++ b/src/network/qftp.h @@ -0,0 +1,202 @@ +/**************************************************************************** +** +** Definition of TQFtp class. +** +** Created : 970521 +** +** Copyright (C) 1997-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#ifndef TQFTP_H +#define TQFTP_H + +#ifndef QT_H +#include "qstring.h" // char*->TQString conversion +#include "qurlinfo.h" +#include "qnetworkprotocol.h" +#endif // QT_H + +#if !defined( QT_MODULE_NETWORK ) || defined( QT_LICENSE_PROFESSIONAL ) || defined( QT_INTERNAL_NETWORK ) +#define TQM_EXPORT_FTP +#else +#define TQM_EXPORT_FTP Q_EXPORT +#endif + +#ifndef QT_NO_NETWORKPROTOCOL_FTP + + +class TQSocket; +class TQFtpCommand; + +class TQM_EXPORT_FTP TQFtp : public TQNetworkProtocol +{ + Q_OBJECT + +public: + TQFtp(); // ### TQt 4.0: get rid of this overload + TQFtp( TQObject *parent, const char *name=0 ); + virtual ~TQFtp(); + + int supportedOperations() const; + + // non-TQNetworkProtocol 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 TQString &host, Q_UINT16 port=21 ); + int login( const TQString &user=TQString::null, const TQString &password=TQString::null ); + int close(); + int list( const TQString &dir=TQString::null ); + int cd( const TQString &dir ); + int get( const TQString &file, TQIODevice *dev=0 ); + int put( const TQByteArray &data, const TQString &file ); + int put( TQIODevice *dev, const TQString &file ); + int remove( const TQString &file ); + int mkdir( const TQString &dir ); + int rmdir( const TQString &dir ); + int rename( const TQString &oldname, const TQString &newname ); + + int rawCommand( const TQString &command ); + + Q_ULONG bytesAvailable() const; + Q_LONG readBlock( char *data, Q_ULONG maxlen ); + TQByteArray readAll(); + + int currentId() const; + TQIODevice* currentDevice() const; + Command currentCommand() const; + bool hasPendingCommands() const; + void clearPendingCommands(); + + State state() const; + + Error error() const; + TQString errorString() const; + +public slots: + void abort(); + +signals: + void stateChanged( int ); + void listInfo( const TQUrlInfo& ); + void readyRead(); + void dataTransferProgress( int, int ); + void rawCommandReply( int, const TQString& ); + + void commandStarted( int ); + void commandFinished( int, bool ); + void done( bool ); + +protected: + void parseDir( const TQString &buffer, TQUrlInfo &info ); // ### TQt 4.0: delete this? (not public API) + void operationListChildren( TQNetworkOperation *op ); + void operationMkDir( TQNetworkOperation *op ); + void operationRemove( TQNetworkOperation *op ); + void operationRename( TQNetworkOperation *op ); + void operationGet( TQNetworkOperation *op ); + void operationPut( TQNetworkOperation *op ); + + // ### TQt 4.0: delete these + // unused variables: + TQSocket *commandSocket, *dataSocket; + bool connectionReady, passiveMode; + int getTotalSize, getDoneSize; + bool startGetOnFail; + int putToWrite, putWritten; + bool errorInListChildren; + +private: + void init(); + int addCommand( TQFtpCommand * ); + + bool checkConnection( TQNetworkOperation *op ); + +private slots: + void startNextCommand(); + void piFinished( const TQString& ); + void piError( int, const TQString& ); + void piConnectState( int ); + void piFtpReply( int, const TQString& ); + +private slots: + void npListInfo( const TQUrlInfo & ); + void npDone( bool ); + void npStateChanged( int ); + void npDataTransferProgress( int, int ); + void npReadyRead(); + +protected slots: + // ### TQt 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 + +#endif // TQFTP_H diff --git a/src/network/qhostaddress.cpp b/src/network/qhostaddress.cpp new file mode 100644 index 000000000..2c4e2e6f9 --- /dev/null +++ b/src/network/qhostaddress.cpp @@ -0,0 +1,453 @@ +/**************************************************************************** +** +** Implementation of TQHostAddress class. +** +** Created : 979899 +** +** Copyright (C) 1997-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qhostaddress.h" +#include "qstringlist.h" + +#ifndef QT_NO_NETWORK +class TQHostAddressPrivate +{ +public: + TQHostAddressPrivate( Q_UINT32 a_=0 ) : a(a_), isIp4(TRUE) + { + } + TQHostAddressPrivate( Q_UINT8 *a_ ); + TQHostAddressPrivate(const Q_IPV6ADDR &a_); + ~TQHostAddressPrivate() + { + } + + TQHostAddressPrivate & operator=( const TQHostAddressPrivate &from ) + { + a = from.a; + isIp4 = from.isIp4; + a6 = from.a6; + return *this; + } + +private: + Q_UINT32 a; // ip 4 address + Q_IPV6ADDR a6; // ip 6 address + bool isIp4; + + friend class TQHostAddress; +}; + +TQHostAddressPrivate::TQHostAddressPrivate(Q_UINT8 *a_) : a(0), isIp4(FALSE) +{ + for ( int i=0; i<16; i++ ) { + a6.c[i] = a_[i]; + } +} + +TQHostAddressPrivate::TQHostAddressPrivate(const Q_IPV6ADDR &a_) : a(0), isIp4(FALSE) +{ + a6 = a_; +} + +/*! + \class TQHostAddress qhostaddress.h + \brief The TQHostAddress class provides an IP address. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + This class contains an IP address in a platform and protocol + independent manner. It stores both IPv4 and IPv6 addresses in a + way that you can easily access on any platform. + + TQHostAddress is normally used with the classes TQSocket, + TQServerSocket and TQSocketDevice to set up a server or to connect + to a host. + + Host addresses may be set with setAddress() and retrieved with + ip4Addr() or toString(). + + \sa TQSocket, TQServerSocket, TQSocketDevice +*/ + + +/*! + Creates a host address object with the IP address 0.0.0.0. +*/ +TQHostAddress::TQHostAddress() + : d( new TQHostAddressPrivate ) +{ +} + + +/*! + Creates a host address object for the IPv4 address \a ip4Addr. +*/ +TQHostAddress::TQHostAddress( Q_UINT32 ip4Addr ) + : d( new TQHostAddressPrivate( ip4Addr ) ) +{ +} + + +/*! + Creates a host address object with the specified IPv6 address. + + \a ip6Addr must be a 16 byte array in network byte order + (high-order byte first). +*/ +TQHostAddress::TQHostAddress( Q_UINT8 *ip6Addr ) + : d( new TQHostAddressPrivate( ip6Addr ) ) +{ +} + +/*! + Creates a host address object with the IPv6 address, \a ip6Addr. +*/ +TQHostAddress::TQHostAddress(const Q_IPV6ADDR &ip6Addr) + : d(new TQHostAddressPrivate(ip6Addr)) +{ +} + +// ### DOC: Can only make this public if we specify precisely the +// format of the address string. +/*! + \internal +*/ +TQHostAddress::TQHostAddress(const TQString &address) + : d( new TQHostAddressPrivate ) +{ + setAddress( address ); +} + +/*! + Creates a copy of \a address. +*/ +TQHostAddress::TQHostAddress( const TQHostAddress &address ) + : d( new TQHostAddressPrivate ) +{ + *d = *(address.d); +} + + +/*! + Destroys the host address object. +*/ +TQHostAddress::~TQHostAddress() +{ + delete d; +} + + +/*! + Assigns another host address object \a address to this object and + returns a reference to this object. +*/ +TQHostAddress & TQHostAddress::operator=( const TQHostAddress & address ) +{ + *d = *(address.d); + return *this; +} + + +/*! + Set the IPv4 address specified by \a ip4Addr. +*/ +void TQHostAddress::setAddress( Q_UINT32 ip4Addr ) +{ + delete d; + d = new TQHostAddressPrivate( ip4Addr ); +} + + +/*! + \overload + + Set the IPv6 address specified by \a ip6Addr. + + \a ip6Addr must be a 16 byte array in network byte order + (high-order byte first). +*/ +void TQHostAddress::setAddress( Q_UINT8 *ip6Addr ) +{ + delete d; + d = new TQHostAddressPrivate( ip6Addr ); +} + +#ifndef QT_NO_STRINGLIST +static bool parseIp4(const TQString& address, Q_UINT32 *addr) +{ + TQStringList ipv4 = TQStringList::split(".", address, FALSE); + if (ipv4.count() == 4) { + int i = 0; + bool ok = TRUE; + while(ok && i < 4) { + uint byteValue = ipv4[i].toUInt(&ok); + if (byteValue > 255) + ok = FALSE; + if (ok) + *addr = (*addr << 8) + byteValue; + ++i; + } + if (ok) + return TRUE; + } + return FALSE; +} + +/*! + \overload + + Sets the IPv4 or IPv6 address specified by the string + representation \a address (e.g. "127.0.0.1"). Returns TRUE and + sets the address if the address was successfully parsed; otherwise + returns FALSE and leaves the address unchanged. +*/ +bool TQHostAddress::setAddress(const TQString& address) +{ + TQString a = address.simplifyWhiteSpace(); + + // try ipv4 + Q_UINT32 maybeIp4 = 0; + if (parseIp4(address, &maybeIp4)) { + setAddress(maybeIp4); + return TRUE; + } + + // try ipv6 + TQStringList ipv6 = TQStringList::split(":", a, TRUE); + int count = (int)ipv6.count(); + if (count < 3) + return FALSE; // there must be at least two ":" + if (count > 8) + return FALSE; // maximum of seven ":" exceeded + Q_UINT8 maybeIp6[16]; + int mc = 16; + int fillCount = 9 - count; + for (int i=count-1; i>=0; --i) { + if ( mc <= 0 ) + return FALSE; + + if (ipv6[i].isEmpty()) { + if (i==count-1) { + // special case: ":" is last character + if (!ipv6[i-1].isEmpty()) + return FALSE; + maybeIp6[--mc] = 0; + maybeIp6[--mc] = 0; + } else if (i==0) { + // special case: ":" is first character + if (!ipv6[i+1].isEmpty()) + return FALSE; + maybeIp6[--mc] = 0; + maybeIp6[--mc] = 0; + } else { + for (int j=0; j<fillCount; ++j) { + if ( mc <= 0 ) + return FALSE; + maybeIp6[--mc] = 0; + maybeIp6[--mc] = 0; + } + } + } else { + bool ok = FALSE; + uint byteValue = ipv6[i].toUInt(&ok, 16); + if (ok && byteValue <= 0xffff) { + maybeIp6[--mc] = byteValue & 0xff; + maybeIp6[--mc] = (byteValue >> 8) & 0xff; + } else { + if (i == count-1) { + // parse the ipv4 part of a mixed type + if (!parseIp4(ipv6[i], &maybeIp4)) + return FALSE; + maybeIp6[--mc] = maybeIp4 & 0xff; + maybeIp6[--mc] = (maybeIp4 >> 8) & 0xff; + maybeIp6[--mc] = (maybeIp4 >> 16) & 0xff; + maybeIp6[--mc] = (maybeIp4 >> 24) & 0xff; + --fillCount; + } else { + return FALSE; + } + } + } + } + if (mc == 0) { + setAddress(maybeIp6); + return TRUE; + } + + return FALSE; +} +#endif + +/*! + \obsolete + + Use isIPv4Address() instead. +*/ +bool TQHostAddress::isIp4Addr() const +{ + return isIPv4Address(); +} + +/*! + Returns TRUE if the host address represents an IPv4 address; + otherwise returns FALSE. +*/ +bool TQHostAddress::isIPv4Address() const +{ + return d->isIp4; +} + +/*! + \obsolete + + Use toIPv4Address() instead. +*/ +Q_UINT32 TQHostAddress::ip4Addr() const +{ + return toIPv4Address(); +} + +/*! + Returns the IPv4 address as a number. + + For example, if the address is 127.0.0.1, the returned value is + 2130706433 (i.e. 0x7f000001). + + This value is only valid when isIp4Addr() returns TRUE. + + \sa toString() +*/ +Q_UINT32 TQHostAddress::toIPv4Address() const +{ + return d->a; +} + +/*! + Returns TRUE if the host address represents an IPv6 address; + otherwise returns FALSE. +*/ +bool TQHostAddress::isIPv6Address() const +{ + return !d->isIp4; +} + +/*! + Returns the IPv6 address as a Q_IPV6ADDR structure. The structure + consists of 16 unsigned characters. + + \code + Q_IPV6ADDR addr = hostAddr.ip6Addr(); + // addr.c[] contains 16 unsigned characters + + for (int i = 0; i < 16; ++i) { + // process addr.c[i] + } + \endcode + + This value is only valid when isIPv6Address() returns TRUE. + + \sa toString() +*/ +Q_IPV6ADDR TQHostAddress::toIPv6Address() const +{ + return d->a6; +} + +#ifndef QT_NO_SPRINTF +/*! + Returns the address as a string. + + For example, if the address is the IPv4 address 127.0.0.1, the + returned string is "127.0.0.1". + + \sa ip4Addr() +*/ +TQString TQHostAddress::toString() const +{ + if ( d->isIp4 ) { + Q_UINT32 i = ip4Addr(); + TQString s; + s.sprintf( "%d.%d.%d.%d", (i>>24) & 0xff, (i>>16) & 0xff, + (i >> 8) & 0xff, i & 0xff ); + return s; + } else { + Q_UINT16 ugle[8]; + for ( int i=0; i<8; i++ ) { + ugle[i] = ( (Q_UINT16)( d->a6.c[2*i] ) << 8 ) | + ( (Q_UINT16)( d->a6.c[2*i+1] ) ); + } + TQString s; + s.sprintf( "%X:%X:%X:%X:%X:%X:%X:%X", + ugle[0], ugle[1], ugle[2], ugle[3], + ugle[4], ugle[5], ugle[6], ugle[7] ); + return s; + } +} +#endif + + +/*! + Returns TRUE if this host address is the same as \a other; + otherwise returns FALSE. +*/ +bool TQHostAddress::operator==( const TQHostAddress & other ) const +{ + return d->a == other.d->a; +} + + +/*! + Returns TRUE if this host address is null (INADDR_ANY or in6addr_any). The + default constructor creates a null address, and that address isn't valid + for any particular host or interface. +*/ +bool TQHostAddress::isNull() const +{ + if ( d->isIp4 ) + return d->a == 0; + int i = 0; + while( i < 16 ) { + if ( d->a6.c[i++] != 0 ) + return FALSE; + } + return TRUE; +} + +#endif //QT_NO_NETWORK diff --git a/src/network/qhostaddress.h b/src/network/qhostaddress.h new file mode 100644 index 000000000..99ea62f68 --- /dev/null +++ b/src/network/qhostaddress.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Definition of TQHostAddress class. +** +** Created : 979899 +** +** Copyright (C) 1997-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#ifndef TQHOSTADDRESS_H +#define TQHOSTADDRESS_H + +#ifndef QT_H +#include "qstring.h" +#endif // QT_H + +#if !defined( QT_MODULE_NETWORK ) || defined( QT_LICENSE_PROFESSIONAL ) || defined( QT_INTERNAL_NETWORK ) +#define TQM_EXPORT_NETWORK +#else +#define TQM_EXPORT_NETWORK Q_EXPORT +#endif + +#ifndef QT_NO_NETWORK +class TQHostAddressPrivate; + +typedef struct { + Q_UINT8 c[16]; +} Q_IPV6ADDR; + +class TQM_EXPORT_NETWORK TQHostAddress +{ +public: + TQHostAddress(); + TQHostAddress( Q_UINT32 ip4Addr ); + TQHostAddress( Q_UINT8 *ip6Addr ); + TQHostAddress(const Q_IPV6ADDR &ip6Addr); +#ifndef QT_NO_STRINGLIST + TQHostAddress(const TQString &address); +#endif + TQHostAddress( const TQHostAddress & ); + virtual ~TQHostAddress(); + + TQHostAddress & operator=( const TQHostAddress & ); + + void setAddress( Q_UINT32 ip4Addr ); + void setAddress( Q_UINT8 *ip6Addr ); +#ifndef QT_NO_STRINGLIST + bool setAddress( const TQString& address ); +#endif + bool isIp4Addr() const; // obsolete + Q_UINT32 ip4Addr() const; // obsolete + + bool isIPv4Address() const; + Q_UINT32 toIPv4Address() const; + bool isIPv6Address() const; + Q_IPV6ADDR toIPv6Address() const; + +#ifndef QT_NO_SPRINTF + TQString toString() const; +#endif + + bool operator==( const TQHostAddress & ) const; + bool isNull() const; + +private: + TQHostAddressPrivate* d; +}; + +#endif //QT_NO_NETWORK +#endif diff --git a/src/network/qhttp.cpp b/src/network/qhttp.cpp new file mode 100644 index 000000000..143421c54 --- /dev/null +++ b/src/network/qhttp.cpp @@ -0,0 +1,2384 @@ +/**************************************************************************** +** +** Implementation of TQHttp and related classes. +** +** Created : 970521 +** +** Copyright (C) 1997-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qhttp.h" + +#ifndef QT_NO_NETWORKPROTOCOL_HTTP + +#include "qsocket.h" +#include "qtextstream.h" +#include "qmap.h" +#include "qstring.h" +#include "qstringlist.h" +#include "qcstring.h" +#include "qbuffer.h" +#include "qurloperator.h" +#include "qtimer.h" +#include "private/qinternal_p.h" + +//#define TQHTTP_DEBUG + +class TQHttpPrivate +{ +public: + TQHttpPrivate() : + state( TQHttp::Unconnected ), + error( TQHttp::NoError ), + hostname( TQString::null ), + port( 0 ), + toDevice( 0 ), + postDevice( 0 ), + bytesDone( 0 ), + chunkedSize( -1 ), + idleTimer( 0 ) + { + pending.setAutoDelete( TRUE ); + } + + TQSocket socket; + TQPtrList<TQHttpRequest> pending; + + TQHttp::State state; + TQHttp::Error error; + TQString errorString; + + TQString hostname; + Q_UINT16 port; + + TQByteArray buffer; + TQIODevice* toDevice; + TQIODevice* postDevice; + + uint bytesDone; + uint bytesTotal; + Q_LONG chunkedSize; + + TQHttpRequestHeader header; + + bool readHeader; + TQString headerStr; + TQHttpResponseHeader response; + + int idleTimer; + + TQMembuf rba; +}; + +class TQHttpRequest +{ +public: + TQHttpRequest() + { + id = ++idCounter; + } + virtual ~TQHttpRequest() + { } + + virtual void start( TQHttp * ) = 0; + virtual bool hasRequestHeader(); + virtual TQHttpRequestHeader requestHeader(); + + virtual TQIODevice* sourceDevice() = 0; + virtual TQIODevice* destinationDevice() = 0; + + int id; + +private: + static int idCounter; +}; + +int TQHttpRequest::idCounter = 0; + +bool TQHttpRequest::hasRequestHeader() +{ + return FALSE; +} + +TQHttpRequestHeader TQHttpRequest::requestHeader() +{ + return TQHttpRequestHeader(); +} + +/**************************************************** + * + * TQHttpNormalRequest + * + ****************************************************/ + +class TQHttpNormalRequest : public TQHttpRequest +{ +public: + TQHttpNormalRequest( const TQHttpRequestHeader &h, TQIODevice *d, TQIODevice *t ) : + header(h), to(t) + { + is_ba = FALSE; + data.dev = d; + } + + TQHttpNormalRequest( const TQHttpRequestHeader &h, TQByteArray *d, TQIODevice *t ) : + header(h), to(t) + { + is_ba = TRUE; + data.ba = d; + } + + ~TQHttpNormalRequest() + { + if ( is_ba ) + delete data.ba; + } + + void start( TQHttp * ); + bool hasRequestHeader(); + TQHttpRequestHeader requestHeader(); + + TQIODevice* sourceDevice(); + TQIODevice* destinationDevice(); + +protected: + TQHttpRequestHeader header; + +private: + union { + TQByteArray *ba; + TQIODevice *dev; + } data; + bool is_ba; + TQIODevice *to; +}; + +void TQHttpNormalRequest::start( TQHttp *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 = TQByteArray(); + + 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 TQHttpNormalRequest::hasRequestHeader() +{ + return TRUE; +} + +TQHttpRequestHeader TQHttpNormalRequest::requestHeader() +{ + return header; +} + +TQIODevice* TQHttpNormalRequest::sourceDevice() +{ + if ( is_ba ) + return 0; + return data.dev; +} + +TQIODevice* TQHttpNormalRequest::destinationDevice() +{ + return to; +} + +/**************************************************** + * + * TQHttpPGHRequest + * (like a TQHttpNormalRequest, but for the convenience + * functions put(), get() and head() -- i.e. set the + * host header field correctly before sending the + * request) + * + ****************************************************/ + +class TQHttpPGHRequest : public TQHttpNormalRequest +{ +public: + TQHttpPGHRequest( const TQHttpRequestHeader &h, TQIODevice *d, TQIODevice *t ) : + TQHttpNormalRequest( h, d, t ) + { } + + TQHttpPGHRequest( const TQHttpRequestHeader &h, TQByteArray *d, TQIODevice *t ) : + TQHttpNormalRequest( h, d, t ) + { } + + ~TQHttpPGHRequest() + { } + + void start( TQHttp * ); +}; + +void TQHttpPGHRequest::start( TQHttp *http ) +{ + header.setValue( "Host", http->d->hostname ); + TQHttpNormalRequest::start( http ); +} + +/**************************************************** + * + * TQHttpSetHostRequest + * + ****************************************************/ + +class TQHttpSetHostRequest : public TQHttpRequest +{ +public: + TQHttpSetHostRequest( const TQString &h, Q_UINT16 p ) : + hostname(h), port(p) + { } + + void start( TQHttp * ); + + TQIODevice* sourceDevice() + { return 0; } + TQIODevice* destinationDevice() + { return 0; } + +private: + TQString hostname; + Q_UINT16 port; +}; + +void TQHttpSetHostRequest::start( TQHttp *http ) +{ + http->d->hostname = hostname; + http->d->port = port; + http->finishedWithSuccess(); +} + +/**************************************************** + * + * TQHttpCloseRequest + * + ****************************************************/ + +class TQHttpCloseRequest : public TQHttpRequest +{ +public: + TQHttpCloseRequest() + { } + void start( TQHttp * ); + + TQIODevice* sourceDevice() + { return 0; } + TQIODevice* destinationDevice() + { return 0; } +}; + +void TQHttpCloseRequest::start( TQHttp *http ) +{ + http->close(); +} + +/**************************************************** + * + * TQHttpHeader + * + ****************************************************/ + +/*! + \class TQHttpHeader qhttp.h + \brief The TQHttpHeader class contains header information for HTTP. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + In most cases you should use the more specialized derivatives of + this class, TQHttpResponseHeader and TQHttpRequestHeader, rather + than directly using TQHttpHeader. + + TQHttpHeader 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: + \code + content-type: text/html + \endcode + + 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. + \code + header.setValue( "content-type", "text/html" ); + TQString contentType = header.value( "content-type" ); + \endcode + + 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 TQHttpRequestHeader TQHttpResponseHeader +*/ + +/*! + \fn int TQHttpHeader::majorVersion() const + + Returns the major protocol-version of the HTTP header. +*/ + +/*! + \fn int TQHttpHeader::minorVersion() const + + Returns the minor protocol-version of the HTTP header. +*/ + +/*! + Constructs an empty HTTP header. +*/ +TQHttpHeader::TQHttpHeader() + : valid( TRUE ) +{ +} + +/*! + Constructs a copy of \a header. +*/ +TQHttpHeader::TQHttpHeader( const TQHttpHeader& 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. +*/ +TQHttpHeader::TQHttpHeader( const TQString& str ) + : valid( TRUE ) +{ + parse( str ); +} + +/*! + Destructor. +*/ +TQHttpHeader::~TQHttpHeader() +{ +} + +/*! + Assigns \a h and returns a reference to this http header. +*/ +TQHttpHeader& TQHttpHeader::operator=( const TQHttpHeader& h ) +{ + values = h.values; + valid = h.valid; + return *this; +} + +/*! + Returns TRUE if the HTTP header is valid; otherwise returns FALSE. + + A TQHttpHeader is invalid if it was created by parsing a malformed string. +*/ +bool TQHttpHeader::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 TQHttpHeader becomes \link isValid() invalid\endlink. + + Returns TRUE if \a str was successfully parsed; otherwise returns FALSE. + + \sa toString() +*/ +bool TQHttpHeader::parse( const TQString& str ) +{ + TQStringList lst; + int pos = str.find( '\n' ); + if ( pos > 0 && str.at( pos - 1 ) == '\r' ) + lst = TQStringList::split( "\r\n", str.stripWhiteSpace(), FALSE ); + else + lst = TQStringList::split( "\n", str.stripWhiteSpace(), FALSE ); + + if ( lst.isEmpty() ) + return TRUE; + + TQStringList lines; + TQStringList::Iterator it = lst.begin(); + for( ; it != lst.end(); ++it ) { + if ( !(*it).isEmpty() ) { + if ( (*it)[0].isSpace() ) { + if ( !lines.isEmpty() ) { + lines.last() += " "; + 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 TQHttpHeader::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() +*/ +TQString TQHttpHeader::value( const TQString& key ) const +{ + return values[ key.lower() ]; +} + +/*! + Returns a list of the keys in the HTTP header. + + \sa hasKey() +*/ +TQStringList TQHttpHeader::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 TQHttpHeader::hasKey( const TQString& 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 TQHttpHeader::setValue( const TQString& key, const TQString& value ) +{ + values[ key.lower() ] = value; +} + +/*! + Removes the entry with the key \a key from the HTTP header. + + \sa value() setValue() +*/ +void TQHttpHeader::removeValue( const TQString& 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 TQHttpHeader::parseLine( const TQString& line, int ) +{ + int i = line.find( ":" ); + 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 + TQString. It consists of lines with the format: key, colon, space, + value, "\r\n". +*/ +TQString TQHttpHeader::toString() const +{ + if ( !isValid() ) + return ""; + + TQString ret = ""; + + TQMap<TQString,TQString>::ConstIterator it = values.begin(); + for( ; it != values.end(); ++it ) + ret += it.key() + ": " + it.data() + "\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 TQHttpHeader::hasContentLength() const +{ + return hasKey( "content-length" ); +} + +/*! + Returns the value of the special HTTP header field \c + content-length. + + \sa setContentLength() hasContentLength() +*/ +uint TQHttpHeader::contentLength() const +{ + return values[ "content-length" ].toUInt(); +} + +/*! + Sets the value of the special HTTP header field \c content-length + to \a len. + + \sa contentLength() hasContentLength() +*/ +void TQHttpHeader::setContentLength( int len ) +{ + values[ "content-length" ] = TQString::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 TQHttpHeader::hasContentType() const +{ + return hasKey( "content-type" ); +} + +/*! + Returns the value of the special HTTP header field \c content-type. + + \sa setContentType() hasContentType() +*/ +TQString TQHttpHeader::contentType() const +{ + TQString type = values[ "content-type" ]; + if ( type.isEmpty() ) + return TQString::null; + + int pos = type.find( ";" ); + 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 TQHttpHeader::setContentType( const TQString& type ) +{ + values[ "content-type" ] = type; +} + +/**************************************************** + * + * TQHttpResponseHeader + * + ****************************************************/ + +/*! + \class TQHttpResponseHeader qhttp.h + \brief The TQHttpResponseHeader class contains response header information for HTTP. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + This class is used by the TQHttp 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 TQHttpRequestHeader TQHttp +*/ + +/*! + Constructs an empty HTTP response header. +*/ +TQHttpResponseHeader::TQHttpResponseHeader() +{ + 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. +*/ +TQHttpResponseHeader::TQHttpResponseHeader( int code, const TQString& text, int majorVer, int minorVer ) + : TQHttpHeader(), statCode( code ), reasonPhr( text ), majVer( majorVer ), minVer( minorVer ) +{ +} + +/*! + Constructs a copy of \a header. +*/ +TQHttpResponseHeader::TQHttpResponseHeader( const TQHttpResponseHeader& header ) + : TQHttpHeader( 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. +*/ +TQHttpResponseHeader::TQHttpResponseHeader( const TQString& str ) + : TQHttpHeader() +{ + 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 TQHttpResponseHeader::setStatusLine( int code, const TQString& 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 TQHttpResponseHeader::statusCode() const +{ + return statCode; +} + +/*! + Returns the reason phrase of the HTTP response header. + + \sa statusCode() majorVersion() minorVersion() +*/ +TQString TQHttpResponseHeader::reasonPhrase() const +{ + return reasonPhr; +} + +/*! + Returns the major protocol-version of the HTTP response header. + + \sa minorVersion() statusCode() reasonPhrase() +*/ +int TQHttpResponseHeader::majorVersion() const +{ + return majVer; +} + +/*! + Returns the minor protocol-version of the HTTP response header. + + \sa majorVersion() statusCode() reasonPhrase() +*/ +int TQHttpResponseHeader::minorVersion() const +{ + return minVer; +} + +/*! \reimp +*/ +bool TQHttpResponseHeader::parseLine( const TQString& line, int number ) +{ + if ( number != 0 ) + return TQHttpHeader::parseLine( line, number ); + + TQString l = line.simplifyWhiteSpace(); + if ( l.length() < 10 ) + return FALSE; + + if ( l.left( 5 ) == "HTTP/" && l[5].isDigit() && l[6] == '.' && + l[7].isDigit() && l[8] == ' ' && l[9].isDigit() ) { + majVer = l[5].latin1() - '0'; + minVer = l[7].latin1() - '0'; + + int pos = l.find( ' ', 9 ); + if ( pos != -1 ) { + reasonPhr = l.mid( pos + 1 ); + statCode = l.mid( 9, pos - 9 ).toInt(); + } else { + statCode = l.mid( 9 ).toInt(); + reasonPhr = TQString::null; + } + } else { + return FALSE; + } + + return TRUE; +} + +/*! \reimp +*/ +TQString TQHttpResponseHeader::toString() const +{ + TQString ret( "HTTP/%1.%2 %3 %4\r\n%5\r\n" ); + return ret.arg( majVer ).arg ( minVer ).arg( statCode ).arg( reasonPhr ).arg( TQHttpHeader::toString() ); +} + +/**************************************************** + * + * TQHttpRequestHeader + * + ****************************************************/ + +/*! + \class TQHttpRequestHeader qhttp.h + \brief The TQHttpRequestHeader class contains request header information for +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + HTTP. +\if defined(commercial_edition) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + This class is used in the TQHttp 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 TQHttpHeader subclass so that class's functions, + e.g. \link TQHttpHeader::setValue() setValue()\endlink, \link + TQHttpHeader::value() value()\endlink, etc. are also available. + + \sa TQHttpResponseHeader TQHttp + + \important value() setValue() +*/ + +/*! + Constructs an empty HTTP request header. +*/ +TQHttpRequestHeader::TQHttpRequestHeader() + : TQHttpHeader() +{ + 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. +*/ +TQHttpRequestHeader::TQHttpRequestHeader( const TQString& method, const TQString& path, int majorVer, int minorVer ) + : TQHttpHeader(), m( method ), p( path ), majVer( majorVer ), minVer( minorVer ) +{ +} + +/*! + Constructs a copy of \a header. +*/ +TQHttpRequestHeader::TQHttpRequestHeader( const TQHttpRequestHeader& header ) + : TQHttpHeader( 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. +*/ +TQHttpRequestHeader::TQHttpRequestHeader( const TQString& str ) + : TQHttpHeader() +{ + 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 TQHttpRequestHeader::setRequest( const TQString& method, const TQString& 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() +*/ +TQString TQHttpRequestHeader::method() const +{ + return m; +} + +/*! + Returns the request-URI of the HTTP request header. + + \sa method() majorVersion() minorVersion() setRequest() +*/ +TQString TQHttpRequestHeader::path() const +{ + return p; +} + +/*! + Returns the major protocol-version of the HTTP request header. + + \sa minorVersion() method() path() setRequest() +*/ +int TQHttpRequestHeader::majorVersion() const +{ + return majVer; +} + +/*! + Returns the minor protocol-version of the HTTP request header. + + \sa majorVersion() method() path() setRequest() +*/ +int TQHttpRequestHeader::minorVersion() const +{ + return minVer; +} + +/*! \reimp +*/ +bool TQHttpRequestHeader::parseLine( const TQString& line, int number ) +{ + if ( number != 0 ) + return TQHttpHeader::parseLine( line, number ); + + TQStringList lst = TQStringList::split( " ", line.simplifyWhiteSpace() ); + if ( lst.count() > 0 ) { + m = lst[0]; + if ( lst.count() > 1 ) { + p = lst[1]; + if ( lst.count() > 2 ) { + TQString v = lst[2]; + if ( v.length() >= 8 && v.left( 5 ) == "HTTP/" && + v[5].isDigit() && v[6] == '.' && v[7].isDigit() ) { + majVer = v[5].latin1() - '0'; + minVer = v[7].latin1() - '0'; + return TRUE; + } + } + } + } + + return FALSE; +} + +/*! \reimp +*/ +TQString TQHttpRequestHeader::toString() const +{ + TQString first( "%1 %2"); + TQString last(" HTTP/%3.%4\r\n%5\r\n" ); + return first.arg( m ).arg( p ) + + last.arg( majVer ).arg( minVer ).arg( TQHttpHeader::toString()); +} + + +/**************************************************** + * + * TQHttp + * + ****************************************************/ +/*! + \class TQHttp qhttp.h + \brief The TQHttp class provides an implementation of the HTTP protocol. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + This class provides two different interfaces: one is the + TQNetworkProtocol interface that allows you to use HTTP through the + TQUrlOperator 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 TQHttp with the TQNetworkProtocol interface, you + do not use it directly, but rather through a TQUrlOperator, for + example: + + \code + TQUrlOperator op( "http://www.trolltech.com" ); + op.get( "index.html" ); + \endcode + + This code will only work if the TQHttp class is registered; to + register the class, you must call qInitNetworkProtocols() before + using a TQUrlOperator with HTTP. + + The TQNetworkProtocol interface for HTTP only supports the + operations operationGet() and operationPut(), i.e. + TQUrlOperator::get() and TQUrlOperator::put(), if you use it with a + TQUrlOperator. + + 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://www.trolltech.com/index.html): + + \code + TQHttpRequestHeader header( "GET", "/index.html" ); + header.setValue( "Host", "www.trolltech.com" ); + http->setHost( "www.trolltech.com" ); + http->request( header ); + \endcode + + For the common HTTP requests \c GET, \c POST and \c HEAD, TQHttp + 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: + + \code + http->setHost( "www.trolltech.com" ); // id == 1 + http->get( "/index.html" ); // id == 2 + \endcode + + For this example the following sequence of signals is emitted + (with small variations, depending on network traffic, etc.): + + \code + requestStarted( 1 ) + requestFinished( 1, FALSE ) + + requestStarted( 2 ) + stateChanged( Connecting ) + stateChanged( Sending ) + dataSendProgress( 77, 77 ) + stateChanged( Reading ) + responseHeaderReceived( responseheader ) + dataReadProgress( 5388, 0 ) + readyRead( responseheader ) + dataReadProgress( 18300, 0 ) + readyRead( responseheader ) + stateChanged( Connected ) + requestFinished( 2, FALSE ) + + done( FALSE ) + + stateChanged( Closing ) + stateChanged( Unconnected ) + \endcode + + The dataSendProgress() and dataReadProgress() signals in the above + example are useful if you want to show a \link TQProgressBar + progressbar\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 TQProgressBar 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 + + \code + http->setHost( "www.foo.bar" ); // id == 1 + http->get( "/index.html" ); // id == 2 + http->post( "register.html", data ); // id == 3 + \endcode + + and the get() request fails because the host lookup fails, then + the post() request is never executed and the signals would look + like this: + + \code + requestStarted( 1 ) + requestFinished( 1, FALSE ) + + requestStarted( 2 ) + stateChanged( HostLookup ) + requestFinished( 2, TRUE ) + + done( TRUE ) + + stateChanged( Unconnected ) + \endcode + + 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 + TQHttpResponseHeader::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 \link network.html TQt Network Documentation \endlink TQNetworkProtocol, TQUrlOperator TQFtp +*/ + +/*! + Constructs a TQHttp object. +*/ +TQHttp::TQHttp() +{ + init(); +} + +/*! + Constructs a TQHttp object. The parameters \a parent and \a name + are passed on to the TQObject constructor. +*/ +TQHttp::TQHttp( TQObject* parent, const char* name ) +{ + if ( parent ) + parent->insertChild( this ); + setName( name ); + init(); +} + +/*! + Constructs a TQHttp 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 TQObject + constructor. + + \sa setHost() +*/ +TQHttp::TQHttp( const TQString &hostname, Q_UINT16 port, TQObject* parent, const char* name ) +{ + if ( parent ) + parent->insertChild( this ); + setName( name ); + init(); + + d->hostname = hostname; + d->port = port; +} + +void TQHttp::init() +{ + bytesRead = 0; + d = new TQHttpPrivate; + d->errorString = 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 TQHttp object. If there is an open connection, it is + closed. +*/ +TQHttp::~TQHttp() +{ + abort(); + delete d; +} + +/*! + \enum TQHttp::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 TQHttp::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 TQHttp::stateChanged( int state ) + + This signal is emitted when the state of the TQHttp 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 TQHttp::responseHeaderReceived( const TQHttpResponseHeader& 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 TQHttp::readyRead( const TQHttpResponseHeader& 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 TQHttp::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 TQProgressBar, 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() TQProgressBar::setProgress() +*/ + +/*! + \fn void TQHttp::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 TQProgressBar, 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() TQProgressBar::setProgress() +*/ + +/*! + \fn void TQHttp::requestStarted( int id ) + + This signal is emitted when processing the request identified by + \a id starts. + + \sa requestFinished() done() +*/ + +/*! + \fn void TQHttp::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 TQHttp::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 TQHttp::abort() +{ + TQHttpRequest *r = d->pending.getFirst(); + if ( r == 0 ) + return; + + finishedWithError( 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 TQHttp::bytesAvailable() const +{ +#if defined(TQHTTP_DEBUG) + qDebug( "TQHttp::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 TQHttp::readBlock( char *data, Q_ULONG maxlen ) +{ + if ( data == 0 && maxlen != 0 ) { +#if defined(QT_CHECK_NULL) + qWarning( "TQHttp::readBlock: Null pointer error" ); +#endif + return -1; + } + if ( maxlen >= d->rba.size() ) + maxlen = d->rba.size(); + d->rba.consumeBytes( maxlen, data ); + + d->bytesDone += maxlen; +#if defined(TQHTTP_DEBUG) + qDebug( "TQHttp::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() +*/ +TQByteArray TQHttp::readAll() +{ + Q_ULONG avail = bytesAvailable(); + TQByteArray 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 TQHttp::currentId() const +{ + TQHttpRequest *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. + TQHttpRequestHeader::isValid() returns FALSE. + + \sa currentId() +*/ +TQHttpRequestHeader TQHttp::currentRequest() const +{ + TQHttpRequest *r = d->pending.getFirst(); + if ( r != 0 && r->hasRequestHeader() ) + return r->requestHeader(); + return TQHttpRequestHeader(); +} + +/*! + Returns the TQIODevice 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 TQIODevice in the slot connected to + the requestFinished() signal. + + \sa currentDestinationDevice() post() request() +*/ +TQIODevice* TQHttp::currentSourceDevice() const +{ + TQHttpRequest *r = d->pending.getFirst(); + if ( !r ) + return 0; + return r->sourceDevice(); +} + +/*! + Returns the TQIODevice 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 TQIODevice in the slot connected to + the requestFinished() signal. + + \sa currentDestinationDevice() get() post() request() +*/ +TQIODevice* TQHttp::currentDestinationDevice() const +{ + TQHttpRequest *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 TQHttp::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 this as well, use abort(). + + \sa hasPendingRequests() abort() +*/ +void TQHttp::clearPendingRequests() +{ + TQHttpRequest *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 TQHttp::setHost(const TQString &hostname, Q_UINT16 port ) +{ + return addRequest( new TQHttpSetHostRequest( 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://www.trolltech.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 TQHttp::get( const TQString& path, TQIODevice* to ) +{ + TQHttpRequestHeader header( "GET", path ); + header.setValue( "Connection", "Keep-Alive" ); + return addRequest( new TQHttpPGHRequest( header, (TQIODevice*)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://www.trolltech.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 TQHttp::post( const TQString& path, TQIODevice* data, TQIODevice* to ) +{ + TQHttpRequestHeader header( "POST", path ); + header.setValue( "Connection", "Keep-Alive" ); + return addRequest( new TQHttpPGHRequest( header, data, to ) ); +} + +/*! + \overload + + \a data is used as the content data of the HTTP request. +*/ +int TQHttp::post( const TQString& path, const TQByteArray& data, TQIODevice* to ) +{ + TQHttpRequestHeader header( "POST", path ); + header.setValue( "Connection", "Keep-Alive" ); + return addRequest( new TQHttpPGHRequest( header, new TQByteArray(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://www.trolltech.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 TQHttp::head( const TQString& path ) +{ + TQHttpRequestHeader header( "HEAD", path ); + header.setValue( "Connection", "Keep-Alive" ); + return addRequest( new TQHttpPGHRequest( header, (TQIODevice*)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 TQHttp::request( const TQHttpRequestHeader &header, TQIODevice *data, TQIODevice *to ) +{ + return addRequest( new TQHttpNormalRequest( header, data, to ) ); +} + +/*! + \overload + + \a data is used as the content data of the HTTP request. +*/ +int TQHttp::request( const TQHttpRequestHeader &header, const TQByteArray &data, TQIODevice *to ) +{ + return addRequest( new TQHttpNormalRequest( header, new TQByteArray(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(), TQHttp sets + the connection to be keep-alive. You can also do this using the + header you pass to the request() function. TQHttp only closes the + connection to the HTTP server if the response header retquires 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 TQHttp::closeConnection() +{ + return addRequest( new TQHttpCloseRequest() ); +} + +int TQHttp::addRequest( TQHttpRequest *req ) +{ + d->pending.append( req ); + + if ( d->pending.count() == 1 ) + // don't emit the requestStarted() signal before the id is returned + TQTimer::singleShot( 0, this, SLOT(startNextRequest()) ); + + return req->id; +} + +void TQHttp::startNextRequest() +{ + TQHttpRequest *r = d->pending.getFirst(); + if ( r == 0 ) + return; + + d->error = NoError; + d->errorString = tr( "Unknown error" ); + + if ( bytesAvailable() ) + readAll(); // clear the data + emit requestStarted( r->id ); + r->start( this ); +} + +void TQHttp::sendRequest() +{ + if ( d->hostname.isNull() ) { + finishedWithError( 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() != TQSocket::Connection ) { + setState( TQHttp::Connecting ); + d->socket.connectToHost( d->hostname, d->port ); + } else { + slotConnected(); + } + +} + +void TQHttp::finishedWithSuccess() +{ + TQHttpRequest *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 TQHttp::finishedWithError( const TQString& detail, int errorCode ) +{ + TQHttpRequest *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 TQHttp::slotClosed() +{ + if ( d->state == Closing ) + return; + + if ( d->state == Reading ) { + if ( d->response.hasKey( "content-length" ) ) { + // We got Content-Length, so did we get all bytes? + if ( d->bytesDone+bytesAvailable() != d->response.contentLength() ) { + finishedWithError( tr("Wrong content length"), WrongContentLength ); + } + } + } else if ( d->state == Connecting || d->state == Sending ) { + finishedWithError( tr("Server closed connection unexpectedly"), UnexpectedClose ); + } + + d->postDevice = 0; + setState( Closing ); + d->idleTimer = startTimer( 0 ); +} + +void TQHttp::slotConnected() +{ + if ( d->state != Sending ) { + d->bytesDone = 0; + setState( Sending ); + } + + TQString str = d->header.toString(); + d->bytesTotal = str.length(); + d->socket.writeBlock( str.latin1(), d->bytesTotal ); +#if defined(TQHTTP_DEBUG) + qDebug( "TQHttp: 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 = TQByteArray(); // save memory + } +} + +void TQHttp::slotError( int err ) +{ + d->postDevice = 0; + + if ( d->state == Connecting || d->state == Reading || d->state == Sending ) { + switch ( err ) { + case TQSocket::ErrConnectionRefused: + finishedWithError( tr("Connection refused"), ConnectionRefused ); + break; + case TQSocket::ErrHostNotFound: + finishedWithError( tr("Host %1 not found").arg(d->socket.peerName()), HostNotFound ); + break; + default: + finishedWithError( tr("HTTP request failed"), UnknownError ); + break; + } + } + + close(); +} + +void TQHttp::slotBytesWritten( int written ) +{ + d->bytesDone += written; + emit dataSendProgress( d->bytesDone, d->bytesTotal ); + + if ( !d->postDevice ) + return; + + if ( d->socket.bytesToWrite() == 0 ) { + int max = TQMIN( 4096, d->postDevice->size() - d->postDevice->at() ); + TQByteArray 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 TQHttp::slotReadyRead() +{ + if ( d->state != Reading ) { + setState( Reading ); + d->buffer = TQByteArray(); + d->readHeader = TRUE; + d->headerStr = ""; + d->bytesDone = 0; + d->chunkedSize = -1; + } + + while ( d->readHeader ) { + bool end = FALSE; + TQString tmp; + while ( !end && d->socket.canReadLine() ) { + tmp = d->socket.readLine(); + if ( tmp == "\r\n" || tmp == "\n" ) + end = TRUE; + else + d->headerStr += tmp; + } + + if ( !end ) + return; + +#if defined(TQHTTP_DEBUG) + qDebug( "TQHttp: read response header:\n---{\n%s}---", d->headerStr.latin1() ); +#endif + d->response = TQHttpResponseHeader( d->headerStr ); + d->headerStr = ""; +#if defined(TQHTTP_DEBUG) + qDebug( "TQHttp: read response header:\n---{\n%s}---", d->response.toString().latin1() ); +#endif + // Check header + if ( !d->response.isValid() ) { + finishedWithError( 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( "transfer-encoding" ) && + d->response.value( "transfer-encoding" ).lower().contains( "chunked" ) ) + d->chunkedSize = 0; + + emit responseHeaderReceived( d->response ); + } + } + + if ( !d->readHeader ) { + bool everythingRead = FALSE; + + if ( currentRequest().method() == "HEAD" ) { + everythingRead = TRUE; + } else { + Q_ULONG n = d->socket.bytesAvailable(); + TQByteArray *arr = 0; + if ( d->chunkedSize != -1 ) { + // transfer-encoding is chunked + for ( ;; ) { + // get chunk size + if ( d->chunkedSize == 0 ) { + if ( !d->socket.canReadLine() ) + break; + TQString sizeString = d->socket.readLine(); + int tPos = sizeString.find( ';' ); + if ( tPos != -1 ) + sizeString.truncate( tPos ); + bool ok; + d->chunkedSize = sizeString.toInt( &ok, 16 ); + if ( !ok ) { + finishedWithError( 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() ) { + TQString read = d->socket.readLine(); + if ( read == "\r\n" || read == "\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 = TQMIN( (Q_LONG)n, (d->chunkedSize < 0 ? (Q_LONG)n : d->chunkedSize) ); + if ( !arr ) + arr = new TQByteArray( 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( tr("Invalid HTTP chunked body"), WrongContentLength ); + close(); + return; + } + } + } + } else if ( d->response.hasContentLength() ) { + n = TQMIN( d->response.contentLength() - d->bytesDone, n ); + if ( n > 0 ) { + arr = new TQByteArray( 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 + TQByteArray temp = d->socket.readAll(); + arr = new TQByteArray( temp ); + } + + if ( arr ) { + n = arr->size(); + if ( d->toDevice ) { + d->toDevice->writeBlock( arr->data(), n ); + delete arr; + d->bytesDone += n; +#if defined(TQHTTP_DEBUG) + qDebug( "TQHttp::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(TQHTTP_DEBUG) + qDebug( "TQHttp::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("connection").lower() == "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() +*/ +TQHttp::State TQHttp::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. +*/ +TQHttp::Error TQHttp::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. +*/ +TQString TQHttp::errorString() const +{ + return d->errorString; +} + +/*! \reimp +*/ +void TQHttp::timerEvent( TQTimerEvent *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 { + TQObject::timerEvent( e ); + } +} + +void TQHttp::killIdleTimer() +{ + killTimer( d->idleTimer ); + d->idleTimer = 0; +} + +void TQHttp::setState( int s ) +{ +#if defined(TQHTTP_DEBUG) + qDebug( "TQHttp state changed %d -> %d", d->state, s ); +#endif + d->state = (State)s; + emit stateChanged( s ); +} + +void TQHttp::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() == TQSocket::Idle ) { + // Prepare to emit the requestFinished() signal. + d->idleTimer = startTimer( 0 ); + } + } +} + +/********************************************************************** + * + * TQHttp implementation of the TQNetworkProtocol interface + * + *********************************************************************/ +/*! \reimp +*/ +int TQHttp::supportedOperations() const +{ + return OpGet | OpPut; +} + +/*! \reimp +*/ +void TQHttp::operationGet( TQNetworkOperation *op ) +{ + connect( this, SIGNAL(readyRead(const TQHttpResponseHeader&)), + this, SLOT(clientReply(const TQHttpResponseHeader&)) ); + connect( this, SIGNAL(done(bool)), + this, SLOT(clientDone(bool)) ); + connect( this, SIGNAL(stateChanged(int)), + this, SLOT(clientStateChanged(int)) ); + + bytesRead = 0; + op->setState( StInProgress ); + TQUrl u( operationInProgress()->arg( 0 ) ); + TQHttpRequestHeader header( "GET", u.encodedPathAndQuery(), 1, 0 ); + header.setValue( "Host", u.host() ); + setHost( u.host(), u.port() != -1 ? u.port() : 80 ); + request( header ); +} + +/*! \reimp +*/ +void TQHttp::operationPut( TQNetworkOperation *op ) +{ + connect( this, SIGNAL(readyRead(const TQHttpResponseHeader&)), + this, SLOT(clientReply(const TQHttpResponseHeader&)) ); + connect( this, SIGNAL(done(bool)), + this, SLOT(clientDone(bool)) ); + connect( this, SIGNAL(stateChanged(int)), + this, SLOT(clientStateChanged(int)) ); + + bytesRead = 0; + op->setState( StInProgress ); + TQUrl u( operationInProgress()->arg( 0 ) ); + TQHttpRequestHeader header( "POST", u.encodedPathAndQuery(), 1, 0 ); + header.setValue( "Host", u.host() ); + setHost( u.host(), u.port() != -1 ? u.port() : 80 ); + request( header, op->rawArg(1) ); +} + +void TQHttp::clientReply( const TQHttpResponseHeader &rep ) +{ + TQNetworkOperation *op = operationInProgress(); + if ( op ) { + if ( rep.statusCode() >= 400 && rep.statusCode() < 600 ) { + op->setState( StFailed ); + op->setProtocolDetail( + TQString("%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 ) { + TQByteArray ba = readAll(); + emit data( ba, op ); + bytesRead += ba.size(); + if ( rep.hasContentLength() ) { + emit dataTransferProgress( bytesRead, rep.contentLength(), op ); + } + } + } +} + +void TQHttp::clientDone( bool err ) +{ + disconnect( this, SIGNAL(readyRead(const TQHttpResponseHeader&)), + this, SLOT(clientReply(const TQHttpResponseHeader&)) ); + disconnect( this, SIGNAL(done(bool)), + this, SLOT(clientDone(bool)) ); + disconnect( this, SIGNAL(stateChanged(int)), + this, SLOT(clientStateChanged(int)) ); + + if ( err ) { + TQNetworkOperation *op = operationInProgress(); + if ( op ) { + op->setState( TQNetworkProtocol::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 { + TQNetworkOperation *op = operationInProgress(); + if ( op ) { + if ( op->state() != StFailed ) { + op->setState( TQNetworkProtocol::StDone ); + op->setErrorCode( TQNetworkProtocol::NoError ); + } + emit finished( op ); + } + } + +} + +void TQHttp::clientStateChanged( int state ) +{ + if ( url() ) { + switch ( (State)state ) { + case Connecting: + emit connectionStateChanged( ConHostFound, tr( "Host %1 found" ).arg( url()->host() ) ); + break; + case Sending: + emit connectionStateChanged( ConConnected, tr( "Connected to host %1" ).arg( url()->host() ) ); + break; + case Unconnected: + emit connectionStateChanged( ConClosed, tr( "Connection to %1 closed" ).arg( url()->host() ) ); + break; + default: + break; + } + } else { + switch ( (State)state ) { + case Connecting: + emit connectionStateChanged( ConHostFound, tr( "Host found" ) ); + break; + case Sending: + emit connectionStateChanged( ConConnected, tr( "Connected to host" ) ); + break; + case Unconnected: + emit connectionStateChanged( ConClosed, tr( "Connection closed" ) ); + break; + default: + break; + } + } +} + +#endif diff --git a/src/network/qhttp.h b/src/network/qhttp.h new file mode 100644 index 000000000..ed44c8e08 --- /dev/null +++ b/src/network/qhttp.h @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** Definition of TQHttp and related classes. +** +** Created : 970521 +** +** Copyright (C) 1997-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#ifndef TQHTTP_H +#define TQHTTP_H + +#ifndef QT_H +#include "qobject.h" +#include "qnetworkprotocol.h" +#include "qstringlist.h" +#endif // QT_H + +#if !defined( QT_MODULE_NETWORK ) || defined( QT_LICENSE_PROFESSIONAL ) || defined( QT_INTERNAL_NETWORK ) +#define TQM_EXPORT_HTTP +#define TQM_TEMPLATE_EXTERN_HTTP +#else +#define TQM_EXPORT_HTTP Q_EXPORT +#define TQM_TEMPLATE_EXTERN_HTTP Q_TEMPLATE_EXTERN +#endif + +#ifndef QT_NO_NETWORKPROTOCOL_HTTP + +class TQSocket; +class TQTimerEvent; +class TQTextStream; +class TQIODevice; + +class TQHttpPrivate; +class TQHttpRequest; + +class TQM_EXPORT_HTTP TQHttpHeader +{ +public: + TQHttpHeader(); + TQHttpHeader( const TQHttpHeader& header ); + TQHttpHeader( const TQString& str ); + virtual ~TQHttpHeader(); + + TQHttpHeader& operator=( const TQHttpHeader& h ); + + TQString value( const TQString& key ) const; + void setValue( const TQString& key, const TQString& value ); + void removeValue( const TQString& key ); + + TQStringList keys() const; + bool hasKey( const TQString& key ) const; + + bool hasContentLength() const; + uint contentLength() const; + void setContentLength( int len ); + + bool hasContentType() const; + TQString contentType() const; + void setContentType( const TQString& type ); + + virtual TQString toString() const; + bool isValid() const; + + virtual int majorVersion() const = 0; + virtual int minorVersion() const = 0; + +protected: + virtual bool parseLine( const TQString& line, int number ); + bool parse( const TQString& str ); + void setValid( bool ); + +private: + TQMap<TQString,TQString> values; + bool valid; +}; + +class TQM_EXPORT_HTTP TQHttpResponseHeader : public TQHttpHeader +{ +private: + TQHttpResponseHeader( int code, const TQString& text = TQString::null, int majorVer = 1, int minorVer = 1 ); + TQHttpResponseHeader( const TQString& str ); + + void setStatusLine( int code, const TQString& text = TQString::null, int majorVer = 1, int minorVer = 1 ); + +public: + TQHttpResponseHeader(); + TQHttpResponseHeader( const TQHttpResponseHeader& header ); + + int statusCode() const; + TQString reasonPhrase() const; + + int majorVersion() const; + int minorVersion() const; + + TQString toString() const; + +protected: + bool parseLine( const TQString& line, int number ); + +private: + int statCode; + TQString reasonPhr; + int majVer; + int minVer; + + friend class TQHttp; +}; + +class TQM_EXPORT_HTTP TQHttpRequestHeader : public TQHttpHeader +{ +public: + TQHttpRequestHeader(); + TQHttpRequestHeader( const TQString& method, const TQString& path, int majorVer = 1, int minorVer = 1 ); + TQHttpRequestHeader( const TQHttpRequestHeader& header ); + TQHttpRequestHeader( const TQString& str ); + + void setRequest( const TQString& method, const TQString& path, int majorVer = 1, int minorVer = 1 ); + + TQString method() const; + TQString path() const; + + int majorVersion() const; + int minorVersion() const; + + TQString toString() const; + +protected: + bool parseLine( const TQString& line, int number ); + +private: + TQString m; + TQString p; + int majVer; + int minVer; +}; + +class TQM_EXPORT_HTTP TQHttp : public TQNetworkProtocol +{ + Q_OBJECT + +public: + TQHttp(); + TQHttp( TQObject* parent, const char* name = 0 ); // ### TQt 4.0: make parent=0 and get rid of the TQHttp() constructor + TQHttp( const TQString &hostname, Q_UINT16 port=80, TQObject* parent=0, const char* name = 0 ); + virtual ~TQHttp(); + + 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 TQString &hostname, Q_UINT16 port=80 ); + + int get( const TQString& path, TQIODevice* to=0 ); + int post( const TQString& path, TQIODevice* data, TQIODevice* to=0 ); + int post( const TQString& path, const TQByteArray& data, TQIODevice* to=0 ); + int head( const TQString& path ); + int request( const TQHttpRequestHeader &header, TQIODevice *device=0, TQIODevice *to=0 ); + int request( const TQHttpRequestHeader &header, const TQByteArray &data, TQIODevice *to=0 ); + + int closeConnection(); + + Q_ULONG bytesAvailable() const; + Q_LONG readBlock( char *data, Q_ULONG maxlen ); + TQByteArray readAll(); + + int currentId() const; + TQIODevice* currentSourceDevice() const; + TQIODevice* currentDestinationDevice() const; + TQHttpRequestHeader currentRequest() const; + bool hasPendingRequests() const; + void clearPendingRequests(); + + State state() const; + + Error error() const; + TQString errorString() const; + +public slots: + void abort(); + +signals: + void stateChanged( int ); + void responseHeaderReceived( const TQHttpResponseHeader& resp ); + void readyRead( const TQHttpResponseHeader& resp ); + void dataSendProgress( int, int ); + void dataReadProgress( int, int ); + + void requestStarted( int ); + void requestFinished( int, bool ); + void done( bool ); + +protected: + void operationGet( TQNetworkOperation *op ); + void operationPut( TQNetworkOperation *op ); + + void timerEvent( TQTimerEvent * ); + +private slots: + void clientReply( const TQHttpResponseHeader &rep ); + void clientDone( bool ); + void clientStateChanged( int ); + + void startNextRequest(); + void slotReadyRead(); + void slotConnected(); + void slotError( int ); + void slotClosed(); + void slotBytesWritten( int ); + +private: + TQHttpPrivate *d; + void *unused; // ### TQt 4.0: remove this (in for binary compatibility) + int bytesRead; + + int addRequest( TQHttpRequest * ); + void sendRequest(); + void finishedWithSuccess(); + void finishedWithError( const TQString& detail, int errorCode ); + + void killIdleTimer(); + + void init(); + void setState( int ); + void close(); + + friend class TQHttpNormalRequest; + friend class TQHttpSetHostRequest; + friend class TQHttpCloseRequest; + friend class TQHttpPGHRequest; +}; + +#define Q_DEFINED_QHTTP +#include "qwinexport.h" +#endif +#endif diff --git a/src/network/qnetwork.cpp b/src/network/qnetwork.cpp new file mode 100644 index 000000000..4043b1b6a --- /dev/null +++ b/src/network/qnetwork.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Implementation of qInitNetworkProtocols function. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qnetwork.h" + +#ifndef QT_NO_NETWORK + +#include "qnetworkprotocol.h" + +// protocols +#include "qftp.h" +#include "qhttp.h" + +/*! \file qnetwork.h */ +/*! + \relates TQUrlOperator + + This function registers the network protocols for FTP and HTTP. + You must call this function before you use TQUrlOperator for + these protocols. + + This function is declared in \l qnetwork.h. +*/ +void qInitNetworkProtocols() +{ +#ifndef QT_NO_NETWORKPROTOCOL_FTP + TQNetworkProtocol::registerNetworkProtocol( "ftp", new TQNetworkProtocolFactory< TQFtp > ); +#endif +#ifndef QT_NO_NETWORKPROTOCOL_HTTP + TQNetworkProtocol::registerNetworkProtocol( "http", new TQNetworkProtocolFactory< TQHttp > ); +#endif +} + +#endif // QT_NO_NETWORK diff --git a/src/network/qnetwork.h b/src/network/qnetwork.h new file mode 100644 index 000000000..40213e116 --- /dev/null +++ b/src/network/qnetwork.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Definition of qInitNetworkProtocols function. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#ifndef TQNETWORK_H +#define TQNETWORK_H + +#ifndef QT_H +#include "qglobal.h" +#endif // QT_H + +#if !defined( QT_MODULE_NETWORK ) || defined( QT_LICENSE_PROFESSIONAL ) || defined( QT_INTERNAL_NETWORK ) +#define TQM_EXPORT_NETWORK +#else +#define TQM_EXPORT_NETWORK Q_EXPORT +#endif + +#ifndef QT_NO_NETWORK + +TQM_EXPORT_NETWORK void qInitNetworkProtocols(); + +#endif + +#endif diff --git a/src/network/qserversocket.cpp b/src/network/qserversocket.cpp new file mode 100644 index 000000000..7bb0cbe5e --- /dev/null +++ b/src/network/qserversocket.cpp @@ -0,0 +1,297 @@ +/**************************************************************************** +** +** Implementation of TQServerSocket class. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qserversocket.h" + +#ifndef QT_NO_NETWORK + +#include "qsocketnotifier.h" + +class TQServerSocketPrivate { +public: + TQServerSocketPrivate(): s(0), n(0) {} + ~TQServerSocketPrivate() { delete n; delete s; } + TQSocketDevice *s; + TQSocketNotifier *n; +}; + + +/*! + \class TQServerSocket qserversocket.h + \brief The TQServerSocket class provides a TCP-based server. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + This class is a convenience class for accepting incoming TCP + connections. You can specify the port or have TQServerSocket pick + one, and listen on just one address or on all the machine's + addresses. + + Using the API is very simple: subclass TQServerSocket, 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, + TQServerSocket cannot accept or reject connections conditionally.) + + \sa TQSocket, TQSocketDevice, TQHostAddress, TQSocketNotifier +*/ + + +/*! + Creates a server socket object, that will serve the given \a port + on all the addresses of this host. If \a port is 0, TQServerSocket + 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 TQObject + 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. +*/ + +TQServerSocket::TQServerSocket( Q_UINT16 port, int backlog, + TQObject *parent, const char *name ) + : TQObject( parent, name ) +{ + d = new TQServerSocketPrivate; + init( TQHostAddress(), 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 TQObject + 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. +*/ + +TQServerSocket::TQServerSocket( const TQHostAddress & address, Q_UINT16 port, + int backlog, + TQObject *parent, const char *name ) + : TQObject( parent, name ) +{ + d = new TQServerSocketPrivate; + init( address, port, backlog ); +} + + +/*! + Construct an empty server socket. + + This constructor, in combination with setSocket(), allows us to + use the TQServerSocket 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 TQObject + constructor. + + \sa setSocket() +*/ + +TQServerSocket::TQServerSocket( TQObject *parent, const char *name ) + : TQObject( parent, name ) +{ + d = new TQServerSocketPrivate; +} + + +/*! + Returns TRUE if the construction succeeded; otherwise returns FALSE. +*/ +bool TQServerSocket::ok() const +{ + return !!d->s; +} + +/* + The common bit of the constructors. + */ +void TQServerSocket::init( const TQHostAddress & address, Q_UINT16 port, int backlog ) +{ + d->s = new TQSocketDevice( TQSocketDevice::Stream, address.isIPv4Address() + ? TQSocketDevice::IPv4 : TQSocketDevice::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 TQSocketNotifier( d->s->socket(), TQSocketNotifier::Read, + this, "accepting new connections" ); + connect( d->n, SIGNAL(activated(int)), + this, SLOT(incomingConnection(int)) ); + } else { + qWarning( "TQServerSocket: 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 + TQSocketDevice::accept()) to be severed. + + Existing connections continue to exist; this only affects the + acceptance of new connections. +*/ +TQServerSocket::~TQServerSocket() +{ + delete d; +} + + +/*! + \fn void TQServerSocket::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 TQServerSocket::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, + TQServerSocket will pick a non-zero port itself. ok() must be TRUE + before calling this function. + + \sa address() TQSocketDevice::port() +*/ +Q_UINT16 TQServerSocket::port() const +{ + if ( !d || !d->s ) + return 0; + return d->s->port(); +} + + +/*! + Returns the operating system socket. +*/ +int TQServerSocket::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() TQSocketDevice::address() +*/ +TQHostAddress TQServerSocket::address() const +{ + if ( !d || !d->s ) + return TQHostAddress(); + + 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. +*/ +TQSocketDevice *TQServerSocket::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 TQServerSocket class as a wrapper for + other socket types (e.g. Unix Domain Sockets). +*/ +void TQServerSocket::setSocket( int socket ) +{ + delete d; + d = new TQServerSocketPrivate; + d->s = new TQSocketDevice( socket, TQSocketDevice::Stream ); + d->n = new TQSocketNotifier( d->s->socket(), TQSocketNotifier::Read, + this, "accepting new connections" ); + connect( d->n, SIGNAL(activated(int)), + this, SLOT(incomingConnection(int)) ); +} + +#endif //QT_NO_NETWORK diff --git a/src/network/qserversocket.h b/src/network/qserversocket.h new file mode 100644 index 000000000..c536f61f0 --- /dev/null +++ b/src/network/qserversocket.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Definition of TQServerSocketClass. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#ifndef TQSERVERSOCKET_H +#define TQSERVERSOCKET_H + +#ifndef QT_H +#include "qobject.h" +#include "qhostaddress.h" +#include "qsocketdevice.h" // ### remove or keep for users' convenience? +#endif // QT_H +#ifndef QT_NO_NETWORK + +#if !defined( QT_MODULE_NETWORK ) || defined( QT_LICENSE_PROFESSIONAL ) || defined( QT_INTERNAL_NETWORK ) +#define TQM_EXPORT_NETWORK +#else +#define TQM_EXPORT_NETWORK Q_EXPORT +#endif + +class TQServerSocketPrivate; + + +class TQM_EXPORT_NETWORK TQServerSocket : public TQObject +{ + Q_OBJECT +public: + TQServerSocket( Q_UINT16 port, int backlog = 1, + TQObject *parent=0, const char *name=0 ); + TQServerSocket( const TQHostAddress & address, Q_UINT16 port, int backlog = 1, + TQObject *parent=0, const char *name=0 ); + TQServerSocket( TQObject *parent=0, const char *name=0 ); + virtual ~TQServerSocket(); + + bool ok() const; + + Q_UINT16 port() const ; + + int socket() const ; + virtual void setSocket( int socket ); + + TQHostAddress address() const ; + + virtual void newConnection( int socket ) = 0; + +protected: + TQSocketDevice *socketDevice(); + +private slots: + void incomingConnection( int socket ); + +private: + TQServerSocketPrivate *d; + void init( const TQHostAddress & address, Q_UINT16 port, int backlog ); +}; + +#endif // QT_NO_NETWORK +#endif // TQSERVERSOCKET_H diff --git a/src/network/qsocket.cpp b/src/network/qsocket.cpp new file mode 100644 index 000000000..2e62f7998 --- /dev/null +++ b/src/network/qsocket.cpp @@ -0,0 +1,1546 @@ +/**************************************************************************** +** +** Implementation of TQSocket class. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qsocket.h" +#ifndef QT_NO_NETWORK +#include "qptrlist.h" +#include "qtimer.h" +#include "qsocketdevice.h" +#include "qdns.h" +#include "private/qinternal_p.h" + +#include <string.h> +#ifndef NO_ERRNO_H +#include <errno.h> +#endif + +//#define TQSOCKET_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 TQSocket available + as a separate class as a standard part of the TQt library, something along + the line of: + + class TQByteBuffer : public TQIODevice { ... } + + The same class could/would be used within TQSocket 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 = TQByteArray + buffer.close(); + + TQByteArray b; + b.resize( buffer.size() ); + buffer.open( IO_ReadOnly ); + buffer.readBlock( b.data(), b.size() ); + buffer.close(); + + But would also be useable with TQDataStream (via TQIODevice) with: + + buffer.open( IO_WriteOnly | IO_Append ); + TQDataStream is( &buffer ); + is << 100; + buffer.close(); + + buffer.open( IO_ReadOnly ); + TQDataStream os( &buffer ); + Q_UINT32 x; + os >> x; + buffer.close(); + + The real usefulness is with any situations where data (TQByteArray) arrives + incrementally (as in TQSocket and filter case above). + + I tried using TQBuffer, but TQBuffer does not trim bytes from the front of + the buffer in cases like: + + TQBuffer buf; + buf.open( IO_ReadOnly ); + TQDataStream ds( &buf ); + Q_INT32 x; + ds >> x; + buf.close(); + + In the above case, buf.size() will be identical before and after the + operation with TQDataStream. Based on the implementation of TQBuffer, it + does not appear well suited for this kind of operation. +*/ + +// Private class for TQSocket + +class TQSocketPrivate { +public: + TQSocketPrivate(); + ~TQSocketPrivate(); + void closeSocket(); + void close(); + void connectionClosed(); + void setSocketDevice( TQSocket *q, TQSocketDevice *device ); + + TQSocket::State state; // connection state + TQString host; // host name + Q_UINT16 port; // host port + TQSocketDevice *socket; // connection socket + TQSocketNotifier *rsn, *wsn; // socket notifiers + TQMembuf rba; // read buffer + Q_ULONG readBufferSize; // limit for the read buffer size + TQPtrList<TQByteArray> wba; // list of write bufs + TQHostAddress addr; // connection address + TQValueList<TQHostAddress> addresses; // alternatives looked up + TQIODevice::Offset wsize; // write total buf size + TQIODevice::Offset windex; // write index +#ifndef QT_NO_DNS + TQDns *dns4; + TQDns *dns6; +#endif + static TQPtrList<TQSocket> sn_read_alreadyCalled; // used to avoid unwanted recursion + TQValueList<TQHostAddress> l4; + TQValueList<TQHostAddress> l6; +}; + +TQPtrList<TQSocket> TQSocketPrivate::sn_read_alreadyCalled; + +TQSocketPrivate::TQSocketPrivate() + : state(TQSocket::Idle), host(TQString::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 ); +} + +TQSocketPrivate::~TQSocketPrivate() +{ + close(); + delete socket; +#ifndef QT_NO_DNS + delete dns4; + delete dns6; +#endif +} + +void TQSocketPrivate::closeSocket() +{ + // Order is important here - the socket notifiers must go away + // before the socket does, otherwise libc or the kernel will + // become unhappy. + delete rsn; + rsn = 0; + delete wsn; + wsn = 0; + if ( socket ) + socket->close(); +} + +void TQSocketPrivate::close() +{ + closeSocket(); + wsize = 0; + rba.clear(); wba.clear(); + windex = 0; +} + +void TQSocketPrivate::connectionClosed() +{ + // We keep the open state in case there's unread incoming data + state = TQSocket::Idle; + closeSocket(); + wba.clear(); + windex = wsize = 0; +} + +void TQSocketPrivate::setSocketDevice( TQSocket *q, TQSocketDevice *device ) +{ + delete socket; + delete rsn; + delete wsn; + + if ( device ) { + socket = device; + } else { + socket = new TQSocketDevice( TQSocketDevice::Stream, + ( addr.isIPv4Address() ? + TQSocketDevice::IPv4 : + TQSocketDevice::IPv6 ), 0 ); + socket->setBlocking( FALSE ); + socket->setAddressReusable( TRUE ); + } + + rsn = new TQSocketNotifier( socket->socket(), + TQSocketNotifier::Read, q, "read" ); + wsn = new TQSocketNotifier( socket->socket(), + TQSocketNotifier::Write, q, "write" ); + + TQObject::connect( rsn, SIGNAL(activated(int)), q, SLOT(sn_read()) ); + rsn->setEnabled( FALSE ); + TQObject::connect( wsn, SIGNAL(activated(int)), q, SLOT(sn_write()) ); + wsn->setEnabled( FALSE ); +} + +/*! + \class TQSocket qsocket.h + \brief The TQSocket class provides a buffered TCP connection. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + It provides a totally non-blocking TQIODevice, and modifies and + extends the API of TQIODevice with socket-specific code. + + Note that a TQApplication must have been constructed before this + class can be used. + + The functions you're likely to call most are connectToHost(), + bytesAvailable(), canReadLine() and the ones it inherits from + TQIODevice. + + 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 TQSocket + 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 TQSocketDevice used for this socket. + + TQSocket inherits TQIODevice, and reimplements some functions. In + general, you can treat it as a TQIODevice for writing, and mostly + also for reading. The match isn't perfect, since the TQIODevice + API is designed for devices that are controlled by the same + machine, and an asynchronous peer-to-peer network connection isn't + tquite like that. For example, there is nothing that matches + TQIODevice::size() exactly. The documentation for open(), close(), + flush(), size(), at(), atEnd(), readBlock(), writeBlock(), + getch(), putch(), ungetch() and readLine() describes the + differences in detail. + + \warning TQSocket is not suitable for use in threads. If you need + to uses sockets in threads use the lower-level TQSocketDevice class. + + \warning Because TQt doesn't use the native socketstream + implementation on Mac OS X, TQSocket has an implicit transfer + latency of 100ms. You can achieve lower latency on Mac OS X by + using TQSocketDevice instead. + + \sa TQSocketDevice, TQHostAddress, TQSocketNotifier +*/ + + +/*! + Creates a TQSocket object in \c TQSocket::Idle state. + + The \a parent and \a name arguments are passed on to the TQObject + constructor. + + Note that a TQApplication must have been constructed before sockets + can be used. +*/ + +TQSocket::TQSocket( TQObject *parent, const char *name ) + : TQObject( parent, name ) +{ + d = new TQSocketPrivate; + setSocketDevice( 0 ); + setFlags( IO_Direct ); + resetStatus(); +} + + +/*! + Destroys the socket. Closes the connection if necessary. + + \sa close() +*/ + +TQSocket::~TQSocket() +{ +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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. +*/ + +TQSocketDevice *TQSocket::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 TQSocket; after setting the socket call connectToHost() to + make the connection. + + This function is useful if you need to subclass TQSocketDevice and + want to use the TQSocket API, for example, to implement Unix domain + sockets. +*/ + +void TQSocket::setSocketDevice( TQSocketDevice *device ) +{ + if ( state() != Idle ) + close(); + d->setSocketDevice( this, device ); +} + +/*! + \enum TQSocket::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. +*/ + +/*! + Returns the current state of the socket connection. + + \sa TQSocket::State +*/ + +TQSocket::State TQSocket::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 + TQSocket 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. TQSocket will do a normal DNS lookup if retquired. Note that + \a port is in native byte order, unlike some other libraries. + + \sa state() +*/ + +void TQSocket::connectToHost( const TQString &host, Q_UINT16 port ) +{ +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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 TQDns( host, TQDns::A ); + d->dns6 = new TQDns( host, TQDns::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 TQSocket::tryConnecting() +{ +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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() == TQSocketDevice::NoError ) { + if ( d->wsn ) + d->wsn->setEnabled( TRUE ); + return; // not serious, try again later + } + +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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 TQSocket::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 TQSocket::error( int ) + + This signal is emitted after an error occurred. The parameter is + the \l Error value. +*/ + +/*! + \fn void TQSocket::hostFound() + + This signal is emitted after connectToHost() has been called and + the host lookup has succeeded. + + \sa connected() +*/ + + +/*! + \fn void TQSocket::connected() + + This signal is emitted after connectToHost() has been called and a + connection has been successfully established. + + \sa connectToHost(), connectionClosed() +*/ + + +/*! + \fn void TQSocket::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 TQSocket::delayedCloseFinished() + + This signal is emitted when a delayed close is finished. + + If you call close() and there is buffered output data to be + written, TQSocket goes into the \c TQSocket::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 TQSocket::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 TQSocket::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 TQIODevice file mode \a m. + This function is called automatically when needed and you should + not call it yourself. + + \sa close() +*/ + +bool TQSocket::open( int m ) +{ + if ( isOpen() ) { +#if defined(QT_CHECK_STATE) + qWarning( "TQSocket::open: Already open" ); +#endif + return FALSE; + } + TQIODevice::setMode( m & IO_ReadWrite ); + setState( IO_Open ); + return TRUE; +} + + +/*! + Closes the socket. + + The read buffer is cleared. + + If the output buffer is empty, the state is set to \c + TQSocket::Idle and the connection is terminated immediately. If the + output buffer still contains data to be written, TQSocket goes into + the \c TQSocket::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 \c TQSocket::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 TQSocket::close() +{ + if ( !isOpen() || d->state == Idle ) // already closed + return; + if ( d->state == Closing ) + return; + if ( !d->rsn || !d->wsn ) + return; +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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; + } + setFlags( IO_Sequential ); + resetStatus(); + setState( 0 ); + d->close(); + d->state = Idle; +} + + +/*! + This function consumes \a nbytes bytes of data from the write + buffer. +*/ + +bool TQSocket::consumeWriteBuf( Q_ULONG nbytes ) +{ + if ( nbytes <= 0 || nbytes > d->wsize ) + return FALSE; +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%s): skipWriteBuf %d bytes", name(), (int)nbytes ); +#endif + d->wsize -= nbytes; + for ( ;; ) { + TQByteArray *a = d->wba.first(); + if ( 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 TQIODevice::flush() function. +*/ + +void TQSocket::flush() +{ + if ( !d->socket ) + return; + bool osBufferFull = FALSE; + int consumed = 0; + while ( !osBufferFull && d->state >= Connecting && d->wsize > 0 ) { +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%s): flush: Write data to the socket", name() ); +#endif + TQByteArray *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. + TQByteArray 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->writeBlock( out.data(), i ); + if ( d->wsn ) + d->wsn->setEnabled( FALSE ); // the TQSocketNotifier documentation says so + } else { + // Big block, write it immediately + i = a->size() - d->windex; + nwritten = d->socket->writeBlock( a->data() + d->windex, i ); + if ( d->wsn ) + d->wsn->setEnabled( FALSE ); // the TQSocketNotifier documentation says so + } + if ( nwritten > 0 ) { + if ( consumeWriteBuf( nwritten ) ) + consumed += nwritten; + } + if ( nwritten < i ) + osBufferFull = TRUE; + } + if ( consumed > 0 ) { +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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(TQSOCKET_DEBUG) + qDebug( "TQSocket (%s): flush: Delayed close done. Terminating.", + name() ); +#endif + setFlags( IO_Sequential ); + resetStatus(); + setState( 0 ); + d->close(); + d->state = Idle; + emit delayedCloseFinished(); + return; + } + if ( !d->socket->isOpen() ) { + d->connectionClosed(); + emit connectionClosed(); + return; + } + if ( d->wsn ) + d->wsn->setEnabled( d->wsize > 0 ); // write if there's data +} + + +/*! + Returns the number of incoming bytes that can be read right now + (like bytesAvailable()). +*/ + +TQIODevice::Offset TQSocket::size() const +{ + return (Offset)bytesAvailable(); +} + + +/*! + Returns the current read index. Since TQSocket is a sequential + device, the current read index is always zero. +*/ + +TQIODevice::Offset TQSocket::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 TQSocket::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 && TQSocketPrivate::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 TQSocket::atEnd() const +{ + if ( d->socket == 0 ) + return TRUE; + TQSocket * that = (TQSocket *)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(). + + This function can trigger the readyRead() signal, if more data has + arrived on the socket. + + \sa bytesToWrite() +*/ + +Q_ULONG TQSocket::bytesAvailable() const +{ + if ( d->socket == 0 ) + return 0; + TQSocket * that = (TQSocket *)this; + if ( that->d->socket->bytesAvailable() ) // a little slow, perhaps... + (void)that->sn_read(); + return that->d->rba.size(); +} + + +/*! + 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 TQSocket::waitForMore( int msecs, bool *timeout ) const +{ + if ( d->socket == 0 ) + return 0; + TQSocket * that = (TQSocket *)this; + if ( that->d->socket->waitForMore( msecs, timeout ) > 0 ) + (void)that->sn_read( TRUE ); + return that->d->rba.size(); +} + +/*! \overload +*/ + +Q_ULONG TQSocket::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() +*/ + +Q_ULONG TQSocket::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 TQSocket::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. +*/ + +Q_LONG TQSocket::readBlock( char *data, Q_ULONG maxlen ) +{ + if ( data == 0 && maxlen != 0 ) { +#if defined(QT_CHECK_NULL) + qWarning( "TQSocket::readBlock: Null pointer error" ); +#endif + return -1; + } + if ( !isOpen() ) { +#if defined(QT_CHECK_STATE) + qWarning( "TQSocket::readBlock: Socket is not open" ); +#endif + return -1; + } + if ( maxlen >= d->rba.size() ) + maxlen = d->rba.size(); +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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 && TQSocketPrivate::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. +*/ + +Q_LONG TQSocket::writeBlock( const char *data, Q_ULONG len ) +{ +#if defined(QT_CHECK_NULL) + if ( data == 0 && len != 0 ) { + qWarning( "TQSocket::writeBlock: Null pointer error" ); + } +#endif +#if defined(QT_CHECK_STATE) + if ( !isOpen() ) { + qWarning( "TQSocket::writeBlock: Socket is not open" ); + return -1; + } +#endif +#if defined(QT_CHECK_STATE) + if ( d->state == Closing ) { + qWarning( "TQSocket::writeBlock: Cannot write, socket is closing" ); + } +#endif + if ( len == 0 || d->state == Closing || d->state == Idle ) + return 0; + TQByteArray *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 TQByteArray( 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(TQSOCKET_DEBUG) + qDebug( "TQSocket (%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 TQSocket::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 && TQSocketPrivate::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 TQSocket::putch( int ch ) +{ + char buf[2]; + buf[0] = ch; + return writeBlock(buf, 1) == 1 ? ch : -1; +} + + +/*! + This implementation of the virtual function TQIODevice::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 TQSocket::ungetch( int ch ) +{ +#if defined(QT_CHECK_STATE) + if ( !isOpen() ) { + qWarning( "TQSocket::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: + + \code + while( !socket->canReadLine() ) // WRONG + ; + \endcode + + \sa readLine() +*/ + +bool TQSocket::canReadLine() const +{ + if ( ((TQSocket*)this)->d->rba.scanNewline( 0 ) ) + return TRUE; + return ( bytesAvailable() > 0 && + ((TQSocket*)this)->d->rba.scanNewline( 0 ) ); +} + +/*! + \reimp + \internal + So that it's not hidden by our other readLine(). +*/ +Q_LONG TQSocket::readLine( char *data, Q_ULONG maxlen ) +{ + return TQIODevice::readLine(data,maxlen); +} + +/*! + Returns a line of text including a terminating newline character + (\n). Returns "" if canReadLine() returns FALSE. + + \sa canReadLine() +*/ + +TQString TQSocket::readLine() +{ + TQByteArray a(256); + bool nl = d->rba.scanNewline( &a ); + TQString s; + if ( nl ) { + at( a.size() ); // skips the data read + s = TQString( a ); + } + return s; +} + +/*! + \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 TQSocket::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 TQSocketPrivate::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 && TQSocketPrivate::sn_read_alreadyCalled.findRef(this) != -1 ) + return; + TQSocketPrivate::sn_read_alreadyCalled.append( this ); + + char buf[4096]; + Q_LONG nbytes = d->socket->bytesAvailable(); + Q_LONG nread; + TQByteArray *a = 0; + + if ( state() == Connecting ) { + if ( nbytes > 0 ) { + tryConnection(); + } else { + // nothing to do, nothing to care about + TQSocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } + } + if ( state() == Idle ) { + TQSocketPrivate::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 TQSocket is used without an + // event loop. + nread = d->socket->readBlock( buf, maxToRead ? TQMIN((Q_LONG)sizeof(buf),maxToRead) : sizeof(buf) ); + if ( nread == 0 ) { // really closed + if ( !d->socket->isOpen() ) { +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%s): sn_read: Connection closed", name() ); +#endif + d->connectionClosed(); + emit connectionClosed(); + } + TQSocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } else { + if ( nread < 0 ) { + if ( d->socket->error() == TQSocketDevice::NoError ) { + // all is fine + TQSocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } +#if defined(TQSOCKET_DEBUG) + qWarning( "TQSocket::sn_read (%s): Close error", name() ); +#endif + if ( d->rsn ) + d->rsn->setEnabled( FALSE ); + emit error( ErrSocketRead ); + TQSocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } + a = new TQByteArray( nread ); + memcpy( a->data(), buf, nread ); + } + + } else { // data to be read +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%s): sn_read: %ld incoming bytes", name(), nbytes ); +#endif + if ( nbytes > (int)sizeof(buf) ) { + // big + a = new TQByteArray( nbytes ); + nread = d->socket->readBlock( a->data(), maxToRead ? TQMIN(nbytes,maxToRead) : nbytes ); + } else { + a = 0; + nread = d->socket->readBlock( buf, maxToRead ? TQMIN((Q_LONG)sizeof(buf),maxToRead) : sizeof(buf) ); + if ( nread > 0 ) { + // ##### could setRawData + a = new TQByteArray( nread ); + memcpy( a->data(), buf, nread ); + } + } + if ( nread == 0 ) { +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%s): sn_read: Connection closed", name() ); +#endif + // ### we should rather ask the socket device if it is closed + d->connectionClosed(); + emit connectionClosed(); + TQSocketPrivate::sn_read_alreadyCalled.removeRef( this ); + delete a; + return; + } else if ( nread < 0 ) { + delete a; + + if ( d->socket->error() == TQSocketDevice::NoError ) { + // all is fine + TQSocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } +#if defined(QT_CHECK_RANGE) + qWarning( "TQSocket::sn_read: Read error" ); +#endif + if ( d->rsn ) + d->rsn->setEnabled( FALSE ); + emit error( ErrSocketRead ); + TQSocketPrivate::sn_read_alreadyCalled.removeRef( this ); + return; + } + if ( nread != (int)a->size() ) { // unexpected +#if defined(CHECK_RANGE) && !defined(Q_OS_WIN32) + qWarning( "TQSocket::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 ); + } + + TQSocketPrivate::sn_read_alreadyCalled.removeRef( this ); +} + + +/*! + \internal + Internal slot for handling socket write notifications. +*/ + +void TQSocket::sn_write() +{ + if ( d->state == Connecting ) // connection established? + tryConnection(); + flush(); +} + +void TQSocket::emitErrorConnectionRefused() +{ + emit error( ErrConnectionRefused ); +} + +void TQSocket::tryConnection() +{ + if ( d->socket->connect( d->addr, d->port ) ) { + d->state = Connected; +#if defined(TQSOCKET_DEBUG) + qDebug( "TQSocket (%s): sn_write: Got connection to %s", + name(), peerName().ascii() ); +#endif + if ( d->rsn ) + d->rsn->setEnabled( TRUE ); + emit connected(); + } else { + d->state = Idle; + TQTimer::singleShot( 0, this, SLOT(emitErrorConnectionRefused()) ); + return; + } +} + + +/*! + Returns the socket number, or -1 if there is no socket at the moment. +*/ + +int TQSocket::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 TQSocket class as a wrapper for other + socket types (e.g. Unix Domain Sockets). +*/ + +void TQSocket::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 TQSocket::setSocketIntern( int socket ) +{ + if ( state() != Idle ) { + clearPendingData(); + close(); + } + Q_ULONG oldBufferSize = d ? d->readBufferSize : 0; + delete d; + + d = new TQSocketPrivate; + if (oldBufferSize) + d->readBufferSize = oldBufferSize; + if ( socket >= 0 ) { + TQSocketDevice *sd = new TQSocketDevice( socket, TQSocketDevice::Stream ); + sd->setBlocking( FALSE ); + sd->setAddressReusable( TRUE ); + d->setSocketDevice( this, sd ); + } + d->state = Idle; + + // Initialize the IO device flags + setFlags( IO_Direct ); + resetStatus(); + open( IO_ReadWrite ); + + // hm... this is not very nice. + d->host = TQString::null; + 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 TQSocket::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 TQt always uses native byte order, i.e. 67 is 67 in TQt; + there is no need to call htons(). +*/ + +Q_UINT16 TQSocket::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.) +*/ + +TQHostAddress TQSocket::address() const +{ + if ( d->socket == 0 ) { + TQHostAddress tmp; + return tmp; + } + return d->socket->address(); +} + + +/*! + Returns the address of the connected peer if the socket is in + Connected state; otherwise an empty TQHostAddress is returned. +*/ + +TQHostAddress TQSocket::peerAddress() const +{ + if ( d->socket == 0 ) { + TQHostAddress 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. +*/ + +TQString TQSocket::peerName() const +{ + return d->host; +} + +/*! + Sets the size of the TQSocket's internal read buffer to \a bufSize. + + Usually TQSocket 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 TQSocket 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 TQSocket::setReadBufferSize( Q_ULONG bufSize ) +{ + d->readBufferSize = bufSize; +} + +/*! + Returns the size of the read buffer. + + \sa setReadBufferSize() +*/ + +Q_ULONG TQSocket::readBufferSize() const +{ + return d->readBufferSize; +} + +#endif //QT_NO_NETWORK diff --git a/src/network/qsocket.h b/src/network/qsocket.h new file mode 100644 index 000000000..3472a63e0 --- /dev/null +++ b/src/network/qsocket.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Definition of TQSocket class. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#ifndef TQSOCKET_H +#define TQSOCKET_H + +#ifndef QT_H +#include "qobject.h" +#include "qiodevice.h" +#include "qhostaddress.h" // int->TQHostAddress conversion +#endif // QT_H + +#if !defined( QT_MODULE_NETWORK ) || defined( QT_LICENSE_PROFESSIONAL ) || defined( QT_INTERNAL_NETWORK ) +#define TQM_EXPORT_NETWORK +#else +#define TQM_EXPORT_NETWORK Q_EXPORT +#endif + +#ifndef QT_NO_NETWORK +class TQSocketPrivate; +class TQSocketDevice; + + +class TQM_EXPORT_NETWORK TQSocket : public TQObject, public TQIODevice +{ + Q_OBJECT +public: + enum Error { + ErrConnectionRefused, + ErrHostNotFound, + ErrSocketRead + }; + + TQSocket( TQObject *parent=0, const char *name=0 ); + virtual ~TQSocket(); + + enum State { Idle, HostLookup, Connecting, + Connected, Closing, + Connection=Connected }; + State state() const; + + int socket() const; + virtual void setSocket( int ); + + TQSocketDevice *socketDevice(); + virtual void setSocketDevice( TQSocketDevice * ); + +#ifndef QT_NO_DNS + virtual void connectToHost( const TQString &host, Q_UINT16 port ); +#endif + TQString peerName() const; + + // Implementation of TQIODevice abstract virtual functions + bool open( int mode ); + void close(); + void flush(); + Offset size() const; + Offset at() const; + bool at( Offset ); + bool atEnd() const; + + Q_ULONG bytesAvailable() const; // ### TQIODevice::Offset instead? + Q_ULONG waitForMore( int msecs, bool *timeout ) const; + Q_ULONG waitForMore( int msecs ) const; // ### TQt 4.0: merge the two overloads + Q_ULONG bytesToWrite() const; + void clearPendingData(); + + Q_LONG readBlock( char *data, Q_ULONG maxlen ); + Q_LONG writeBlock( const char *data, Q_ULONG len ); + Q_LONG readLine( char *data, Q_ULONG maxlen ); + + int getch(); + int putch( int ); + int ungetch(int); + + bool canReadLine() const; + virtual TQString readLine(); + + Q_UINT16 port() const; + Q_UINT16 peerPort() const; + TQHostAddress address() const; + TQHostAddress peerAddress() const; + + void setReadBufferSize( Q_ULONG ); + Q_ULONG readBufferSize() const; + +signals: + void hostFound(); + void connected(); + void connectionClosed(); + void delayedCloseFinished(); + void readyRead(); + void bytesWritten( int nbytes ); + void error( int ); + +protected slots: + virtual void sn_read( bool force=FALSE ); + virtual void sn_write(); + +private slots: + void tryConnecting(); + void emitErrorConnectionRefused(); + +private: + TQSocketPrivate *d; + + bool consumeWriteBuf( Q_ULONG nbytes ); + void tryConnection(); + void setSocketIntern( int socket ); + +private: // Disabled copy constructor and operator= +#if defined(Q_DISABLE_COPY) + TQSocket( const TQSocket & ); + TQSocket &operator=( const TQSocket & ); +#endif +}; + +#endif //QT_NO_NETWORK +#endif // TQSOCKET_H diff --git a/src/network/qsocketdevice.cpp b/src/network/qsocketdevice.cpp new file mode 100644 index 000000000..10895a538 --- /dev/null +++ b/src/network/qsocketdevice.cpp @@ -0,0 +1,576 @@ +/**************************************************************************** +** +** Implementation of TQSocketDevice class. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#include "qsocketdevice.h" +#ifndef QT_NO_NETWORK + +#include "qwindowdefs.h" +#include <string.h> + + +//#define TQSOCKETDEVICE_DEBUG + + +class TQSocketDevicePrivate +{ +public: + TQSocketDevicePrivate( TQSocketDevice::Protocol p ) + : protocol(p) + { } + + TQSocketDevice::Protocol protocol; +}; + + +/*! + \class TQSocketDevice qsocketdevice.h + \brief The TQSocketDevice class provides a platform-independent low-level socket API. +\if defined(commercial) + It is part of the <a href="commercialeditions.html">TQt Enterprise Edition</a>. +\endif + + \ingroup io + \module network + + 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 + TQSocket class provides a much easier and high level alternative, but + certain things (like UDP) can't be done with TQSocket and if you need a + platform-independent API for those, TQSocketDevice is the right choice. + + The essential purpose of the class is to provide a TQIODevice that + works on sockets, wrapped in a platform-independent API. + + When calling connect() or bind(), TQSocketDevice detects the + protocol family (IPv4, IPv6) automatically. Passing the protocol + family to TQSocketDevice'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 TQSocket, TQSocketNotifier, TQHostAddress +*/ + + +/*! + \enum TQSocketDevice::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 TQSocketDevice with an already existing socket; it + tries to determine the protocol family, but this can fail if the + protocol family is not known to TQSocketDevice. + + \sa protocol() setSocket() +*/ + +/*! + \enum TQSocketDevice::Error + + This enum type describes the error states of TQSocketDevice. + + \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 TQSocketDevice. + + \value Impossible An attempt was made to do something which makes + no sense. For example: + \code + ::close( sd->socket() ); + sd->writeBlock( someData, 42 ); + \endcode + The libc ::close() closes the socket, but TQSocketDevice is not aware + of this. So when you call writeBlock(), the impossible happens. + + \value NoFiles The operating system will not let TQSocketDevice 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. +*/ + +/*! + \enum TQSocketDevice::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 TQSocketDevice object for the existing socket \a socket. + + The \a type argument must match the actual socket type; use \c + TQSocketDevice::Stream for a reliable, connection-oriented TCP + socket, or \c TQSocketDevice::Datagram for an unreliable, + connectionless UDP socket. +*/ +TQSocketDevice::TQSocketDevice( int socket, Type type ) + : fd( socket ), t( type ), p( 0 ), pp( 0 ), e( NoError ), + d(new TQSocketDevicePrivate(Unknown)) +{ +#if defined(TQSOCKETDEVICE_DEBUG) + qDebug( "TQSocketDevice: Created TQSocketDevice %p (socket %x, type %d)", + this, socket, type ); +#endif + init(); + setSocket( socket, type ); +} + +/*! + Creates a TQSocketDevice object for a stream or datagram socket. + + The \a type argument must be either \c TQSocketDevice::Stream for a + reliable, connection-oriented TCP socket, or \c + TQSocketDevice::Datagram for an unreliable UDP socket. + + The socket is created as an IPv4 socket. + + \sa blocking() protocol() +*/ +TQSocketDevice::TQSocketDevice( Type type ) + : fd( -1 ), t( type ), p( 0 ), pp( 0 ), e( NoError ), + d(new TQSocketDevicePrivate(IPv4)) +{ +#if defined(TQSOCKETDEVICE_DEBUG) + qDebug( "TQSocketDevice: Created TQSocketDevice object %p, type %d", + this, type ); +#endif + init(); + setSocket( createNewSocket(), type ); +} + +/*! + Creates a TQSocketDevice object for a stream or datagram socket. + + The \a type argument must be either \c TQSocketDevice::Stream for a + reliable, connection-oriented TCP socket, or \c + TQSocketDevice::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() +*/ +TQSocketDevice::TQSocketDevice( Type type, Protocol protocol, int ) + : fd( -1 ), t( type ), p( 0 ), pp( 0 ), e( NoError ), + d(new TQSocketDevicePrivate(protocol)) +{ +#if defined(TQSOCKETDEVICE_DEBUG) + qDebug( "TQSocketDevice: Created TQSocketDevice object %p, type %d", + this, type ); +#endif + init(); + setSocket( createNewSocket(), type ); +} + +/*! + Destroys the socket device and closes the socket if it is open. +*/ +TQSocketDevice::~TQSocketDevice() +{ + close(); + delete d; + d = 0; +#if defined(TQSOCKETDEVICE_DEBUG) + qDebug( "TQSocketDevice: Destroyed TQSocketDevice %p", this ); +#endif +} + + +/*! + Returns TRUE if this is a valid socket; otherwise returns FALSE. + + \sa socket() +*/ +bool TQSocketDevice::isValid() const +{ + return fd != -1; +} + + +/*! + \fn Type TQSocketDevice::type() const + + Returns the socket type which is either \c TQSocketDevice::Stream + or \c TQSocketDevice::Datagram. + + \sa socket() +*/ +TQSocketDevice::Type TQSocketDevice::type() const +{ + return t; +} + +/*! + Returns the socket's protocol family, which is one of \c Unknown, \c IPv4, + or \c IPv6. + + TQSocketDevice 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() +*/ +TQSocketDevice::Protocol TQSocketDevice::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 TQSocketDevice::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 + TQSocketDevice::Stream for a reliable, connection-oriented TCP + socket, or \c TQSocketDevice::Datagram for an unreliable, + connectionless UDP socket. + + Any existing socket is closed. + + \sa isValid(), close() +*/ +void TQSocketDevice::setSocket( int socket, Type type ) +{ + if ( fd != -1 ) // close any open socket + close(); +#if defined(TQSOCKETDEVICE_DEBUG) + qDebug( "TQSocketDevice::setSocket: socket %x, type %d", socket, type ); +#endif + t = type; + fd = socket; + d->protocol = Unknown; + e = NoError; + setFlags( IO_Sequential ); + resetStatus(); + open( IO_ReadWrite ); + fetchConnectionParameters(); +} + + +/*! + \reimp + + Opens the socket using the specified TQIODevice file \a mode. This + function is called from the TQSocketDevice constructors and from + the setSocket() function. You should not call it yourself. + + \sa close(). +*/ +bool TQSocketDevice::open( int mode ) +{ + if ( isOpen() || !isValid() ) + return FALSE; +#if defined(TQSOCKETDEVICE_DEBUG) + qDebug( "TQSocketDevice::open: mode %x", mode ); +#endif + setMode( mode & IO_ReadWrite ); + setState( IO_Open ); + return TRUE; +} + + +/*! + \reimp + + The current TQSocketDevice implementation does not buffer at all, + so this is a no-op. +*/ +void TQSocketDevice::flush() +{ +} + + +/*! + \reimp + + The size is meaningless for a socket, therefore this function returns 0. +*/ +TQIODevice::Offset TQSocketDevice::size() const +{ + return 0; +} + + +/*! + \reimp + + The read/write index is meaningless for a socket, therefore this + function returns 0. +*/ +TQIODevice::Offset TQSocketDevice::at() const +{ + return 0; +} + + +/*! + \reimp + + The read/write index is meaningless for a socket, therefore this + function does nothing and returns TRUE. +*/ +bool TQSocketDevice::at( Offset ) +{ + return TRUE; +} + + +/*! + \reimp + + Returns TRUE if no data is currently available at the socket; + otherwise returns FALSE. +*/ +bool TQSocketDevice::atEnd() const +{ + return bytesAvailable() <= 0; +} + + +/*! + \reimp + + \warning getch() is implemented as a one-byte readBlock(), so it + may be very slow if you call it more than a few times. + + \sa putch() readBlock() +*/ +int TQSocketDevice::getch() +{ + char buf[2]; + return readBlock(buf,1) == 1 ? buf[0] : -1; +} + + +/*! + \reimp + + \warning putch() is implemented as a one-byte writeBlock(), so it + may be very slow if you call it more than a few times. + + \sa getch() +*/ +int TQSocketDevice::putch( int ch ) +{ + char buf[2]; + buf[0] = ch; + return writeBlock(buf, 1) == 1 ? ch : -1; +} + + +/*! + \reimp + + This implementation of ungetch returns -1 (error). A socket is a + sequential device and does not allow any ungetch operation. +*/ +int TQSocketDevice::ungetch( int ) +{ + return -1; +} + + +/*! + 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 TQSocketDevice::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 TQSocketDevice::setAddressReusable( bool enable ) +{ + setOption( ReuseAddress, enable ); +} + + +/*! + Returns the size of the operating system receive buffer. + + \sa setReceiveBufferSize() +*/ +int TQSocketDevice::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 TQSocketDevice::setReceiveBufferSize( uint size ) +{ + setOption( ReceiveBuffer, size ); +} + + +/*! + Returns the size of the operating system send buffer. + + \sa setSendBufferSize() +*/ +int TQSocketDevice::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 TQSocketDevice::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 TQt always uses native byte order, i.e. 67 is 67 in TQt; + there is no need to call htons(). +*/ +Q_UINT16 TQSocketDevice::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. +*/ +TQHostAddress TQSocketDevice::address() const +{ + return a; +} + + +/*! + Returns the first error seen. +*/ +TQSocketDevice::Error TQSocketDevice::error() const +{ + return e; +} + + +/*! + Allows subclasses to set the error state to \a err. +*/ +void TQSocketDevice::setError( Error err ) +{ + e = err; +} +#endif //QT_NO_NETWORK + diff --git a/src/network/qsocketdevice.h b/src/network/qsocketdevice.h new file mode 100644 index 000000000..06b7fd21f --- /dev/null +++ b/src/network/qsocketdevice.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Definition of TQSocketDevice class. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#ifndef TQSOCKETDEVICE_H +#define TQSOCKETDEVICE_H + +#ifndef QT_H +#include "qiodevice.h" +#include "qhostaddress.h" // int->TQHostAddress conversion +#endif // QT_H + +#if !defined( QT_MODULE_NETWORK ) || defined( QT_LICENSE_PROFESSIONAL ) || defined( QT_INTERNAL_NETWORK ) +#define TQM_EXPORT_NETWORK +#else +#define TQM_EXPORT_NETWORK Q_EXPORT +#endif + +#ifndef QT_NO_NETWORK +class TQSocketDevicePrivate; + + +class TQM_EXPORT_NETWORK TQSocketDevice: public TQIODevice +{ +public: + enum Type { Stream, Datagram }; + enum Protocol { IPv4, IPv6, Unknown }; + + TQSocketDevice( Type type = Stream ); + TQSocketDevice( Type type, Protocol protocol, int dummy ); + TQSocketDevice( int socket, Type type ); + virtual ~TQSocketDevice(); + + bool isValid() const; + Type type() const; + Protocol protocol() const; + + int socket() const; + virtual void setSocket( int socket, Type type ); + + bool open( int mode ); + void close(); + void flush(); + + // Implementation of TQIODevice 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 TQHostAddress &, Q_UINT16 ); + + virtual bool bind( const TQHostAddress &, Q_UINT16 ); + virtual bool listen( int backlog ); + virtual int accept(); + + Q_LONG bytesAvailable() const; + Q_LONG waitForMore( int msecs, bool *timeout=0 ) const; + Q_LONG readBlock( char *data, Q_ULONG maxlen ); + Q_LONG writeBlock( const char *data, Q_ULONG len ); + virtual Q_LONG writeBlock( const char *data, Q_ULONG len, + const TQHostAddress & host, Q_UINT16 port ); + + int getch(); + int putch( int ); + int ungetch(int); + + Q_UINT16 port() const; + Q_UINT16 peerPort() const; + TQHostAddress address() const; + TQHostAddress peerAddress() const; + + enum Error { + NoError, + AlreadyBound, + Inaccessible, + NoResources, + InternalError, + Bug = InternalError, // ### remove in 4.0? + Impossible, + NoFiles, + ConnectionRefused, + NetworkFailure, + UnknownError + }; + Error error() const; + +protected: + void setError( Error err ); + +private: + int fd; + Type t; + Q_UINT16 p; + TQHostAddress a; + Q_UINT16 pp; + TQHostAddress pa; + TQSocketDevice::Error e; + TQSocketDevicePrivate * d; + + enum Option { Broadcast, ReceiveBuffer, ReuseAddress, SendBuffer }; + + int option( Option ) const; + virtual void setOption( Option, int ); + + void fetchConnectionParameters(); +#if defined(Q_OS_WIN32) + void fetchPeerConnectionParameters(); +#endif + + static void init(); + int createNewSocket(); + Protocol getProtocol() const; + +private: // Disabled copy constructor and operator= +#if defined(Q_DISABLE_COPY) + TQSocketDevice( const TQSocketDevice & ); + TQSocketDevice &operator=( const TQSocketDevice & ); +#endif +}; + +#endif // QT_NO_NETWORK +#endif // TQSOCKETDEVICE_H diff --git a/src/network/qsocketdevice_unix.cpp b/src/network/qsocketdevice_unix.cpp new file mode 100644 index 000000000..dab9208dd --- /dev/null +++ b/src/network/qsocketdevice_unix.cpp @@ -0,0 +1,1099 @@ +/**************************************************************************** +** +** Implementation of TQSocketDevice class. +** +** Created : 970521 +** +** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved. +** +** This file is part of the network module of the TQt GUI Toolkit. +** +** This file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free +** Software Foundation and appearing in the files LICENSE.GPL2 +** and LICENSE.GPL3 included in the packaging of this file. +** Alternatively you may (at your option) use any later version +** of the GNU General Public License if such license has been +** publicly approved by Trolltech ASA (or its successors, if any) +** and the KDE Free TQt Foundation. +** +** Please review the following information to ensure GNU General +** Public Licensing retquirements will be met: +** http://trolltech.com/products/qt/licenses/licensing/opensource/. +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://trolltech.com/products/qt/licenses/licensing/licensingoverview +** or contact the sales department at [email protected]. +** +** This file may be used under the terms of the Q Public License as +** defined by Trolltech ASA and appearing in the file LICENSE.TQPL +** included in the packaging of this file. Licensees holding valid TQt +** Commercial licenses may use this file in accordance with the TQt +** Commercial License Agreement provided with the Software. +** +** This file is provided "AS IS" with NO WARRANTY OF ANY KIND, +** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted +** herein. +** +**********************************************************************/ + +#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 + +// Solaris redefines bind -> __xnet_bind with _XOPEN_SOURCE_EXTENDED +static inline int qt_socket_bind(int s, struct sockaddr *addr, QT_SOCKLEN_T addrlen) +{ return ::bind(s, addr, addrlen); } +#if defined(bind) +# undef bind +#endif + +// Solaris redefines connect -> __xnet_connect with _XOPEN_SOURCE_EXTENDED +static inline int qt_socket_connect(int s, struct sockaddr *addr, QT_SOCKLEN_T addrlen) +{ return ::connect(s, addr, addrlen); } +#if defined(connect) +# undef connect +#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 "qsocketdevice.h" + +#ifndef QT_NO_NETWORK + +#include "qwindowdefs.h" + +#include <errno.h> +#include <sys/types.h> + + +static inline void qt_socket_getportaddr( struct sockaddr *sa, + Q_UINT16 *port, TQHostAddress *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) ); + TQHostAddress a( tmp ); + *addr = a; + *port = ntohs( sa6->sin6_port ); + return; + } +#endif + struct sockaddr_in *sa4 = (struct sockaddr_in *)sa; + TQHostAddress a( ntohl( sa4->sin_addr.s_addr ) ); + *port = ntohs( sa4->sin_port ); + *addr = TQHostAddress( ntohl( sa4->sin_addr.s_addr ) ); + return; +} + + +//#define TQSOCKETDEVICE_DEBUG + +// internal +void TQSocketDevice::init() +{ +} + + +TQSocketDevice::Protocol TQSocketDevice::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; +} + +/*! + Creates a new socket identifier. Returns -1 if there is a failure + to create the new identifier; error() explains why. + + \sa setSocket() +*/ + +int TQSocketDevice::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 { + // ensure that the socket is closed on exec..() after being dup()'ed by + // fork() in TQProcess. + ::fcntl(s, F_SETFD, FD_CLOEXEC); + return s; + } + return -1; +} + +/*! + \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-specfic errors that we haven't come + across, however.) + + \sa open() +*/ +void TQSocketDevice::close() +{ + if ( fd == -1 || !isOpen() ) // already closed + return; + setFlags( IO_Sequential ); + resetStatus(); + setState( 0 ); + ::close( fd ); +#if defined(TQSOCKETDEVICE_DEBUG) + qDebug( "TQSocketDevice::close: Closed socket %x", fd ); +#endif + fd = -1; + fetchConnectionParameters(); +} + + +/*! + 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() +*/ +bool TQSocketDevice::blocking() const +{ + if ( !isValid() ) + return TRUE; + int s = fcntl(fd, F_GETFL, 0); + return !(s >= 0 && ((s & O_NDELAY) != 0)); +} + + +/*! + 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 TQSocketNotifier on Windows, the socket is + immediately made nonblocking. + + \sa blocking(), isValid() +*/ +void TQSocketDevice::setBlocking( bool enable ) +{ +#if defined(TQSOCKETDEVICE_DEBUG) + qDebug( "TQSocketDevice::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; + } +} + + +/*! + Returns the value of the socket option \a opt. +*/ +int TQSocketDevice::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 ) { + TQSocketDevice *that = (TQSocketDevice*)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; +} + + +/*! + Sets the socket option \a opt to \a v. +*/ +void TQSocketDevice::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; + } + } +} + + +/*! + 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. +*/ +bool TQSocketDevice::connect( const TQHostAddress &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; +} + + +/*! + 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(). +*/ +bool TQSocketDevice::bind( const TQHostAddress &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; +} + + +/*! + 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 tquite 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() +*/ +bool TQSocketDevice::listen( int backlog ) +{ + if ( !isValid() ) + return FALSE; + if ( qt_socket_listen( fd, backlog ) >= 0 ) + return TRUE; + if ( !e ) + e = Impossible; + return FALSE; +} + + +/*! + 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() +*/ +int TQSocketDevice::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-tquickly case. + case EPERM: + // firewalling wouldn't let us accept. we treat it like + // the client-closed-tquickly 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); + // ensure that the socket is closed on exec..() after being dup()'ed by + // fork() in TQProcess. + ::fcntl(s, F_SETFD, FD_CLOEXEC); + return s; +} + + +/*! + 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(). TQSocket has + workarounds to deal with this problem. +*/ +Q_LONG TQSocketDevice::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); +} + + +/*! + 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() +*/ +Q_LONG TQSocketDevice::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(); +} + + +/*! + 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. +*/ +Q_LONG TQSocketDevice::readBlock( char *data, Q_ULONG maxlen ) +{ +#if defined(QT_CHECK_NULL) + if ( data == 0 && maxlen != 0 ) { + qWarning( "TQSocketDevice::readBlock: Null pointer error" ); + } +#endif +#if defined(QT_CHECK_STATE) + if ( !isValid() ) { + qWarning( "TQSocketDevice::readBlock: Invalid socket" ); + return -1; + } + if ( !isOpen() ) { + qWarning( "TQSocketDevice::readBlock: Device is not open" ); + return -1; + } + if ( !isReadable() ) { + qWarning( "TQSocketDevice::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; +} + + +/*! + 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 \c TQSocketDevice::Stream sockets. +*/ +Q_LONG TQSocketDevice::writeBlock( const char *data, Q_ULONG len ) +{ + if ( data == 0 && len != 0 ) { +#if defined(QT_CHECK_NULL) || defined(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::writeBlock: Null pointer error" ); +#endif + return -1; + } + if ( !isValid() ) { +#if defined(QT_CHECK_STATE) || defined(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::writeBlock: Invalid socket" ); +#endif + return -1; + } + if ( !isOpen() ) { +#if defined(QT_CHECK_STATE) || defined(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::writeBlock: Device is not open" ); +#endif + return -1; + } + if ( !isWritable() ) { +#if defined(QT_CHECK_STATE) || defined(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::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; +} + + +/*! + \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 \c TQSocketDevice::Datagram sockets. You must + specify the \a host and \a port of the destination of the data. +*/ +Q_LONG TQSocketDevice::writeBlock( const char * data, Q_ULONG len, + const TQHostAddress & host, Q_UINT16 port ) +{ + if ( t != Datagram ) { +#if defined(QT_CHECK_STATE) || defined(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::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(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::sendBlock: Null pointer error" ); +#endif + return -1; + } + if ( !isValid() ) { +#if defined(QT_CHECK_STATE) || defined(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::sendBlock: Invalid socket" ); +#endif + return -1; + } + if ( !isOpen() ) { +#if defined(QT_CHECK_STATE) || defined(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::sendBlock: Device is not open" ); +#endif + return -1; + } + if ( !isWritable() ) { +#if defined(QT_CHECK_STATE) || defined(TQSOCKETDEVICE_DEBUG) + qWarning( "TQSocketDevice::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; +} + + +/*! + Fetches information about both ends of the connection: whatever is + available. +*/ +void TQSocketDevice::fetchConnectionParameters() +{ + if ( !isValid() ) { + p = 0; + a = TQHostAddress(); + pp = 0; + pa = TQHostAddress(); + 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 ); +} + + +/*! + 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. +*/ +Q_UINT16 TQSocketDevice::peerPort() const +{ + return pp; +} + + +/*! + 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. +*/ +TQHostAddress TQSocketDevice::peerAddress() const +{ + return pa; +} + +#endif //QT_NO_NETWORK diff --git a/src/network/qt_network.pri b/src/network/qt_network.pri new file mode 100644 index 000000000..86fcf24d4 --- /dev/null +++ b/src/network/qt_network.pri @@ -0,0 +1,23 @@ +# Qt network module + +network { + HEADERS += $$NETWORK_H/qdns.h \ + $$NETWORK_H/qftp.h \ + $$NETWORK_H/qhttp.h \ + $$NETWORK_H/qhostaddress.h \ + $$NETWORK_H/qnetwork.h \ + $$NETWORK_H/qserversocket.h \ + $$NETWORK_H/qsocket.h \ + $$NETWORK_H/qsocketdevice.h + NETWORK_SOURCES = $$NETWORK_CPP/qdns.cpp \ + $$NETWORK_CPP/qftp.cpp \ + $$NETWORK_CPP/qhttp.cpp \ + $$NETWORK_CPP/qhostaddress.cpp \ + $$NETWORK_CPP/qnetwork.cpp \ + $$NETWORK_CPP/qserversocket.cpp \ + $$NETWORK_CPP/qsocket.cpp \ + $$NETWORK_CPP/qsocketdevice.cpp + unix:NETWORK_SOURCES += $$NETWORK_CPP/qsocketdevice_unix.cpp + win32:NETWORK_SOURCES += $$NETWORK_CPP/qsocketdevice_win.cpp + SOURCES += $$NETWORK_SOURCES +} |