diff options
Diffstat (limited to 'kdecore/network')
41 files changed, 15770 insertions, 0 deletions
diff --git a/kdecore/network/Makefile.am b/kdecore/network/Makefile.am new file mode 100644 index 000000000..71ed013d9 --- /dev/null +++ b/kdecore/network/Makefile.am @@ -0,0 +1,59 @@ +## Makefile.am for libqt-addon + +# this is the program that gets installed. it's name is used for all +# of the other Makefile.am variables +noinst_LTLIBRARIES = libkdecorenetwork.la + +# set the include path for X, qt and KDE +INCLUDES = $(all_includes) + +# the library search path. +# convenience lib - no LDFLAGS or LIBADD ! +# Note: +# ksocketdevice.cpp must appear before any inclusion of ksocketdevice.h +libkdecorenetwork_la_SOURCES = kresolver.cpp \ + kresolvermanager.cpp \ + kresolverworkerbase.cpp \ + ksocketaddress.cpp \ + kresolverstandardworkers.cpp \ + kreverseresolver.cpp \ + ksocketdevice.cpp \ + ksocketbase.cpp \ + kclientsocketbase.cpp \ + kstreamsocket.cpp \ + kserversocket.cpp \ + kdatagramsocket.cpp \ + khttpproxysocketdevice.cpp \ + ksockssocketdevice.cpp \ + kbufferedsocket.cpp \ + ksocketbuffer.cpp \ + ksrvresolverworker.cpp + +include_HEADERS = kresolver.h \ + kreverseresolver.h \ + ksocketaddress.h \ + ksocketbase.h \ + ksocketdevice.h \ + kclientsocketbase.h \ + kstreamsocket.h \ + kserversocket.h \ + kdatagramsocket.h \ + kmulticastsocketdevice.h \ + kmulticastsocket.h \ + knetworkinterface.h \ + khttpproxysocketdevice.h \ + ksockssocketdevice.h \ + kbufferedsocket.h \ + kiobuffer.h +noinst_HEADERS = kresolver_p.h \ + kresolverworkerbase.h \ + kresolverstandardworkers_p.h \ + ksocketbuffer_p.h \ + ksrvresolverworker_p.h \ + syssocket.h + +configdir = $(kde_confdir) +config_DATA = ipv6blacklist + +# let automoc handle all of the meta source files (moc) +METASOURCES = AUTO diff --git a/kdecore/network/ipv6blacklist b/kdecore/network/ipv6blacklist new file mode 100644 index 000000000..f4d4d1252 --- /dev/null +++ b/kdecore/network/ipv6blacklist @@ -0,0 +1,3 @@ +.doubleclick.net +.linebourse.fr +.banquepopulaire.fr diff --git a/kdecore/network/kbufferedsocket.cpp b/kdecore/network/kbufferedsocket.cpp new file mode 100644 index 000000000..7207a9c4f --- /dev/null +++ b/kdecore/network/kbufferedsocket.cpp @@ -0,0 +1,414 @@ +/* -*- C++ -*- + * Copyright (C) 2003-2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <qmutex.h> +#include <qtimer.h> + +#include "ksocketdevice.h" +#include "ksocketaddress.h" +#include "ksocketbuffer_p.h" +#include "kbufferedsocket.h" + +using namespace KNetwork; +using namespace KNetwork::Internal; + +class KNetwork::KBufferedSocketPrivate +{ +public: + mutable KSocketBuffer *input, *output; + + KBufferedSocketPrivate() + { + input = 0L; + output = 0L; + } +}; + +KBufferedSocket::KBufferedSocket(const QString& host, const QString& service, + QObject *parent, const char *name) + : KStreamSocket(host, service, parent, name), + d(new KBufferedSocketPrivate) +{ + setInputBuffering(true); + setOutputBuffering(true); +} + +KBufferedSocket::~KBufferedSocket() +{ + closeNow(); + delete d->input; + delete d->output; + delete d; +} + +void KBufferedSocket::setSocketDevice(KSocketDevice* device) +{ + KStreamSocket::setSocketDevice(device); + device->setBlocking(false); +} + +bool KBufferedSocket::setSocketOptions(int opts) +{ + if (opts == Blocking) + return false; + + opts &= ~Blocking; + return KStreamSocket::setSocketOptions(opts); +} + +void KBufferedSocket::close() +{ + if (!d->output || d->output->isEmpty()) + closeNow(); + else + { + setState(Closing); + QSocketNotifier *n = socketDevice()->readNotifier(); + if (n) + n->setEnabled(false); + emit stateChanged(Closing); + } +} + +Q_LONG KBufferedSocket::bytesAvailable() const +{ + if (!d->input) + return KStreamSocket::bytesAvailable(); + + return d->input->length(); +} + +Q_LONG KBufferedSocket::waitForMore(int msecs, bool *timeout) +{ + Q_LONG retval = KStreamSocket::waitForMore(msecs, timeout); + if (d->input) + { + resetError(); + slotReadActivity(); + return bytesAvailable(); + } + return retval; +} + +Q_LONG KBufferedSocket::readBlock(char *data, Q_ULONG maxlen) +{ + if (d->input) + { + if (d->input->isEmpty()) + { + setError(IO_ReadError, WouldBlock); + emit gotError(WouldBlock); + return -1; + } + resetError(); + return d->input->consumeBuffer(data, maxlen); + } + return KStreamSocket::readBlock(data, maxlen); +} + +Q_LONG KBufferedSocket::readBlock(char *data, Q_ULONG maxlen, KSocketAddress& from) +{ + from = peerAddress(); + return readBlock(data, maxlen); +} + +Q_LONG KBufferedSocket::peekBlock(char *data, Q_ULONG maxlen) +{ + if (d->input) + { + if (d->input->isEmpty()) + { + setError(IO_ReadError, WouldBlock); + emit gotError(WouldBlock); + return -1; + } + resetError(); + return d->input->consumeBuffer(data, maxlen, false); + } + return KStreamSocket::peekBlock(data, maxlen); +} + +Q_LONG KBufferedSocket::peekBlock(char *data, Q_ULONG maxlen, KSocketAddress& from) +{ + from = peerAddress(); + return peekBlock(data, maxlen); +} + +Q_LONG KBufferedSocket::writeBlock(const char *data, Q_ULONG len) +{ + if (state() != Connected) + { + // cannot write now! + setError(IO_WriteError, NotConnected); + return -1; + } + + if (d->output) + { + if (d->output->isFull()) + { + setError(IO_WriteError, WouldBlock); + emit gotError(WouldBlock); + return -1; + } + resetError(); + + // enable notifier to send data + QSocketNotifier *n = socketDevice()->writeNotifier(); + if (n) + n->setEnabled(true); + + return d->output->feedBuffer(data, len); + } + + return KStreamSocket::writeBlock(data, len); +} + +Q_LONG KBufferedSocket::writeBlock(const char *data, Q_ULONG maxlen, + const KSocketAddress&) +{ + // ignore the third parameter + return writeBlock(data, maxlen); +} + +void KBufferedSocket::enableRead(bool enable) +{ + KStreamSocket::enableRead(enable); + if (!enable && d->input) + { + // reenable it + QSocketNotifier *n = socketDevice()->readNotifier(); + if (n) + n->setEnabled(true); + } + + if (enable && state() != Connected && d->input && !d->input->isEmpty()) + // this means the buffer is still dirty + // allow the signal to be emitted + QTimer::singleShot(0, this, SLOT(slotReadActivity())); +} + +void KBufferedSocket::enableWrite(bool enable) +{ + KStreamSocket::enableWrite(enable); + if (!enable && d->output && !d->output->isEmpty()) + { + // reenable it + QSocketNotifier *n = socketDevice()->writeNotifier(); + if (n) + n->setEnabled(true); + } +} + +void KBufferedSocket::stateChanging(SocketState newState) +{ + if (newState == Connecting || newState == Connected) + { + // we're going to connect + // make sure the buffers are clean + if (d->input) + d->input->clear(); + if (d->output) + d->output->clear(); + + // also, turn on notifiers + enableRead(emitsReadyRead()); + enableWrite(emitsReadyWrite()); + } + KStreamSocket::stateChanging(newState); +} + +void KBufferedSocket::setInputBuffering(bool enable) +{ + QMutexLocker locker(mutex()); + if (!enable) + { + delete d->input; + d->input = 0L; + } + else if (d->input == 0L) + { + d->input = new KSocketBuffer; + } +} + +KIOBufferBase* KBufferedSocket::inputBuffer() +{ + return d->input; +} + +void KBufferedSocket::setOutputBuffering(bool enable) +{ + QMutexLocker locker(mutex()); + if (!enable) + { + delete d->output; + d->output = 0L; + } + else if (d->output == 0L) + { + d->output = new KSocketBuffer; + } +} + +KIOBufferBase* KBufferedSocket::outputBuffer() +{ + return d->output; +} + +Q_ULONG KBufferedSocket::bytesToWrite() const +{ + if (!d->output) + return 0; + + return d->output->length(); +} + +void KBufferedSocket::closeNow() +{ + KStreamSocket::close(); + if (d->output) + d->output->clear(); +} + +bool KBufferedSocket::canReadLine() const +{ + if (!d->input) + return false; + + return d->input->canReadLine(); +} + +QCString KBufferedSocket::readLine() +{ + return d->input->readLine(); +} + +void KBufferedSocket::waitForConnect() +{ + if (state() != Connecting) + return; // nothing to be waited on + + KStreamSocket::setSocketOptions(socketOptions() | Blocking); + connectionEvent(); + KStreamSocket::setSocketOptions(socketOptions() & ~Blocking); +} + +void KBufferedSocket::slotReadActivity() +{ + if (d->input && state() == Connected) + { + mutex()->lock(); + Q_LONG len = d->input->receiveFrom(socketDevice()); + + if (len == -1) + { + if (socketDevice()->error() != WouldBlock) + { + // nope, another error! + copyError(); + mutex()->unlock(); + emit gotError(error()); + closeNow(); // emits closed + return; + } + } + else if (len == 0) + { + // remotely closed + setError(IO_ReadError, RemotelyDisconnected); + mutex()->unlock(); + emit gotError(error()); + closeNow(); // emits closed + return; + } + + // no error + mutex()->unlock(); + } + + if (state() == Connected) + KStreamSocket::slotReadActivity(); // this emits readyRead + else if (emitsReadyRead()) // state() != Connected + { + if (d->input && !d->input->isEmpty()) + { + // buffer isn't empty + // keep emitting signals till it is + QTimer::singleShot(0, this, SLOT(slotReadActivity())); + emit readyRead(); + } + } +} + +void KBufferedSocket::slotWriteActivity() +{ + if (d->output && !d->output->isEmpty() && + (state() == Connected || state() == Closing)) + { + mutex()->lock(); + Q_LONG len = d->output->sendTo(socketDevice()); + + if (len == -1) + { + if (socketDevice()->error() != WouldBlock) + { + // nope, another error! + copyError(); + mutex()->unlock(); + emit gotError(error()); + closeNow(); + return; + } + } + else if (len == 0) + { + // remotely closed + setError(IO_ReadError, RemotelyDisconnected); + mutex()->unlock(); + emit gotError(error()); + closeNow(); + return; + } + + if (d->output->isEmpty()) + // deactivate the notifier until we have something to send + // writeNotifier can't return NULL here + socketDevice()->writeNotifier()->setEnabled(false); + + mutex()->unlock(); + emit bytesWritten(len); + } + + if (state() != Closing) + KStreamSocket::slotWriteActivity(); + else if (d->output && d->output->isEmpty() && state() == Closing) + { + KStreamSocket::close(); // finished sending data + } +} + +#include "kbufferedsocket.moc" diff --git a/kdecore/network/kbufferedsocket.h b/kdecore/network/kbufferedsocket.h new file mode 100644 index 000000000..b0b99f8d1 --- /dev/null +++ b/kdecore/network/kbufferedsocket.h @@ -0,0 +1,253 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago@kde.org> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KBUFFEREDSOCKET_H +#define KBUFFEREDSOCKET_H + +#include <qobject.h> +#include <qcstring.h> +#include <qvaluelist.h> +#include "kstreamsocket.h" +#include <kdelibs_export.h> + +class KIOBufferBase; + +namespace KNetwork { + +class KBufferedSocketPrivate; +/** @class KBufferedSocket kbufferedsocket.h kbufferedsocket.h + * @brief Buffered stream sockets. + * + * This class allows the user to create and operate buffered stream sockets + * such as those used in most Internet connections. This class is + * also the one that resembles the most to the old @ref QSocket + * implementation. + * + * Objects of this type operate only in non-blocking mode. A call to + * setBlocking(true) will result in an error. + * + * @note Buffered sockets only make sense if you're using them from + * the main (event-loop) thread. This is actually a restriction + * imposed by Qt's QSocketNotifier. If you want to use a socket + * in an auxiliary thread, please use KStreamSocket. + * + * @see KNetwork::KStreamSocket, KNetwork::KServerSocket + * @author Thiago Macieira <thiago@kde.org> + */ +class KDECORE_EXPORT KBufferedSocket: public KStreamSocket +{ + Q_OBJECT +public: + /** + * Default constructor. + * + * @param node destination host + * @param service destination service to connect to + * @param parent the parent object for this object + * @param name the internal name for this object + */ + KBufferedSocket(const QString& node = QString::null, const QString& service = QString::null, + QObject* parent = 0L, const char *name = 0L); + + /** + * Destructor. + */ + virtual ~KBufferedSocket(); + + /** + * Be sure to catch new devices. + */ + virtual void setSocketDevice(KSocketDevice* device); + +protected: + /** + * Buffered sockets can only operate in non-blocking mode. + */ + virtual bool setSocketOptions(int opts); + +public: + /** + * Closes the socket for new data, but allow data that had been buffered + * for output with @ref writeBlock to be still be written. + * + * @sa closeNow + */ + virtual void close(); + + /** + * Make use of the buffers. + */ + virtual Q_LONG bytesAvailable() const; + + /** + * Make use of buffers. + */ + virtual Q_LONG waitForMore(int msecs, bool *timeout = 0L); + + /** + * Reads data from the socket. Make use of buffers. + */ + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen); + + /** + * @overload + * Reads data from a socket. + * + * The @p from parameter is always set to @ref peerAddress() + */ + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen, KSocketAddress& from); + + /** + * Peeks data from the socket. + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen); + + /** + * @overload + * Peeks data from the socket. + * + * The @p from parameter is always set to @ref peerAddress() + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen, KSocketAddress &from); + + /** + * Writes data to the socket. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len); + + /** + * @overload + * Writes data to the socket. + * + * The @p to parameter is discarded. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to); + + /** + * Catch changes. + */ + virtual void enableRead(bool enable); + + /** + * Catch changes. + */ + virtual void enableWrite(bool enable); + + /** + * Sets the use of input buffering. + */ + void setInputBuffering(bool enable); + + /** + * Retrieves the input buffer object. + */ + KIOBufferBase* inputBuffer(); + + /** + * Sets the use of output buffering. + */ + void setOutputBuffering(bool enable); + + /** + * Retrieves the output buffer object. + */ + KIOBufferBase* outputBuffer(); + + /** + * Returns the length of the output buffer. + */ + virtual Q_ULONG bytesToWrite() const; + + /** + * Closes the socket and discards any output data that had been buffered + * with @ref writeBlock but that had not yet been written. + * + * @sa close + */ + virtual void closeNow(); + + /** + * Returns true if a line can be read with @ref readLine + */ + bool canReadLine() const; + + /** + * Reads a line of data from the socket buffers. + */ + QCString readLine(); + + // KDE4: make virtual, add timeout to match the Qt4 signature + // and move to another class up the hierarchy + /** + * Blocks until the connection is either established, or completely + * failed. + */ + void waitForConnect(); + +protected: + /** + * Catch connection to clear the buffers + */ + virtual void stateChanging(SocketState newState); + +protected slots: + /** + * Slot called when there's read activity. + */ + virtual void slotReadActivity(); + + /** + * Slot called when there's write activity. + */ + virtual void slotWriteActivity(); + +signals: + /** + * This signal is emitted whenever data is written. + */ + void bytesWritten(int bytes); + +private: + KBufferedSocket(const KBufferedSocket&); + KBufferedSocket& operator=(const KBufferedSocket&); + + KBufferedSocketPrivate *d; + +public: + // KDE4: remove this function + /** + * @deprecated + * Closes the socket. + * + * This function is provided to ease porting from KExtendedSocket, + * which required a call to reset() in order to be able to connect again + * using the same device. This is not necessary in KBufferedSocket any more. + */ + inline void reset() + { closeNow(); } +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/kclientsocketbase.cpp b/kdecore/network/kclientsocketbase.cpp new file mode 100644 index 000000000..b777dc8de --- /dev/null +++ b/kdecore/network/kclientsocketbase.cpp @@ -0,0 +1,477 @@ +/* -*- C++ -*- + * Copyright (C) 2003,2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <qsocketnotifier.h> +#include <qtimer.h> +#include <qmutex.h> + +#include "ksocketaddress.h" +#include "kresolver.h" +#include "ksocketbase.h" +#include "ksocketdevice.h" +#include "kclientsocketbase.h" + +using namespace KNetwork; + +class KNetwork::KClientSocketBasePrivate +{ +public: + int state; + + KResolver localResolver, peerResolver; + KResolverResults localResults, peerResults; + + bool enableRead : 1, enableWrite : 1; +}; + +KClientSocketBase::KClientSocketBase(QObject *parent, const char *name) + : QObject(parent, name), d(new KClientSocketBasePrivate) +{ + d->state = Idle; + d->enableRead = true; + d->enableWrite = false; +} + +KClientSocketBase::~KClientSocketBase() +{ + close(); + delete d; +} + +KClientSocketBase::SocketState KClientSocketBase::state() const +{ + return static_cast<SocketState>(d->state); +} + +void KClientSocketBase::setState(SocketState state) +{ + d->state = state; + stateChanging(state); +} + +bool KClientSocketBase::setSocketOptions(int opts) +{ + QMutexLocker locker(mutex()); + KSocketBase::setSocketOptions(opts); // call parent + + // don't create the device unnecessarily + if (hasDevice()) + { + bool result = socketDevice()->setSocketOptions(opts); // and set the implementation + copyError(); + return result; + } + + return true; +} + +KResolver& KClientSocketBase::peerResolver() const +{ + return d->peerResolver; +} + +const KResolverResults& KClientSocketBase::peerResults() const +{ + return d->peerResults; +} + +KResolver& KClientSocketBase::localResolver() const +{ + return d->localResolver; +} + +const KResolverResults& KClientSocketBase::localResults() const +{ + return d->localResults; +} + +void KClientSocketBase::setResolutionEnabled(bool enable) +{ + if (enable) + { + d->localResolver.setFlags(d->localResolver.flags() & ~KResolver::NoResolve); + d->peerResolver.setFlags(d->peerResolver.flags() & ~KResolver::NoResolve); + } + else + { + d->localResolver.setFlags(d->localResolver.flags() | KResolver::NoResolve); + d->peerResolver.setFlags(d->peerResolver.flags() | KResolver::NoResolve); + } +} + +void KClientSocketBase::setFamily(int families) +{ + d->localResolver.setFamily(families); + d->peerResolver.setFamily(families); +} + +bool KClientSocketBase::lookup() +{ + if (state() == HostLookup && !blocking()) + return true; // already doing lookup + + if (state() > HostLookup) + return true; // results are already available + + if (state() < HostLookup) + { + if (d->localResolver.serviceName().isNull() && + !d->localResolver.nodeName().isNull()) + d->localResolver.setServiceName(QString::fromLatin1("")); + + // don't restart the lookups if they had succeeded and + // the input values weren't changed + QObject::connect(&d->peerResolver, SIGNAL(finished(KResolverResults)), + this, SLOT(lookupFinishedSlot())); + QObject::connect(&d->localResolver, SIGNAL(finished(KResolverResults)), + this, SLOT(lookupFinishedSlot())); + + if (d->localResolver.status() <= 0) + d->localResolver.start(); + if (d->peerResolver.status() <= 0) + d->peerResolver.start(); + + setState(HostLookup); + emit stateChanged(HostLookup); + + if (!d->localResolver.isRunning() && !d->peerResolver.isRunning()) + { + // if nothing is running, then the lookup results are still valid + // pretend we had done lookup + if (blocking()) + lookupFinishedSlot(); + else + QTimer::singleShot(0, this, SLOT(lookupFinishedSlot())); + } + else + { + d->localResults = d->peerResults = KResolverResults(); + } + } + + if (blocking()) + { + // we're in blocking mode operation + // wait for the results + + localResolver().wait(); + peerResolver().wait(); + + // lookupFinishedSlot has been called + } + + return true; +} + +bool KClientSocketBase::bind(const KResolverEntry& address) +{ + if (state() == HostLookup || state() > Connecting) + return false; + + if (socketDevice()->bind(address)) + { + resetError(); + + // don't set the state or emit signals if we are in a higher state + if (state() < Bound) + { + setState(Bound); + emit stateChanged(Bound); + emit bound(address); + } + return true; + } + return false; +} + +bool KClientSocketBase::connect(const KResolverEntry& address) +{ + if (state() == Connected) + return true; // to be compliant with the other classes + if (state() == HostLookup || state() > Connecting) + return false; + + bool ok = socketDevice()->connect(address); + copyError(); + + if (ok) + { + SocketState newstate; + if (error() == InProgress) + newstate = Connecting; + else + newstate = Connected; + + if (state() < newstate) + { + setState(newstate); + emit stateChanged(newstate); + if (error() == NoError) + { + setFlags(IO_Sequential | IO_Raw | IO_ReadWrite | IO_Open | IO_Async); + emit connected(address); + } + } + + return true; + } + return false; +} + +bool KClientSocketBase::disconnect() +{ + if (state() != Connected) + return false; + + bool ok = socketDevice()->disconnect(); + copyError(); + + if (ok) + { + setState(Unconnected); + emit stateChanged(Unconnected); + return true; + } + return false; +} + +void KClientSocketBase::close() +{ + if (state() == Idle) + return; // nothing to do + + if (state() == HostLookup) + { + d->peerResolver.cancel(false); + d->localResolver.cancel(false); + } + + d->localResults = d->peerResults = KResolverResults(); + + socketDevice()->close(); + setState(Idle); + emit stateChanged(Idle); + emit closed(); +} + +// This function is unlike all the others because it is const +Q_LONG KClientSocketBase::bytesAvailable() const +{ + return socketDevice()->bytesAvailable(); +} + +// All the functions below look really alike +// Should I use a macro to define them? + +Q_LONG KClientSocketBase::waitForMore(int msecs, bool *timeout) +{ + resetError(); + Q_LONG retval = socketDevice()->waitForMore(msecs, timeout); + if (retval == -1) + { + copyError(); + emit gotError(error()); + } + return retval; +} + +Q_LONG KClientSocketBase::readBlock(char *data, Q_ULONG maxlen) +{ + resetError(); + Q_LONG retval = socketDevice()->readBlock(data, maxlen); + if (retval == -1) + { + copyError(); + emit gotError(error()); + } + return retval; +} + +Q_LONG KClientSocketBase::readBlock(char *data, Q_ULONG maxlen, KSocketAddress& from) +{ + resetError(); + Q_LONG retval = socketDevice()->readBlock(data, maxlen, from); + if (retval == -1) + { + copyError(); + emit gotError(error()); + } + return retval; +} + +Q_LONG KClientSocketBase::peekBlock(char *data, Q_ULONG maxlen) +{ + resetError(); + Q_LONG retval = socketDevice()->peekBlock(data, maxlen); + if (retval == -1) + { + copyError(); + emit gotError(error()); + } + return retval; +} + +Q_LONG KClientSocketBase::peekBlock(char *data, Q_ULONG maxlen, KSocketAddress& from) +{ + resetError(); + Q_LONG retval = socketDevice()->peekBlock(data, maxlen, from); + if (retval == -1) + { + copyError(); + emit gotError(error()); + } + return retval; +} + +Q_LONG KClientSocketBase::writeBlock(const char *data, Q_ULONG len) +{ + resetError(); + Q_LONG retval = socketDevice()->writeBlock(data, len); + if (retval == -1) + { + copyError(); + emit gotError(error()); + } + return retval; +} + +Q_LONG KClientSocketBase::writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to) +{ + resetError(); + Q_LONG retval = socketDevice()->writeBlock(data, len, to); + if (retval == -1) + { + copyError(); + emit gotError(error()); + } + return retval; +} + +KSocketAddress KClientSocketBase::localAddress() const +{ + return socketDevice()->localAddress(); +} + +KSocketAddress KClientSocketBase::peerAddress() const +{ + return socketDevice()->peerAddress(); +} + +bool KClientSocketBase::emitsReadyRead() const +{ + return d->enableRead; +} + +void KClientSocketBase::enableRead(bool enable) +{ + QMutexLocker locker(mutex()); + + d->enableRead = enable; + QSocketNotifier *n = socketDevice()->readNotifier(); + if (n) + n->setEnabled(enable); +} + +bool KClientSocketBase::emitsReadyWrite() const +{ + return d->enableWrite; +} + +void KClientSocketBase::enableWrite(bool enable) +{ + QMutexLocker locker(mutex()); + + d->enableWrite = enable; + QSocketNotifier *n = socketDevice()->writeNotifier(); + if (n) + n->setEnabled(enable); +} + +void KClientSocketBase::slotReadActivity() +{ + if (d->enableRead) + emit readyRead(); +} + +void KClientSocketBase::slotWriteActivity() +{ + if (d->enableWrite) + emit readyWrite(); +} + +void KClientSocketBase::lookupFinishedSlot() +{ + if (d->peerResolver.isRunning() || d->localResolver.isRunning() || state() != HostLookup) + return; + + QObject::disconnect(&d->peerResolver, 0L, this, SLOT(lookupFinishedSlot())); + QObject::disconnect(&d->localResolver, 0L, this, SLOT(lookupFinishedSlot())); + if (d->peerResolver.status() < 0 || d->localResolver.status() < 0) + { + setState(Idle); // backtrack + setError(IO_LookupError, LookupFailure); + emit stateChanged(Idle); + emit gotError(LookupFailure); + return; + } + + d->localResults = d->localResolver.results(); + d->peerResults = d->peerResolver.results(); + setState(HostFound); + emit stateChanged(HostFound); + emit hostFound(); +} + +void KClientSocketBase::stateChanging(SocketState newState) +{ + if (newState == Connected && socketDevice()) + { + QSocketNotifier *n = socketDevice()->readNotifier(); + if (n) + { + n->setEnabled(d->enableRead); + QObject::connect(n, SIGNAL(activated(int)), this, SLOT(slotReadActivity())); + } + else + return; + + n = socketDevice()->writeNotifier(); + if (n) + { + n->setEnabled(d->enableWrite); + QObject::connect(n, SIGNAL(activated(int)), this, SLOT(slotWriteActivity())); + } + else + return; + } +} + +void KClientSocketBase::copyError() +{ + setError(socketDevice()->status(), socketDevice()->error()); +} + +#include "kclientsocketbase.moc" diff --git a/kdecore/network/kclientsocketbase.h b/kdecore/network/kclientsocketbase.h new file mode 100644 index 000000000..ccd94994a --- /dev/null +++ b/kdecore/network/kclientsocketbase.h @@ -0,0 +1,517 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KCLIENTSOCKETBASE_H +#define KCLIENTSOCKETBASE_H + +#include <qobject.h> +#include <qstring.h> + +#include "ksocketbase.h" +#include "kresolver.h" +#include <kdelibs_export.h> + +namespace KNetwork { + +class KClientSocketBasePrivate; +/** @class KClientSocketBase kclientsocketbase.h kclientsocketbase.h + * @brief Abstract client socket class. + * + * This class provides the base functionality for client sockets, + * such as, and especially, name resolution and signals. + * + * @note This class is abstract. If you're looking for a normal, + * client socket class, see @ref KStreamSocket and KBufferedSocket + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KClientSocketBase : public QObject, public KActiveSocketBase +{ + Q_OBJECT + +public: + /** + * Socket states. + * + * These are the possible states for a KClientSocketBase: + * - Idle: socket is not connected + * - HostLookup: socket is doing host lookup prior to connecting + * - HostFound: name lookup is complete + * - Bound: the socket is locally bound + * - Connecting: socket is attempting connection + * - Open: socket is open + * - Connected (=Open): socket is connected + * - Connection (=Open): yet another name for a connected socket + * - Closing: socket is shutting down + * + * Whenever the socket state changes, the @ref stateChanged(int) signal + * will be emitted. + */ + enum SocketState + { + Idle, + HostLookup, + HostFound, + Bound, + Connecting, + Open, + Closing, + + Unconnected = Bound, + Connected = Open, + Connection = Open + }; + +public: + /** + * Default constructor. + * + * @param parent the parent QObject object + * @param name the name of this object + */ + KClientSocketBase(QObject* parent, const char *name); + + /** + * Destructor. + */ + virtual ~KClientSocketBase(); + + /** + * Returns the current state for this socket. + * @see SocketState + */ + SocketState state() const; + +protected: + /** + * Sets the socket options. Reimplemented from KSocketBase. + */ + virtual bool setSocketOptions(int opts); + +public: + /** + * Returns the internal KResolver object used for + * looking up the peer host name and service. + * + * This can be used to set extra options to the + * lookup process other than the default values, as well + * as obtaining the error codes in case of lookup failure. + */ + KResolver& peerResolver() const; + + /** + * Returns the internal list of resolved results for the peer address. + */ + const KResolverResults& peerResults() const; + + /** + * Returns the internal KResolver object used for + * looking up the local host name and service. + * + * This can be used to set extra options to the + * lookup process other than the default values, as well + * as obtaining the error codes in case of lookup failure. + */ + KResolver& localResolver() const; + + /** + * Returns the internal list of resolved results for the local address. + */ + const KResolverResults& localResults() const; + + /** + * Enables or disables name resolution. If this flag is set to true, + * @ref bind and @ref connect operations will trigger name lookup + * operations (i.e., converting a hostname into its binary form). + * If the flag is set to false, those operations will instead + * try to convert a string representation of an address without + * attempting name resolution. + * + * This is useful, for instance, when IP addresses are in + * their string representation (such as "1.2.3.4") or come + * from other sources like @ref KSocketAddress. + * + * @param enable whether to enable + */ + void setResolutionEnabled(bool enable); + + /** + * Sets the allowed families for the resolutions. + * + * @param families the families that we want/accept + * @see KResolver::SocketFamilies for possible values + */ + void setFamily(int families); + + /** + * Starts the lookup for peer and local hostnames as + * well as their services. + * + * If the blocking mode for this object is on, this function will + * wait for the lookup results to be available (by calling the + * @ref KResolver::wait method on the resolver objects). + * + * When the lookup is done, the signal @ref hostFound will be + * emitted (only once, even if we're doing a double lookup). + * If the lookup failed (for any of the two lookups) the + * @ref gotError signal will be emitted with the appropriate + * error condition (see @ref KSocketBase::SocketError). + * + * This function returns true on success and false on error. Note that + * this is not the lookup result! + */ + virtual bool lookup(); + + /** + * Binds this socket to the given nodename and service, + * or use the default ones if none are given. + * + * Upon successful binding, the @ref bound signal will be + * emitted. If an error is found, the @ref gotError + * signal will be emitted. + * + * @note Due to the internals of the name lookup and binding + * mechanism, some (if not most) implementations of this function + * do not actually bind the socket until the connection + * is requested (see @ref connect). They only set the values + * for future reference. + * + * This function returns true on success. + * + * @param node the nodename + * @param service the service + */ + virtual bool bind(const QString& node = QString::null, + const QString& service = QString::null) = 0; + + /** + * Reimplemented from KSocketBase. Connect this socket to this + * specific address. + * + * Unlike @ref bind(const QString&, const QString&) above, this function + * really does bind the socket. No lookup is performed. The @ref bound + * signal will be emitted. + */ + virtual bool bind(const KResolverEntry& address); + + /** + * Attempts to connect to the these hostname and service, + * or use the default ones if none are given. If a connection attempt + * is already in progress, check on its state and set the error status + * (NoError or InProgress). + * + * If the blocking mode for this object is on, this function will only + * return when all the resolved peer addresses have been tried or when + * a connection is established. + * + * Upon successfully connecting, the @ref connected signal + * will be emitted. If an error is found, the @ref gotError + * signal will be emitted. + * + * @par Note for derived classes: + * Derived classes must implement this function. The implementation + * will set the parameters for the lookup (using the peer KResolver + * object) and call @ref lookup to start it. + * + * @par + * The implementation should use the @ref hostFound + * signal to be notified of the completion of the lookup process and + * then proceed to start the connection itself. Care should be taken + * regarding the value of @ref blocking flag. + * + * @param node the nodename + * @param service the service + */ + virtual bool connect(const QString& node = QString::null, + const QString& service = QString::null) = 0; + + /** + * @overload + * Reimplemented from KSocketBase. + */ + virtual bool connect(const KResolverEntry& address); + + /** + * @deprecated + * This is a convenience function provided to ease migrating from + * Qt 3.x's QSocket class. + */ + inline void connectToHost(const QString& host, Q_UINT16 port) + { connect(host, QString::number(port)); } + + /** + * Disconnects the socket. + * Note that not all socket types can disconnect. + */ + virtual bool disconnect(); + + /** + * Opens the socket. Reimplemented from QIODevice. + * + * You should not call this function; instead, use @ref connect + */ + virtual inline bool open(int) + { return connect(); } + + /** + * Closes the socket. Reimplemented from QIODevice. + * + * The closing of the socket causes the emission of the + * signal @ref closed. + */ + virtual void close(); + + /** + * This call is not supported on sockets. Reimplemented from QIODevice. + */ + virtual void flush() + { } + + /** + * Returns the number of bytes available on this socket. + * Reimplemented from KSocketBase. + */ + virtual Q_LONG bytesAvailable() const; + + /** + * Waits for more data. Reimplemented from KSocketBase. + */ + virtual Q_LONG waitForMore(int msecs, bool *timeout = 0L); + + /** + * Reads data from a socket. Reimplemented from KSocketBase. + */ + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen); + + /** + * @overload + * Reads data from a socket. Reimplemented from KSocketBase. + */ + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen, KSocketAddress& from); + + /** + * Peeks data from the socket. Reimplemented from KSocketBase. + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen); + + /** + * @overload + * Peeks data from the socket. Reimplemented from KSocketBase. + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen, KSocketAddress &from); + + /** + * Writes data to the socket. Reimplemented from KSocketBase. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len); + + /** + * @overload + * Writes data to the socket. Reimplemented from KSocketBase. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to); + + /** + * Returns the local socket address. Reimplemented from KSocketBase. + */ + virtual KSocketAddress localAddress() const; + + /** + * Returns the peer socket address. Reimplemented from KSocketBase. + */ + virtual KSocketAddress peerAddress() const; + + /** + * Returns true if the readyRead signal is set to be emitted. + */ + bool emitsReadyRead() const; + + /** + * Enables the emission of the readyRead signal. + * By default, this signal is enabled. + * + * @param enable whether to enable the signal + */ + virtual void enableRead(bool enable); + + /** + * Returns true if the readyWrite signal is set to be emitted. + */ + bool emitsReadyWrite() const; + + /** + * Enables the emission of the readyWrite signal. + * By default, this signal is disabled. + * + * @param enable whether to enable the signal + */ + virtual void enableWrite(bool enable); + +protected slots: + // protected slots + + /** + * This slot is connected to the read notifier's signal meaning + * the socket can read more data. + * + * The default implementation only emits the readyRead signal. + * + * Override if your class requires processing of incoming + * data. + */ + virtual void slotReadActivity(); + + /** + * This slot is connected to the write notifier's signal + * meaning the socket can write more data. + * + * The default implementation only emits the readyWrite signal. + * + * Override if your class writes data from another source + * (like a buffer). + */ + virtual void slotWriteActivity(); + +private slots: + void lookupFinishedSlot(); + +signals: + /** + * This signal is emitted whenever the socket state changes. + * + * Note: do not delete this object inside the slot called by this + * signal. + * + * @param newstate the new state of the socket object + */ + void stateChanged(int newstate); + + /** + * This signal is emitted when this object finds an error. + * The @p code parameter contains the error code that can + * also be found by calling @ref error. + */ + void gotError(int code); + + /** + * This signal is emitted when the lookup is successfully completed. + */ + void hostFound(); + + /** + * This signal is emitted when the socket successfully binds + * to an address. + * + * @param local the local address we bound to + */ + void bound(const KResolverEntry& local); + + /** + * This signal is emitted when the socket is about to connect + * to an address (but before doing so). + * + * The @p skip parameter can be used to make the loop skip this address. + * Its value is initially false: change it to true if you want to + * skip the current address (as given by @p remote). + * + * This function is also useful if one wants to reset the timeout. + * + * @param remote the address we're about to connect to + * @param skip set to true if you want to skip this address + * @note if the connection is successful, the @ref connected signal will be + * emitted. + */ + void aboutToConnect(const KResolverEntry& remote, bool& skip); + + /** + * This socket is emitted when the socket successfully connects + * to a remote address. + * + * @param remote the remote address we did connect to + */ + void connected(const KResolverEntry& remote); + + /** + * This signal is emitted when the socket completes the + * closing/shut down process. + */ + void closed(); + + /** + * This signal is emitted whenever the socket is ready for + * reading -- i.e., there is data to be read in the buffers. + * The subsequent read operation is guaranteed to be non-blocking. + * + * You can toggle the emission of this signal with the @ref enableRead + * function. This signal is by default enabled. + */ + void readyRead(); + + /** + * This signal is emitted whenever the socket is ready for + * writing -- i.e., whenever there's space available in the buffers + * to receive more data. The subsequent write operation is + * guaranteed to be non-blocking. + * + * You can toggle the emission of this signal with the @ref enableWrite + * function. This signal is by default disabled. You will + * want to disable this signal after the first reception, since + * it'll probably fire at every event loop. + */ + void readyWrite(); + +protected: + /** + * Sets the socket state to @p state. This function does not + * emit the @ref stateChanged signal. + */ + void setState(SocketState state); + + /** + * This function is called by @ref setState whenever the state + * changes. You should override it if you need to specify any + * actions to be done when the state changes. + * + * The default implementation acts for these states only: + * - Connected: it sets up the socket notifiers to fire readyRead and + * readyWrite signals. + */ + virtual void stateChanging(SocketState newState); + + /** + * Convenience function to set this object's error code to match + * that of the socket device. + */ + void copyError(); + +private: + KClientSocketBase(const KClientSocketBase&); + KClientSocketBase& operator=(const KClientSocketBase&); + + KClientSocketBasePrivate *d; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/kdatagramsocket.cpp b/kdecore/network/kdatagramsocket.cpp new file mode 100644 index 000000000..a0d3bc05d --- /dev/null +++ b/kdecore/network/kdatagramsocket.cpp @@ -0,0 +1,283 @@ +/* -*- C++ -*- + * Copyright (C) 2003,2004 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/socket.h> + +#include "ksocketaddress.h" +#include "kresolver.h" +#include "ksocketdevice.h" +#include "kdatagramsocket.h" + +using namespace KNetwork; + +/* + * TODO: + * + * don't use signals and slots to track state changes: use stateChanging + * + */ + +KDatagramSocket::KDatagramSocket(QObject* parent, const char *name) + : KClientSocketBase(parent, name), d(0L) +{ + peerResolver().setFamily(KResolver::KnownFamily); + localResolver().setFamily(KResolver::KnownFamily); + + peerResolver().setSocketType(SOCK_DGRAM); + localResolver().setSocketType(SOCK_DGRAM); + + localResolver().setFlags(KResolver::Passive); + + // QObject::connect(localResolver(), SIGNAL(finished(KResolverResults)), + // this, SLOT(lookupFinishedLocal())); + QObject::connect(&peerResolver(), SIGNAL(finished(KResolverResults)), + this, SLOT(lookupFinishedPeer())); + QObject::connect(this, SIGNAL(hostFound()), this, SLOT(lookupFinishedLocal())); +} + +KDatagramSocket::~KDatagramSocket() +{ + // KClientSocketBase's destructor closes the socket + + //delete d; +} + +bool KDatagramSocket::bind(const QString& node, const QString& service) +{ + if (state() >= Bound) + return false; + + if (localResolver().isRunning()) + localResolver().cancel(false); + + // no, we must do a host lookup + localResolver().setAddress(node, service); + + if (!lookup()) + return false; + + // see if lookup has finished already + // this also catches blocking mode, since lookup has to finish + // its processing if we're in blocking mode + if (state() > HostLookup) + return doBind(); + + return true; +} + +bool KDatagramSocket::connect(const QString& node, const QString& service) +{ + if (state() >= Connected) + return true; // already connected + + if (peerResolver().nodeName() != node || + peerResolver().serviceName() != service) + peerResolver().setAddress(node, service); // this resets the resolver's state + + // KClientSocketBase::lookup only works if the state is Idle or HostLookup + // therefore, we store the old state, call the lookup routine and then set + // it back. + SocketState s = state(); + setState(s == Connecting ? HostLookup : Idle); + bool ok = lookup(); + if (!ok) + { + setState(s); // go back + return false; + } + + // check if lookup is finished + // if we're in blocking mode, then the lookup has to be finished + if (state() == HostLookup) + { + // it hasn't finished + setState(Connecting); + emit stateChanged(Connecting); + return true; + } + + // it has to be finished here + if (state() != Connected) + { + setState(Connecting); + emit stateChanged(Connecting); + lookupFinishedPeer(); + } + + return state() == Connected; +} + +KDatagramPacket KDatagramSocket::receive() +{ + Q_LONG size = bytesAvailable(); + if (size == 0) + { + // nothing available yet to read + // wait for data if we're not blocking + if (blocking()) + socketDevice()->waitForMore(-1); // wait forever + else + { + // mimic error + setError(IO_ReadError, WouldBlock); + emit gotError(WouldBlock); + return KDatagramPacket(); + } + + // try again + size = bytesAvailable(); + } + + QByteArray data(size); + KSocketAddress address; + + // now do the reading + size = readBlock(data.data(), size, address); + if (size < 0) + // error has been set + return KDatagramPacket(); + + data.resize(size); // just to be sure + return KDatagramPacket(data, address); +} + +Q_LONG KDatagramSocket::send(const KDatagramPacket& packet) +{ + return writeBlock(packet.data(), packet.size(), packet.address()); +} + +Q_LONG KDatagramSocket::writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to) +{ + if (to.family() != AF_UNSPEC) + { + // make sure the socket is open at this point + if (!socketDevice()->isOpen()) + // error handling will happen below + socketDevice()->create(to.family(), SOCK_DGRAM, 0); + } + return KClientSocketBase::writeBlock(data, len, to); +} + +void KDatagramSocket::lookupFinishedLocal() +{ + // bind lookup has finished and succeeded + // state() == HostFound + + if (!doBind()) + return; // failed binding + + if (peerResults().count() > 0) + { + setState(Connecting); + emit stateChanged(Connecting); + + lookupFinishedPeer(); + } +} + +void KDatagramSocket::lookupFinishedPeer() +{ + // this function is called by lookupFinishedLocal above + // and is also connected to a signal + // so it might be called twice. + + if (state() != Connecting) + return; + + if (peerResults().count() == 0) + { + setState(Unconnected); + emit stateChanged(Unconnected); + return; + } + + KResolverResults::ConstIterator it = peerResults().begin(); + for ( ; it != peerResults().end(); ++it) + if (connect(*it)) + { + // weee, we connected + + setState(Connected); // this sets up signals + //setupSignals(); // setState sets up the signals + + emit stateChanged(Connected); + emit connected(*it); + return; + } + + // no connection + copyError(); + setState(Unconnected); + emit stateChanged(Unconnected); + emit gotError(error()); +} + +bool KDatagramSocket::doBind() +{ + if (localResults().count() == 0) + return true; + if (state() >= Bound) + return true; // already bound + + KResolverResults::ConstIterator it = localResults().begin(); + for ( ; it != localResults().end(); ++it) + if (bind(*it)) + { + // bound + setupSignals(); + return true; + } + + // not bound + // no need to set state since it can only be HostFound already + copyError(); + emit gotError(error()); + return false; +} + +void KDatagramSocket::setupSignals() +{ + QSocketNotifier *n = socketDevice()->readNotifier(); + if (n) + { + n->setEnabled(emitsReadyRead()); + QObject::connect(n, SIGNAL(activated(int)), this, SLOT(slotReadActivity())); + } + else + return; + + n = socketDevice()->writeNotifier(); + if (n) + { + n->setEnabled(emitsReadyWrite()); + QObject::connect(n, SIGNAL(activated(int)), this, SLOT(slotWriteActivity())); + } + else + return; +} + +#include "kdatagramsocket.moc" diff --git a/kdecore/network/kdatagramsocket.h b/kdecore/network/kdatagramsocket.h new file mode 100644 index 000000000..31ccf9af7 --- /dev/null +++ b/kdecore/network/kdatagramsocket.h @@ -0,0 +1,278 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KDATAGRAMSOCKET_H +#define KDATAGRAMSOCKET_H + +#include <qcstring.h> + +#include "ksocketaddress.h" +#include "kclientsocketbase.h" + +namespace KNetwork { + +class KResolverEntry; + +/** + * @class KDatagramPacket kdatagramsocket.h kdatagramsocket.h + * @brief one datagram + * + * This object represents one datagram of data sent or received through + * a datagram socket (as @ref KDatagramSocket or derived classes). A datagram + * consists of data as well as a network address associated (whither to send + * the data or whence it came). + * + * This is a lightweight class. Data is stored in a @ref QByteArray, which means + * that it is explicitly shared. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KDatagramPacket +{ + QByteArray m_data; + KSocketAddress m_address; + +public: + /** + * Default constructor. + */ + KDatagramPacket() + { } + + /** + * Constructs the datagram with the specified content. + */ + KDatagramPacket(const QByteArray& content) + : m_data(content) + { } + + /** + * Constructs the datagram with the specified content. + * + * @see setData for information on data sharing. + */ + KDatagramPacket(const char* content, uint length) + { setData(content, length); } + + /** + * Constructs the datagram with the specified content and address. + */ + KDatagramPacket(const QByteArray& content, const KSocketAddress& addr) + : m_data(content), m_address(addr) + { } + + /** + * Constructs the datagram with the specified content and address. + */ + KDatagramPacket(const char *content, uint length, const KSocketAddress& addr) + : m_address(addr) + { setData(content, length); } + + /** + * Copy constructor. Note that data is explicitly shared. + */ + KDatagramPacket(const KDatagramPacket& other) + { *this = other; } + + /** + * Destructor. Non-virtual. + */ + ~KDatagramPacket() + { } + + /** + * Returns the data. + */ + const QByteArray& data() const + { return m_data; } + + /** + * Returns the data length. + */ + uint length() const + { return m_data.size(); } + + /** + * Returns the data length. + */ + uint size() const + { return m_data.size(); } + + /** + * Returns true if this object is empty. + */ + bool isEmpty() const + { return m_data.isEmpty(); } + + /** + * Returns true if this object is null. + */ + bool isNull() const + { return m_data.isNull(); } + + /** + * Returns the socket address + */ + const KSocketAddress& address() const + { return m_address; } + + /** + * Sets the address stored to the given value. + */ + void setAddress(const KSocketAddress& addr) + { m_address = addr; } + + /** + * Detaches our data from a shared pool. + * @see QByteArray::detach + */ + void detach() + { m_data.detach(); } + + /** + * Sets the data to the given value. Data is explicitly shared. + */ + void setData(const QByteArray& data) + { m_data = data; } + + /** + * Sets the data to the given buffer and size. + */ + void setData(const char* data, uint length) + { m_data.duplicate(data, length); } +}; + +class KDatagramSocketPrivate; +/** + * @class KDatagramSocket kdatagramsocket.h kdatagramsocket.h + * @brief A socket that operates on datagrams. + * + * Unlike @ref KStreamSocket, which operates on a connection-based stream + * socket (generally TCP), this class and its descendants operates on datagrams, + * which are normally connectionless. + * + * This class in specific provides easy access to the system's connectionless + * SOCK_DGRAM sockets. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KDatagramSocket: public KClientSocketBase +{ + Q_OBJECT + +public: + /** + * Default constructor. + */ + KDatagramSocket(QObject* parent = 0L, const char *name = 0L); + + /** + * Destructor. This closes the socket. + */ + virtual ~KDatagramSocket(); + + /** + * Performs host lookups. + */ + // virtual bool lookup(); + + /** + * Binds this socket to the given address. If the socket is blocking, + * the socket will be bound when this function returns. + * + * Note that binding a socket is not necessary to be able to send datagrams. + * Some protocol families will use anonymous source addresses, while others + * will allocate an address automatically. + */ + virtual bool bind(const QString& node = QString::null, + const QString& service = QString::null); + + /** + * @overload + * Binds this socket to the given address. + */ + virtual bool bind(const KResolverEntry& entry) + { return KClientSocketBase::bind(entry); } + + /** + * "Connects" this socket to the given address. Note that connecting + * a datagram socket normally does not establish a permanent connection + * with the peer nor normally returns an error in case of failure. + * + * Connecting means only to designate the given address as the default + * destination address for datagrams sent without destination addresses + * ( writeBlock(const char *, Q_ULONG) ). + * + * @note Calling connect will not cause the socket to be bound. You have + * to call @ref bind explicitly. + */ + virtual bool connect(const QString& node = QString::null, + const QString& service = QString::null); + + /** + * @overload + * "Connects" this socket to the given address. + */ + virtual bool connect(const KResolverEntry& entry) + { return KClientSocketBase::connect(entry); } + + /** + * Writes data to the socket. Reimplemented from KClientSocketBase. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to); + + /** + * Receives one datagram from the stream. The reading process is guaranteed + * to be atomical and not lose data from the packet. + * + * If nothing could be read, a null object will be returned. + */ + virtual KDatagramPacket receive(); + + /** + * Sends one datagram into the stream. The destination address must be + * set if this socket has not been connected (see @ref connect). + * + * The data in this packet will be sent only in one single datagram. If the + * system cannot send it like that, this function will fail. So, please take + * into consideration the datagram size limits. + * + * @returns the number of bytes written or -1 in case of error. + */ + virtual Q_LONG send(const KDatagramPacket& packet); + +private slots: + void lookupFinishedLocal(); + void lookupFinishedPeer(); + +private: + bool doBind(); + void setupSignals(); + + KDatagramSocketPrivate *d; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/khttpproxysocketdevice.cpp b/kdecore/network/khttpproxysocketdevice.cpp new file mode 100644 index 000000000..73e433a2e --- /dev/null +++ b/kdecore/network/khttpproxysocketdevice.cpp @@ -0,0 +1,281 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/socket.h> + +#include <qsocketnotifier.h> +#include <qcstring.h> + +#include "kresolver.h" +#include "ksocketaddress.h" +#include "ksocketdevice.h" +#include "khttpproxysocketdevice.h" + +using namespace KNetwork; + +KResolverEntry KHttpProxySocketDevice::defaultProxy; + +class KNetwork::KHttpProxySocketDevicePrivate +{ +public: + KResolverEntry proxy; + QCString request; + QCString reply; + KSocketAddress peer; + + KHttpProxySocketDevicePrivate() + : proxy(KHttpProxySocketDevice::defaultProxy) + { } +}; + +KHttpProxySocketDevice::KHttpProxySocketDevice(const KSocketBase* parent) + : KSocketDevice(parent), d(new KHttpProxySocketDevicePrivate) +{ +} + +KHttpProxySocketDevice::KHttpProxySocketDevice(const KResolverEntry& proxy) + : d(new KHttpProxySocketDevicePrivate) +{ + d->proxy = proxy; +} + +KHttpProxySocketDevice::~KHttpProxySocketDevice() +{ + // nothing special to be done during closing + // KSocketDevice::~KSocketDevice closes the socket + + delete d; +} + +int KHttpProxySocketDevice::capabilities() const +{ + return CanConnectString | CanNotBind | CanNotListen | CanNotUseDatagrams; +} + +const KResolverEntry& +KHttpProxySocketDevice::proxyServer() const +{ + return d->proxy; +} + +void KHttpProxySocketDevice::setProxyServer(const KResolverEntry& proxy) +{ + d->proxy = proxy; +} + +void KHttpProxySocketDevice::close() +{ + d->reply = d->request = QCString(); + d->peer = KSocketAddress(); + KSocketDevice::close(); +} + +KSocketAddress KHttpProxySocketDevice::peerAddress() const +{ + if (isOpen()) + return d->peer; + return KSocketAddress(); +} + +KSocketAddress KHttpProxySocketDevice::externalAddress() const +{ + return KSocketAddress(); +} + +bool KHttpProxySocketDevice::connect(const KResolverEntry& address) +{ + if (d->proxy.family() == AF_UNSPEC) + // no proxy server set ! + return KSocketDevice::connect(address); + + if (isOpen()) + { + // socket is already open + resetError(); + return true; + } + + if (m_sockfd == -1) + // socket isn't created yet + return connect(address.address().nodeName(), + address.address().serviceName()); + + d->peer = address.address(); + return parseServerReply(); +} + +bool KHttpProxySocketDevice::connect(const QString& node, const QString& service) +{ + // same safety checks as above + if (m_sockfd == -1 && (d->proxy.family() == AF_UNSPEC || + node.isEmpty() || service.isEmpty())) + { + // no proxy server set ! + setError(IO_ConnectError, NotSupported); + return false; + } + + if (isOpen()) + { + // socket is already open + return true; + } + + if (m_sockfd == -1) + { + // must create the socket + if (!KSocketDevice::connect(d->proxy)) + return false; // also unable to contact proxy server + setState(0); // unset open flag + + // prepare the request + QString request = QString::fromLatin1("CONNECT %1:%2 HTTP/1.1\r\n" + "Cache-Control: no-cache\r\n" + "Host: \r\n" + "\r\n"); + QString node2 = node; + if (node.contains(':')) + node2 = '[' + node + ']'; + + d->request = request.arg(node2).arg(service).latin1(); + } + + return parseServerReply(); +} + +bool KHttpProxySocketDevice::parseServerReply() +{ + // make sure we're connected + if (!KSocketDevice::connect(d->proxy)) + if (error() == InProgress) + return true; + else if (error() != NoError) + return false; + + if (!d->request.isEmpty()) + { + // send request + Q_LONG written = writeBlock(d->request, d->request.length()); + if (written < 0) + { + qDebug("KHttpProxySocketDevice: would block writing request!"); + if (error() == WouldBlock) + setError(IO_ConnectError, InProgress); + return error() == WouldBlock; // error + } + qDebug("KHttpProxySocketDevice: request written"); + + d->request.remove(0, written); + + if (!d->request.isEmpty()) + { + setError(IO_ConnectError, InProgress); + return true; // still in progress + } + } + + // request header is sent + // must parse reply, but must also be careful not to read too much + // from the buffer + + int index; + if (!blocking()) + { + Q_LONG avail = bytesAvailable(); + qDebug("KHttpProxySocketDevice: %ld bytes available", avail); + setState(0); + if (avail == 0) + { + setError(IO_ConnectError, InProgress); + return true; + } + else if (avail < 0) + return false; // error! + + QByteArray buf(avail); + if (peekBlock(buf.data(), avail) < 0) + return false; // error! + + QCString fullHeaders = d->reply + buf.data(); + // search for the end of the headers + index = fullHeaders.find("\r\n\r\n"); + if (index == -1) + { + // no, headers not yet finished... + // consume data from socket + readBlock(buf.data(), avail); + d->reply += buf.data(); + setError(IO_ConnectError, InProgress); + return true; + } + + // headers are finished + index -= d->reply.length(); + d->reply += fullHeaders.mid(d->reply.length(), index + 4); + + // consume from socket + readBlock(buf.data(), index + 4); + } + else + { + int state = 0; + if (d->reply.right(3) == "\r\n\r") + state = 3; + else if (d->reply.right(2) == "\r\n") + state = 2; + else if (d->reply.right(1) == "\r") + state = 1; + while (state != 4) + { + char c = getch(); + d->reply += c; + + if ((state == 3 && c == '\n') || + (state == 1 && c == '\n') || + c == '\r') + ++state; + else + state = 0; + } + } + + // now really parse the reply + qDebug("KHttpProxySocketDevice: get reply: %s\n", + d->reply.left(d->reply.find('\r')).data()); + if (d->reply.left(7) != "HTTP/1." || + (index = d->reply.find(' ')) == -1 || + d->reply[index + 1] != '2') + { + setError(IO_ConnectError, NetFailure); + return false; + } + + // we've got it + resetError(); + setState(IO_Open); + return true; +} diff --git a/kdecore/network/khttpproxysocketdevice.h b/kdecore/network/khttpproxysocketdevice.h new file mode 100644 index 000000000..ed4f1d60c --- /dev/null +++ b/kdecore/network/khttpproxysocketdevice.h @@ -0,0 +1,122 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KHTTPPROXYSOCKETDEVICE_H +#define KHTTPPROXYSOCKETDEVICE_H + +#include "ksocketdevice.h" + +namespace KNetwork { + +class KHttpProxySocketDevicePrivate; + +/** + * @class KHttpProxySocketDevice khttpproxysocketdevice.h khttproxysocketdevice.h + * @brief The low-level backend for HTTP proxying. + * + * This class derives from @ref KSocketDevice and implements the necessary + * calls to make a connection through an HTTP proxy. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KHttpProxySocketDevice: public KSocketDevice +{ +public: + /** + * Constructor. + */ + KHttpProxySocketDevice(const KSocketBase* = 0L); + + /** + * Constructor with proxy server's address. + */ + KHttpProxySocketDevice(const KResolverEntry& proxy); + + /** + * Destructor + */ + virtual ~KHttpProxySocketDevice(); + + /** + * Sets our capabilities. + */ + virtual int capabilities() const; + + /** + * Retrieves the proxy server address. + */ + const KResolverEntry& proxyServer() const; + + /** + * Sets the proxy server address. + */ + void setProxyServer(const KResolverEntry& proxy); + + /** + * Closes the socket. + */ + virtual void close(); + + /** + * Overrides connection. + */ + virtual bool connect(const KResolverEntry& address); + + /** + * Name-based connection. + * We can tell the HTTP proxy server the full name. + */ + virtual bool connect(const QString& name, const QString& service); + + /** + * Return the peer address. + */ + virtual KSocketAddress peerAddress() const; + + /** + * Return the externally visible address. We can't tell what that address is, + * so this function always returns an empty object. + */ + virtual KSocketAddress externalAddress() const; + +private: + /** + * Parses the server reply after sending the connect command. + * Returns true on success and false on failure. + */ + bool parseServerReply(); + KHttpProxySocketDevicePrivate *d; + +public: + /** + * This is the default proxy server to be used. + * Applications may want to set this value so that calling @ref setProxyServer + * is unnecessary. + */ + static KResolverEntry defaultProxy; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/kiobuffer.h b/kdecore/network/kiobuffer.h new file mode 100644 index 000000000..4da5ecb43 --- /dev/null +++ b/kdecore/network/kiobuffer.h @@ -0,0 +1,144 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KIOBUFFER_H +#define KIOBUFFER_H + +#include <qcstring.h> + +#include <kdelibs_export.h> + +class QIODevice; + +/** + * @class KIOBufferBase kiobuffer.h kiobuffer.h + * @brief base for I/O buffer implementation + * + * This class declares the base methods to interface with an I/O buffer. + * Most applications will not need to access this class directly, since + * it is all handled by @ref KNetwork::KBufferedSocket and other buffering + * classes. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KIOBufferBase +{ +public: + /** + * Default constructor. Does nothing. + */ + KIOBufferBase() + { } + + /** + * Copy constructor. Does nothing here. + */ + KIOBufferBase(const KIOBufferBase& ) + { } + + /** + * Virtual destructor. Does nothing. + */ + virtual ~KIOBufferBase() + { } + + /** + * Assignment operator. Does nothing. + */ + KIOBufferBase& operator=(const KIOBufferBase& ) + { return *this; } + + /** + * Returns true if a line can be read from the buffer. + */ + virtual bool canReadLine() const = 0; + + /** + * Reads a line from the buffer and discards it. + */ + virtual QCString readLine() = 0; + + /** + * Returns the number of bytes in the buffer. Note that this is not + * the size of the buffer. + * + * @sa size + */ + virtual Q_LONG length() const = 0; + + /** + * Returns true if the buffer is empty of data. + */ + inline bool isEmpty() const + { return length() == 0; } + + /** + * Retrieves the buffer size. The value of -1 indicates that + * the buffer has no defined upper limit. + * + * @sa length for the length of the data stored + */ + virtual Q_LONG size() const = 0; + + /** + * Returns true if the buffer is full (i.e., cannot receive more data) + */ + inline bool isFull() const + { return size() != -1 && size() == length(); } + + /** + * Sets the size of the buffer, if allowed. + * + * @param size the maximum size, use -1 for unlimited. + * @returns true on success, false if an error occurred. + * @note if the new size is less than length(), the buffer will be truncated + */ + virtual bool setSize(Q_LONG size) = 0; + + /** + * Adds data to the end of the buffer. + * + * @param data the data to be added + * @param len the data length, in bytes + * @returns the number of bytes added to the end of the buffer. + */ + virtual Q_LONG feedBuffer(const char *data, Q_LONG len) = 0; + + /** + * Consumes data from the beginning of the buffer. + * + * @param data where to copy the data to + * @param maxlen the maximum length to copy, in bytes + * @param discard if true, the bytes copied will be discarded + * @returns the number of bytes copied from the buffer + */ + virtual Q_LONG consumeBuffer(char *data, Q_LONG maxlen, bool discard = true) = 0; + + /** + * Clears the buffer. + */ + virtual void clear() = 0; +}; + +#endif diff --git a/kdecore/network/kmulticastsocket.h b/kdecore/network/kmulticastsocket.h new file mode 100644 index 000000000..80c2f6990 --- /dev/null +++ b/kdecore/network/kmulticastsocket.h @@ -0,0 +1,113 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KMULTICASTSOCKET_H +#define KMULTICASTSOCKET_H + +#include "kdatagramsocket.h" +#include "kmulticastsocketdevice.h" + +namespace KNetwork { + +class KMulticastSocketPrivate; +/** + * @class KMulticastSocket kmulticastsocket.h kmulticastsocket.h + * @brief A multicast-capable datagram socket class + * + * This class derives from @ref KDatagramSocket adding methods to it to + * allow better control over the multicast functionality. In special, + * the join and leave group functions are added. + * + * Other more low-level options on multicast sockets can be accessed + * directly with the @ref KMulticastSocketImpl class returned by + * @ref multicastSocketDevice. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KMulticastSocket: public KDatagramSocket +{ + // Q_add-it-here_OBJECT +public: + /** + * Constructor. + */ + KMulticastSocket(QObject* parent = 0L, const char *name = 0L); + + /** + * Destructor. + */ + ~KMulticastSocket(); + + /** + * Returns the multicast socket device in use by this object. + * + * @note The returned object can be null. + */ + KMulticastSocketImpl* multicastSocketDevice(); + + /** + * @overload + */ + const KMulticastSocketImpl* multicastSocketDevice() const; + + /** + * Joins a multicast group. The group to be joined is identified by the + * @p group parameter. + * + * @param group the multicast group to join + * @returns true on success + */ + virtual bool joinGroup(const KSocketAddress& group); + + /** + * @overload + * Joins a multicast group. This function also specifies the network interface + * to be used. + */ + virtual bool joinGroup(const KSocketAddress& group, + const KNetworkInterface& iface); + + /** + * Leaves a multicast group. The group being left is given by its address in the + * @p group parameter. + * + * @param group the group to leave + * @returns true on successful leaving the group + */ + virtual bool leaveGroup(const KSocketAddress& group); + + /** + * @overload + * Leaves a multicast group. + */ + virtual bool leaveGroup(const KSocketAddress& group, + const KNetworkInterface& iface); + +private: + KMulticastSocketPrivate *d; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/kmulticastsocketdevice.h b/kdecore/network/kmulticastsocketdevice.h new file mode 100644 index 000000000..7710fe5c1 --- /dev/null +++ b/kdecore/network/kmulticastsocketdevice.h @@ -0,0 +1,151 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KMULTICASTSOCKETDEVICE_H +#define KMULTICASTSOCKETDEVICE_H + +#include "ksocketdevice.h" +#include "knetworkinterface.h" +#include "ksocketaddress.h" + +namespace KNetwork { + +class KMulticastSocketImplPrivate; + +/** + * @class KMulticastSocketImpl kmulticastsocketdevice.h kmulticastsocketdevice.h + * @brief The low-level backend for multicasting sockets. + * + * This class is an interface providing methods for handling multicast + * operations. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KMulticastSocketImpl: public KSocketDevice +{ +public: + /** + * Constructor. + */ + KMulticastSocketImpl(const KSocketBase* = 0L); + + /** + * Destructor + */ + virtual ~KMulticastSocketImpl(); + + /** + * Sets our capabilities. + */ + virtual int capabilities() const; + + /** + * Overrides the socket creation. + */ + virtual bool create(int family, int type, int protocol); + + /** + * Overrides connection. Multicast sockets may not connect. + */ + virtual bool connect(const KResolverEntry& address); + + /** + * Retrieves the time-to-live/hop count value on multicast packets being sent. + */ + virtual int timeToLive() const; + + /** + * Sets the time-to-live/hop count for outgoing multicast packets. + * + * @param ttl the hop count, from 0 to 255 + * @returns true if setting the value was successful. + */ + virtual bool setTimeToLive(int ttl); + + /** + * Retrieves the flag indicating if sent packets will be echoed back + * to sender. + */ + virtual bool multicastLoop() const; + + /** + * Sets the flag indicating the loopback of packets to the sender. + * + * @param enable if true, will echo back + * @returns true if setting the value was successful. + */ + virtual bool setMulticastLoop(bool enable); + + /** + * Retrieves the network interface this socket is associated to. + */ + virtual KNetworkInterface networkInterface(); + + /** + * Sets the network interface on which this socket should work. + * + * @param iface the interface to associate with + * @return true if setting the value was successful. + */ + virtual bool setNetworkInterface(const KNetworkInterface& iface); + + /** + * Joins a multicast group. The group to be joined is identified by the + * @p group parameter. + * + * @param group the multicast group to join + * @returns true on success + */ + virtual bool joinGroup(const KSocketAddress& group); + + /** + * @overload + * Joins a multicast group. This function also specifies the network interface + * to be used. + */ + virtual bool joinGroup(const KSocketAddress& group, + const KNetworkInterface& iface); + + /** + * Leaves a multicast group. The group being left is given by its address in the + * @p group parameter. + * + * @param group the group to leave + * @returns true on successful leaving the group + */ + virtual bool leaveGroup(const KSocketAddress& group); + + /** + * @overload + * Leaves a multicast group. + */ + virtual bool leaveGroup(const KSocketAddress& group, + const KNetworkInterface& iface); +private: + KMulticastSocketImplPrivate *d; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/knetworkinterface.h b/kdecore/network/knetworkinterface.h new file mode 100644 index 000000000..74fd52d55 --- /dev/null +++ b/kdecore/network/knetworkinterface.h @@ -0,0 +1,46 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef KNETWORKINTERFACE_H +#define KNETWORKINTERFACE_H + +#include <kdelibs_export.h> + +namespace KNetwork { + +/** + * A place-holder class for a future network interface class. + * This class is to be replaced with a more powerful version, inspired + * by: + * - KInetInterface (kdenetwork/krfb/srvloc) + * - NWInterface (kdenonbeta/knot/lib) + * - java.net.NetworkInterface + */ +class KNetworkInterface +{ +}; + +} // namespace KNetwork + +#endif + diff --git a/kdecore/network/kresolver.cpp b/kdecore/network/kresolver.cpp new file mode 100644 index 000000000..915288123 --- /dev/null +++ b/kdecore/network/kresolver.cpp @@ -0,0 +1,1164 @@ +/* -*- C++ -*- + * Copyright (C) 2003-2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +// System includes +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/param.h> +#include <errno.h> +#include <netdb.h> +#include <time.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <unistd.h> + +// Qt includes +#include <qapplication.h> +#include <qstring.h> +#include <qcstring.h> +#include <qstrlist.h> +#include <qstringlist.h> +#include <qshared.h> +#include <qdatetime.h> +#include <qtimer.h> +#include <qmutex.h> +#include <qguardedptr.h> + +// IDN +#ifdef HAVE_IDNA_H +# include <idna.h> +#endif + +// KDE +#include <klocale.h> + +// Us +#include "kresolver.h" +#include "kresolver_p.h" +#include "ksocketaddress.h" + +#ifdef NEED_MUTEX +#warning "mutex" +QMutex getXXbyYYmutex; +#endif + +using namespace KNetwork; +using namespace KNetwork::Internal; + +///////////////////////////////////////////// +// class KResolverEntry + +class KNetwork::KResolverEntryPrivate: public QShared +{ +public: + KSocketAddress addr; + int socktype; + int protocol; + QString canonName; + QCString encodedName; + + inline KResolverEntryPrivate() : + socktype(0), protocol(0) + { } +}; + +// default constructor +KResolverEntry::KResolverEntry() : + d(0L) +{ +} + +// constructor with stuff +KResolverEntry::KResolverEntry(const KSocketAddress& addr, int socktype, int protocol, + const QString& canonName, const QCString& encodedName) : + d(new KResolverEntryPrivate) +{ + d->addr = addr; + d->socktype = socktype; + d->protocol = protocol; + d->canonName = canonName; + d->encodedName = encodedName; +} + +// constructor with even more stuff +KResolverEntry::KResolverEntry(const struct sockaddr* sa, Q_UINT16 salen, int socktype, + int protocol, const QString& canonName, + const QCString& encodedName) : + d(new KResolverEntryPrivate) +{ + d->addr = KSocketAddress(sa, salen); + d->socktype = socktype; + d->protocol = protocol; + d->canonName = canonName; + d->encodedName = encodedName; +} + +// copy constructor +KResolverEntry::KResolverEntry(const KResolverEntry& that) : + d(0L) +{ + *this = that; +} + +// destructor +KResolverEntry::~KResolverEntry() +{ + if (d == 0L) + return; + + if (d->deref()) + delete d; +} + +// returns the socket address +KSocketAddress KResolverEntry::address() const +{ + return d ? d->addr : KSocketAddress(); +} + +// returns the length +Q_UINT16 KResolverEntry::length() const +{ + return d ? d->addr.length() : 0; +} + +// returns the family +int KResolverEntry::family() const +{ + return d ? d->addr.family() : AF_UNSPEC; +} + +// returns the canonical name +QString KResolverEntry::canonicalName() const +{ + return d ? d->canonName : QString::null; +} + +// returns the encoded name +QCString KResolverEntry::encodedName() const +{ + return d ? d->encodedName : QCString(); +} + +// returns the socket type +int KResolverEntry::socketType() const +{ + return d ? d->socktype : 0; +} + +// returns the protocol +int KResolverEntry::protocol() const +{ + return d ? d->protocol : 0; +} + +// assignment operator +KResolverEntry& KResolverEntry::operator= (const KResolverEntry& that) +{ + // copy the data + if (that.d) + that.d->ref(); + + if (d && d->deref()) + delete d; + + d = that.d; + return *this; +} + +///////////////////////////////////////////// +// class KResolverResults + +class KNetwork::KResolverResultsPrivate +{ +public: + QString node, service; + int errorcode, syserror; + + KResolverResultsPrivate() : + errorcode(0), syserror(0) + { } +}; + +// default constructor +KResolverResults::KResolverResults() + : d(new KResolverResultsPrivate) +{ +} + +// copy constructor +KResolverResults::KResolverResults(const KResolverResults& other) + : QValueList<KResolverEntry>(other), d(new KResolverResultsPrivate) +{ + *d = *other.d; +} + +// destructor +KResolverResults::~KResolverResults() +{ + delete d; +} + +// assignment operator +KResolverResults& +KResolverResults::operator= (const KResolverResults& other) +{ + if (this == &other) + return *this; + + // copy over the other data + *d = *other.d; + + // now let QValueList do the rest of the work + QValueList<KResolverEntry>::operator =(other); + + return *this; +} + +// gets the error code +int KResolverResults::error() const +{ + return d->errorcode; +} + +// gets the system errno +int KResolverResults::systemError() const +{ + return d->syserror; +} + +// sets the error codes +void KResolverResults::setError(int errorcode, int systemerror) +{ + d->errorcode = errorcode; + d->syserror = systemerror; +} + +// gets the hostname +QString KResolverResults::nodeName() const +{ + return d->node; +} + +// gets the service name +QString KResolverResults::serviceName() const +{ + return d->service; +} + +// sets the address +void KResolverResults::setAddress(const QString& node, + const QString& service) +{ + d->node = node; + d->service = service; +} + +void KResolverResults::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } + + +/////////////////////// +// class KResolver + +QStringList *KResolver::idnDomains = 0; + + +// default constructor +KResolver::KResolver(QObject *parent, const char *name) + : QObject(parent, name), d(new KResolverPrivate(this)) +{ +} + +// constructor with host and service +KResolver::KResolver(const QString& nodename, const QString& servicename, + QObject *parent, const char *name) + : QObject(parent, name), d(new KResolverPrivate(this, nodename, servicename)) +{ +} + +// destructor +KResolver::~KResolver() +{ + cancel(false); + delete d; +} + +// get the status +int KResolver::status() const +{ + return d->status; +} + +// get the error code +int KResolver::error() const +{ + return d->errorcode; +} + +// get the errno +int KResolver::systemError() const +{ + return d->syserror; +} + +// are we running? +bool KResolver::isRunning() const +{ + return d->status > 0 && d->status < Success; +} + +// get the hostname +QString KResolver::nodeName() const +{ + return d->input.node; +} + +// get the service +QString KResolver::serviceName() const +{ + return d->input.service; +} + +// sets the hostname +void KResolver::setNodeName(const QString& nodename) +{ + // don't touch those values if we're working! + if (!isRunning()) + { + d->input.node = nodename; + d->status = Idle; + d->results.setAddress(nodename, d->input.service); + } +} + +// sets the service +void KResolver::setServiceName(const QString& service) +{ + // don't change if running + if (!isRunning()) + { + d->input.service = service; + d->status = Idle; + d->results.setAddress(d->input.node, service); + } +} + +// sets the address +void KResolver::setAddress(const QString& nodename, const QString& service) +{ + setNodeName(nodename); + setServiceName(service); +} + +// get the flags +int KResolver::flags() const +{ + return d->input.flags; +} + +// sets the flags +int KResolver::setFlags(int flags) +{ + int oldflags = d->input.flags; + if (!isRunning()) + { + d->input.flags = flags; + d->status = Idle; + } + return oldflags; +} + +// sets the family mask +void KResolver::setFamily(int families) +{ + if (!isRunning()) + { + d->input.familyMask = families; + d->status = Idle; + } +} + +// sets the socket type +void KResolver::setSocketType(int type) +{ + if (!isRunning()) + { + d->input.socktype = type; + d->status = Idle; + } +} + +// sets the protocol +void KResolver::setProtocol(int protonum, const char *name) +{ + if (isRunning()) + return; // can't change now + + // we copy the given protocol name. If it isn't an empty string + // and the protocol number was 0, we will look it up in /etc/protocols + // we also leave the error reporting to the actual lookup routines, in + // case the given protocol name doesn't exist + + d->input.protocolName = name; + if (protonum == 0 && name != 0L && *name != '\0') + { + // must look up the protocol number + d->input.protocol = KResolver::protocolNumber(name); + } + else + d->input.protocol = protonum; + d->status = Idle; +} + +bool KResolver::start() +{ + if (!isRunning()) + { + d->results.empty(); + + // is there anything to be queued? + if (d->input.node.isEmpty() && d->input.service.isEmpty()) + { + d->status = KResolver::Success; + emitFinished(); + } + else + KResolverManager::manager()->enqueue(this, 0L); + } + + return true; +} + +bool KResolver::wait(int msec) +{ + if (!isRunning()) + { + emitFinished(); + return true; + } + + QMutexLocker locker(&d->mutex); + + if (!isRunning()) + { + // it was running and no longer is? + // That means the manager has finished its processing and has posted + // an event for the signal to be emitted already. This means the signal + // will be emitted twice! + + emitFinished(); + return true; + } + else + { + QTime t; + t.start(); + + while (!msec || t.elapsed() < msec) + { + // wait on the manager to broadcast completion + d->waiting = true; + if (msec) + KResolverManager::manager()->notifyWaiters.wait(&d->mutex, msec - t.elapsed()); + else + KResolverManager::manager()->notifyWaiters.wait(&d->mutex); + + // the manager has processed + // see if this object is done + if (!isRunning()) + { + // it's done + d->waiting = false; + emitFinished(); + return true; + } + } + + // if we've got here, we've timed out + d->waiting = false; + return false; + } +} + +void KResolver::cancel(bool emitSignal) +{ + KResolverManager::manager()->dequeue(this); + if (emitSignal) + emitFinished(); +} + +KResolverResults +KResolver::results() const +{ + if (!isRunning()) + return d->results; + + // return a dummy, empty result + KResolverResults r; + r.setAddress(d->input.node, d->input.service); + r.setError(d->errorcode, d->syserror); + return r; +} + +bool KResolver::event(QEvent* e) +{ + if (static_cast<int>(e->type()) == KResolverManager::ResolutionCompleted) + { + emitFinished(); + return true; + } + + return false; +} + +void KResolver::emitFinished() +{ + if (isRunning()) + d->status = KResolver::Success; + + QGuardedPtr<QObject> p = this; // guard against deletion + + emit finished(d->results); + + if (p && d->deleteWhenDone) + deleteLater(); // in QObject +} + +QString KResolver::errorString(int errorcode, int syserror) +{ + // no i18n now... + static const char * const messages[] = + { + I18N_NOOP("no error"), // NoError + I18N_NOOP("requested family not supported for this host name"), // AddrFamily + I18N_NOOP("temporary failure in name resolution"), // TryAgain + I18N_NOOP("non-recoverable failure in name resolution"), // NonRecoverable + I18N_NOOP("invalid flags"), // BadFlags + I18N_NOOP("memory allocation failure"), // Memory + I18N_NOOP("name or service not known"), // NoName + I18N_NOOP("requested family not supported"), // UnsupportedFamily + I18N_NOOP("requested service not supported for this socket type"), // UnsupportedService + I18N_NOOP("requested socket type not supported"), // UnsupportedSocketType + I18N_NOOP("unknown error"), // UnknownError + I18N_NOOP2("1: the i18n'ed system error code, from errno", + "system error: %1") // SystemError + }; + + // handle the special value + if (errorcode == Canceled) + return i18n("request was canceled"); + + if (errorcode > 0 || errorcode < SystemError) + return QString::null; + + QString msg = i18n(messages[-errorcode]); + if (errorcode == SystemError) + msg.arg(QString::fromLocal8Bit(strerror(syserror))); + + return msg; +} + +KResolverResults +KResolver::resolve(const QString& host, const QString& service, int flags, + int families) +{ + KResolver qres(host, service, qApp, "synchronous KResolver"); + qres.setFlags(flags); + qres.setFamily(families); + qres.start(); + qres.wait(); + return qres.results(); +} + +bool KResolver::resolveAsync(QObject* userObj, const char *userSlot, + const QString& host, const QString& service, + int flags, int families) +{ + KResolver* qres = new KResolver(host, service, qApp, "asynchronous KResolver"); + QObject::connect(qres, SIGNAL(finished(KResolverResults)), userObj, userSlot); + qres->setFlags(flags); + qres->setFamily(families); + qres->d->deleteWhenDone = true; // this is the only difference from the example code + return qres->start(); +} + +QStrList KResolver::protocolName(int protonum) +{ + struct protoent *pe = 0L; +#ifndef HAVE_GETPROTOBYNAME_R + QMutexLocker locker(&getXXbyYYmutex); + + pe = getprotobynumber(protonum); + +#else + size_t buflen = 1024; + struct protoent protobuf; + char *buf; + do + { + buf = new char[buflen]; +# ifdef USE_SOLARIS // Solaris uses a 4 argument getprotobynumber_r which returns struct *protoent or NULL + if ((pe = getprotobynumber_r(protonum, &protobuf, buf, buflen)) && (errno == ERANGE)) +# else + if (getprotobynumber_r(protonum, &protobuf, buf, buflen, &pe) == ERANGE) +# endif + { + pe = 0L; + buflen += 1024; + delete [] buf; + } + else + break; + } + while (pe == 0L); +#endif + + // Do common processing + QStrList lst(true); // use deep copies + if (pe != NULL) + { + lst.append(pe->p_name); + for (char **p = pe->p_aliases; *p; p++) + lst.append(*p); + } + +#ifdef HAVE_GETPROTOBYNAME_R + delete [] buf; +#endif + + return lst; +} + +QStrList KResolver::protocolName(const char *protoname) +{ + struct protoent *pe = 0L; +#ifndef HAVE_GETPROTOBYNAME_R + QMutexLocker locker(&getXXbyYYmutex); + + pe = getprotobyname(protoname); + +#else + size_t buflen = 1024; + struct protoent protobuf; + char *buf; + do + { + buf = new char[buflen]; +# ifdef USE_SOLARIS // Solaris uses a 4 argument getprotobyname_r which returns struct *protoent or NULL + if ((pe = getprotobyname_r(protoname, &protobuf, buf, buflen)) && (errno == ERANGE)) +# else + if (getprotobyname_r(protoname, &protobuf, buf, buflen, &pe) == ERANGE) +# endif + { + pe = 0L; + buflen += 1024; + delete [] buf; + } + else + break; + } + while (pe == 0L); +#endif + + // Do common processing + QStrList lst(true); // use deep copies + if (pe != NULL) + { + lst.append(pe->p_name); + for (char **p = pe->p_aliases; *p; p++) + lst.append(*p); + } + +#ifdef HAVE_GETPROTOBYNAME_R + delete [] buf; +#endif + + return lst; +} + +int KResolver::protocolNumber(const char *protoname) +{ + struct protoent *pe = 0L; +#ifndef HAVE_GETPROTOBYNAME_R + QMutexLocker locker(&getXXbyYYmutex); + + pe = getprotobyname(protoname); + +#else + size_t buflen = 1024; + struct protoent protobuf; + char *buf; + do + { + buf = new char[buflen]; +# ifdef USE_SOLARIS // Solaris uses a 4 argument getprotobyname_r which returns struct *protoent or NULL + if ((pe = getprotobyname_r(protoname, &protobuf, buf, buflen)) && (errno == ERANGE)) +# else + if (getprotobyname_r(protoname, &protobuf, buf, buflen, &pe) == ERANGE) +# endif + { + pe = 0L; + buflen += 1024; + delete [] buf; + } + else + break; + } + while (pe == 0L); +#endif + + // Do common processing + int protonum = -1; + if (pe != NULL) + protonum = pe->p_proto; + +#ifdef HAVE_GETPROTOBYNAME_R + delete [] buf; +#endif + + return protonum; +} + +int KResolver::servicePort(const char *servname, const char *protoname) +{ + struct servent *se = 0L; +#ifndef HAVE_GETSERVBYNAME_R + QMutexLocker locker(&getXXbyYYmutex); + + se = getservbyname(servname, protoname); + +#else + size_t buflen = 1024; + struct servent servbuf; + char *buf; + do + { + buf = new char[buflen]; +# ifdef USE_SOLARIS // Solaris uses a 5 argument getservbyname_r which returns struct *servent or NULL + if ((se = getservbyname_r(servname, protoname, &servbuf, buf, buflen)) && (errno == ERANGE)) +# else + if (getservbyname_r(servname, protoname, &servbuf, buf, buflen, &se) == ERANGE) +# endif + { + se = 0L; + buflen += 1024; + delete [] buf; + } + else + break; + } + while (se == 0L); +#endif + + // Do common processing + int servport = -1; + if (se != NULL) + servport = ntohs(se->s_port); + +#ifdef HAVE_GETSERVBYNAME_R + delete [] buf; +#endif + + return servport; +} + +QStrList KResolver::serviceName(const char* servname, const char *protoname) +{ + struct servent *se = 0L; +#ifndef HAVE_GETSERVBYNAME_R + QMutexLocker locker(&getXXbyYYmutex); + + se = getservbyname(servname, protoname); + +#else + size_t buflen = 1024; + struct servent servbuf; + char *buf; + do + { + buf = new char[buflen]; +# ifdef USE_SOLARIS // Solaris uses a 5 argument getservbyname_r which returns struct *servent or NULL + if ((se = getservbyname_r(servname, protoname, &servbuf, buf, buflen)) && (errno == ERANGE)) +# else + if (getservbyname_r(servname, protoname, &servbuf, buf, buflen, &se) == ERANGE) +# endif + { + se = 0L; + buflen += 1024; + delete [] buf; + } + else + break; + } + while (se == 0L); +#endif + + // Do common processing + QStrList lst(true); // use deep copies + if (se != NULL) + { + lst.append(se->s_name); + for (char **p = se->s_aliases; *p; p++) + lst.append(*p); + } + +#ifdef HAVE_GETSERVBYNAME_R + delete [] buf; +#endif + + return lst; +} + +QStrList KResolver::serviceName(int port, const char *protoname) +{ + struct servent *se = 0L; +#ifndef HAVE_GETSERVBYPORT_R + QMutexLocker locker(&getXXbyYYmutex); + + se = getservbyport(port, protoname); + +#else + size_t buflen = 1024; + struct servent servbuf; + char *buf; + do + { + buf = new char[buflen]; +# ifdef USE_SOLARIS // Solaris uses a 5 argument getservbyport_r which returns struct *servent or NULL + if ((se = getservbyport_r(port, protoname, &servbuf, buf, buflen)) && (errno == ERANGE)) +# else + if (getservbyport_r(port, protoname, &servbuf, buf, buflen, &se) == ERANGE) +# endif + { + se = 0L; + buflen += 1024; + delete [] buf; + } + else + break; + } + while (se == 0L); +#endif + + // Do common processing + QStrList lst(true); // use deep copies + if (se != NULL) + { + lst.append(se->s_name); + for (char **p = se->s_aliases; *p; p++) + lst.append(*p); + } + +#ifdef HAVE_GETSERVBYPORT_R + delete [] buf; +#endif + + return lst; +} + +QString KResolver::localHostName() +{ + QCString name; + int len; + +#ifdef MAXHOSTNAMELEN + len = MAXHOSTNAMELEN; +#else + len = 256; +#endif + + while (true) + { + name.resize(len); + + if (gethostname(name.data(), len - 1) == 0) + { + // Call succeeded, but it's not guaranteed to be NUL-terminated + // Note that some systems return success even if they did truncation + name[len - 1] = '\0'; + break; + } + + // Call failed + if (errno == ENAMETOOLONG || errno == EINVAL) + len += 256; + else + { + // Oops! Unknown error! + name = QCString(); + } + } + + if (name.isEmpty()) + return QString::fromLatin1("localhost"); + + if (name.find('.') == -1) + { + // not fully qualified + // must resolve + KResolverResults results = resolve(name, "0", CanonName); + if (results.isEmpty()) + // cannot find a valid hostname! + return QString::fromLatin1("localhost"); + else + return results.first().canonicalName(); + } + + return domainToUnicode(name); +} + + +// forward declaration +static QStringList splitLabels(const QString& unicodeDomain); +static QCString ToASCII(const QString& label); +static QString ToUnicode(const QString& label); + +static QStringList *KResolver_initIdnDomains() +{ + const char *kde_use_idn = getenv("KDE_USE_IDN"); + if (!kde_use_idn) + kde_use_idn = "ac:at:br:cat:ch:cl:cn:de:dk:fi:gr:hu:info:io:is:jp:kr:li:lt:museum:org:no:se:sh:th:tm:tw:vn"; + return new QStringList(QStringList::split(':', QString::fromLatin1(kde_use_idn).lower())); +} + +// implement the ToAscii function, as described by IDN documents +QCString KResolver::domainToAscii(const QString& unicodeDomain) +{ + if (!idnDomains) + idnDomains = KResolver_initIdnDomains(); + + QCString retval; + // RFC 3490, section 4 describes the operation: + // 1) this is a query, so don't allow unassigned + + // 2) split the domain into individual labels, without + // separators. + QStringList input = splitLabels(unicodeDomain); + + // Do we allow IDN names for this TLD? + if (input.count() && !idnDomains->contains(input[input.count()-1].lower())) + return input.join(".").lower().latin1(); // No IDN allowed for this TLD + + // 3) decide whether to enforce the STD3 rules for chars < 0x7F + // we don't enforce + + // 4) for each label, apply ToASCII + QStringList::Iterator it = input.begin(); + const QStringList::Iterator end = input.end(); + for ( ; it != end; ++it) + { + QCString cs = ToASCII(*it); + if (cs.isNull()) + return QCString(); // error! + + // no, all is Ok. + if (!retval.isEmpty()) + retval += '.'; + retval += cs; + } + + return retval; +} + +QString KResolver::domainToUnicode(const QCString& asciiDomain) +{ + return domainToUnicode(QString::fromLatin1(asciiDomain)); +} + +// implement the ToUnicode function, as described by IDN documents +QString KResolver::domainToUnicode(const QString& asciiDomain) +{ + if (asciiDomain.isEmpty()) + return asciiDomain; + if (!idnDomains) + idnDomains = KResolver_initIdnDomains(); + + QString retval; + + // draft-idn-idna-14.txt, section 4 describes the operation: + // 1) this is a query, so don't allow unassigned + // besides, input is ASCII + + // 2) split the domain into individual labels, without + // separators. + QStringList input = splitLabels(asciiDomain); + + // Do we allow IDN names for this TLD? + if (input.count() && !idnDomains->contains(input[input.count()-1].lower())) + return asciiDomain.lower(); // No TLDs allowed + + // 3) decide whether to enforce the STD3 rules for chars < 0x7F + // we don't enforce + + // 4) for each label, apply ToUnicode + QStringList::Iterator it; + const QStringList::Iterator end = input.end(); + for (it = input.begin(); it != end; ++it) + { + QString label = ToUnicode(*it).lower(); + + // ToUnicode can't fail + if (!retval.isEmpty()) + retval += '.'; + retval += label; + } + + return retval; +} + +QString KResolver::normalizeDomain(const QString& domain) +{ + return domainToUnicode(domainToAscii(domain)); +} + +void KResolver::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } + +// here follows IDN functions +// all IDN functions conform to the following documents: +// RFC 3454 - Preparation of Internationalized Strings +// RFC 3490 - Internationalizing Domain Names in Applications (IDNA) +// RFC 3491 - Nameprep: A Stringprep Profile for +// Internationalized Domain Names (IDN +// RFC 3492 - Punycode: A Bootstring encoding of Unicode +// for Internationalized Domain Names in Applications (IDNA) + +static QStringList splitLabels(const QString& unicodeDomain) +{ + // From RFC 3490 section 3.1: + // "Whenever dots are used as label separators, the following characters + // MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full + // stop), U+FF0E (fullwidth full stop), U+FF61 (halfwidth ideographic full + // stop)." + static const unsigned int separators[] = { 0x002E, 0x3002, 0xFF0E, 0xFF61 }; + + QStringList lst; + int start = 0; + uint i; + for (i = 0; i < unicodeDomain.length(); i++) + { + unsigned int c = unicodeDomain[i].unicode(); + + if (c == separators[0] || + c == separators[1] || + c == separators[2] || + c == separators[3]) + { + // found a separator! + lst << unicodeDomain.mid(start, i - start); + start = i + 1; + } + } + if ((long)i >= start) + // there is still one left + lst << unicodeDomain.mid(start, i - start); + + return lst; +} + +static QCString ToASCII(const QString& label) +{ +#ifdef HAVE_IDNA_H + // We have idna.h, so we can use the idna_to_ascii + // function :) + + if (label.length() > 64) + return (char*)0L; // invalid label + + if (label.length() == 0) + // this is allowed + return QCString(""); // empty, not null + + QCString retval; + char buf[65]; + + Q_UINT32* ucs4 = new Q_UINT32[label.length() + 1]; + + uint i; + for (i = 0; i < label.length(); i++) + ucs4[i] = (unsigned long)label[i].unicode(); + ucs4[i] = 0; // terminate with NUL, just to be on the safe side + + if (idna_to_ascii_4i(ucs4, label.length(), buf, 0) == IDNA_SUCCESS) + // success! + retval = buf; + + delete [] ucs4; + return retval; +#else + return label.latin1(); +#endif +} + +static QString ToUnicode(const QString& label) +{ +#ifdef HAVE_IDNA_H + // We have idna.h, so we can use the idna_to_unicode + // function :) + + Q_UINT32 *ucs4_input, *ucs4_output; + size_t outlen; + + ucs4_input = new Q_UINT32[label.length() + 1]; + for (uint i = 0; i < label.length(); i++) + ucs4_input[i] = (unsigned long)label[i].unicode(); + + // try the same length for output + ucs4_output = new Q_UINT32[outlen = label.length()]; + + idna_to_unicode_44i(ucs4_input, label.length(), + ucs4_output, &outlen, + 0); + + if (outlen > label.length()) + { + // it must have failed + delete [] ucs4_output; + ucs4_output = new Q_UINT32[outlen]; + + idna_to_unicode_44i(ucs4_input, label.length(), + ucs4_output, &outlen, + 0); + } + + // now set the answer + QString result; + result.setLength(outlen); + for (uint i = 0; i < outlen; i++) + result[i] = (unsigned int)ucs4_output[i]; + + delete [] ucs4_input; + delete [] ucs4_output; + + return result; +#else + return label; +#endif +} + +#include "kresolver.moc" diff --git a/kdecore/network/kresolver.h b/kdecore/network/kresolver.h new file mode 100644 index 000000000..b4e4334be --- /dev/null +++ b/kdecore/network/kresolver.h @@ -0,0 +1,945 @@ +/* -*- mode: C++; coding: utf-8; -*- + * Copyright (C) 2003,2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KRESOLVER_H +#define KRESOLVER_H + +////////////////// +// Needed includes +#include <qvaluelist.h> +#include <qobject.h> +#include "ksocketaddress.h" + + +//////////////////////// +// Forward declarations +struct sockaddr; +class QString; +class QCString; +class QStrList; + +////////////////// +// Our definitions + +namespace KNetwork { + + namespace Internal { class KResolverManager; } + +class KResolverEntryPrivate; +/** @class KResolverEntry kresolver.h kresolver.h + * @brief One resolution entry. + * + * This class is one element in the resolution results list. + * It contains the socket address for connecting, as well as + * a bit more of information: the socket type, address family + * and protocol numbers. + * + * This class contains all the information required for creating, + * binding and connecting a socket. + * + * KResolverEntry objects implicitly share data, so copying them + * is quite efficient. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KResolverEntry +{ +public: + /** + * Default constructor + * + */ + KResolverEntry(); + + /** + * Constructs a new KResolverEntry from a KSocketAddress + * and other data. + * + * The KSocketAddress @p addr parameter will be deep-copied. + * + * @param addr the address that was resolved + * @param socktype the socket type of the resolved address + * @param protocol the protocol of the resolved address + * @param canonName the canonical name of the resolved hostname + * @param encodedName the ASCII-compatible encoding of the hostname + */ + KResolverEntry(const KSocketAddress& addr, int socktype, int protocol, + const QString& canonName = QString::null, + const QCString& encodedName = QCString()); + + /** + * Constructs a new KResolverEntry from raw forms of + * socket addresses and other data. + * + * This constructor instead creates an internal KSocketAddress object. + * + * @param sa the sockaddr structure containing the raw address + * @param salen the length of the sockaddr structure + * @param socktype the socket type of the resolved address + * @param protocol the protocol of the resolved address + * @param canonName the canonical name of the resolved hostname + * @param encodedName the ASCII-compatible encoding of the hostname + */ + KResolverEntry(const struct sockaddr *sa, Q_UINT16 salen, int socktype, + int protocol, const QString& canonName = QString::null, + const QCString& encodedName = QCString()); + + /** + * Copy constructor. + * + * This constructor performs a shallow-copy of the other object. + */ + KResolverEntry(const KResolverEntry &other); + + /** + * Destructor. + * + * The destructor frees associated resources with this object. It does + * not destroy shared data. + */ + ~KResolverEntry(); + + /** + * Retrieves the socket address associated with this entry. + */ + KSocketAddress address() const; + + /** + * Retrieves the length of the socket address structure. + */ + Q_UINT16 length() const; + + /** + * Retrieves the family associated with this socket address. + */ + int family() const; + + /** + * Retrieves the canonical name associated with this entry, if there is any. + * If the canonical name was not found, this function returns QString::null. + */ + QString canonicalName() const; + + /** + * Retrieves the encoded domain name associated with this entry, if there is + * any. If this domain has been resolved through DNS, this will be the + * the ACE-encoded hostname. + * + * Returns a null QCString if such information is not available. + * + * Please note that this information is NOT to be presented to the user, + * unless requested. + */ + QCString encodedName() const; + + /** + * Retrieves the socket type associated with this entry. + */ + int socketType() const; + + /** + * Retrieves the protocol associated with this entry. + */ + int protocol() const; + + /** + * Assignment operator + * + * This function copies the contents of the other object into this one. + * Data will be shared between the two of them. + */ + KResolverEntry& operator=(const KResolverEntry& other); + +private: + KResolverEntryPrivate* d; +}; + +class KResolverResultsPrivate; +/** + * @class KResolverResults kresolver.h kresolver.h + * @brief Name and service resolution results. + * + * This object contains the results of a name and service resolution, as + * those performed by @ref KResolver. It is also a descendant of QValueList, so + * you may use all its member functions here to access the elements. + * + * A KResolverResults object is associated with a resolution, so, in addition + * to the resolved elements, you can also retrieve information about the + * resolution process itself, like the nodename that was resolved or an error + * code. + * + * Note Resolver also uses KResolverResults objects to indicate failure, so + * you should test for failure. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KResolverResults: public QValueList<KResolverEntry> +{ +public: + /** + * Default constructor. + * + * Constructs an empty list. + */ + KResolverResults(); + + /** + * Copy constructor + * + * Creates a new object with the contents of the other one. Data will be + * shared by the two objects, like QValueList + */ + KResolverResults(const KResolverResults& other); + + /** + * Destructor + * + * Destroys the object and frees associated resources. + */ + virtual ~KResolverResults(); + + /** + * Assignment operator + * + * Copies the contents of the other container into this one, discarding + * our current values. + */ + KResolverResults& operator=(const KResolverResults& other); + + /** + * Retrieves the error code associated with this resolution. The values + * here are the same as in @ref KResolver::ErrorCodes. + */ + int error() const; + + /** + * Retrieves the system error code, if any. + * @see KResolver::systemError for more information + */ + int systemError() const; + + /** + * Sets the error codes + * + * @param errorcode the error code in @ref KResolver::ErrorCodes + * @param systemerror the system error code associated, if any + */ + void setError(int errorcode, int systemerror = 0); + + /** + * The nodename to which the resolution was performed. + */ + QString nodeName() const; + + /** + * The service name to which the resolution was performed. + */ + QString serviceName() const; + + /** + * Sets the new nodename and service name + */ + void setAddress(const QString& host, const QString& service); + +protected: + virtual void virtual_hook( int id, void* data ); +private: + KResolverResultsPrivate* d; +}; + +class KResolverPrivate; +/** + * @class KResolver kresolver.h kresolver.h + * @brief Name and service resolution class. + * + * This class provides support for doing name-to-binary resolution + * for nodenames and service ports. You should use this class if you + * need specific resolution techniques when creating a socket or if you + * want to inspect the results before calling the socket functions. + * + * You can either create an object and set the options you want in it + * or you can simply call the static member functions, which will create + * standard Resolver objects and dispatch the resolution for you. Normally, + * the static functions will be used, except in cases where specific options + * must be set. + * + * A Resolver object defaults to the following: + * @li address family: any address family + * @li socket type: streaming socket + * @li protocol: implementation-defined. Generally, TCP + * @li host and service: unset + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KResolver: public QObject +{ + Q_OBJECT + +public: + + /** + * Address family selection types + * + * These values can be OR-ed together to form a composite family selection. + * + * @li UnknownFamily: a family that is unknown to the current implementation + * @li KnownFamily: a family that is known to the implementation (the exact + * opposite of UnknownFamily) + * @li AnyFamilies: any address family is acceptable + * @li InternetFamily: an address for connecting to the Internet + * @li InetFamily: alias for InternetFamily + * @li IPv6Family: an IPv6 address only + * @li IPv4Family: an IPv4 address only + * @li UnixFamily: an address for the local Unix namespace (i.e., Unix sockets) + * @li LocalFamily: alias for UnixFamily + */ + enum SocketFamilies + { + UnknownFamily = 0x0001, + + UnixFamily = 0x0002, + LocalFamily = UnixFamily, + + IPv4Family = 0x0004, + IPv6Family = 0x0008, + InternetFamily = IPv4Family | IPv6Family, + InetFamily = InternetFamily, + + KnownFamily = ~UnknownFamily, + AnyFamily = KnownFamily | UnknownFamily + }; + + /** + * Flags for the resolution. + * + * These flags are used for setting the resolution behaviour for this + * object: + * @li Passive: resolve to a passive socket (i.e., one that can be used for + * binding to a local interface) + * @li CanonName: request that the canonical name for the given nodename + * be found and recorded + * @li NoResolve: request that no external resolution be performed. The given + * nodename and servicename will be resolved locally only. + * @li NoSrv: don't try to use SRV-based name-resolution. (deprecated) + * @li UseSrv: use SRV-based name resolution. + * @li Multiport: the port/service argument is a list of port numbers and + * ranges. (future extension) + * + * @note SRV-based lookup and Multiport are not implemented yet. + */ + enum Flags + { + Passive = 0x01, + CanonName = 0x02, + NoResolve = 0x04, + NoSrv = 0x08, + Multiport = 0x10, + UseSrv = 0x20 + }; + + /** + * Error codes + * + * These are the possible error values that objects of this class + * may return. See \ref errorString() for getting a string representation + * for these errors. + * + * @li AddrFamily: Address family for the given nodename is not supported. + * @li TryAgain: Temporary failure in name resolution. You should try again. + * @li NonRecoverable: Non-recoverable failure in name resolution. + * @li BadFlags: Invalid flags were given. + * @li Memory: Memory allocation failure. + * @li NoName: The specified name or service doesn't exist. + * @li UnsupportedFamily: The requested socket family is not supported. + * @li UnsupportedService: The requested service is not supported for this + * socket type (i.e., a datagram service in a streaming socket). + * @li UnsupportedSocketType: The requested socket type is not supported. + * @li UnknownError: An unknown, unexpected error occurred. + * @li SystemError: A system error occurred. See @ref systemError. + * @li Canceled: This request was cancelled by the user. + */ + enum ErrorCodes + { + // note: if you change this enum, take a look at KResolver::errorString + NoError = 0, + AddrFamily = -1, + TryAgain = -2, + NonRecoverable = -3, + BadFlags = -4, + Memory = -5, + NoName = -6, + UnsupportedFamily = -7, + UnsupportedService = -8, + UnsupportedSocketType = -9, + UnknownError = -10, + SystemError = -11, + Canceled = -100 + }; + + /** + * Status codes. + * + * These are the possible status for a Resolver object. A value + * greater than zero indicates normal behaviour, while negative + * values either indicate failure or error. + * + * @li Idle: resolution has not yet been started. + * @li Queued: resolution is queued but not yet in progress. + * @li InProgress: resolution is in progress. + * @li PostProcessing: resolution is in progress. + * @li Success: resolution is done; you can retrieve the results. + * @li Canceled: request cancelled by the user. + * @li Failed: resolution is done, but failed. + * + * Note: the status Canceled and the error code Canceled are the same. + * + * Note 2: the status Queued and InProgress might not be distinguishable. + * Some implementations might not differentiate one from the other. + */ + enum StatusCodes + { + Idle = 0, + Queued = 1, + InProgress = 5, + PostProcessing = 6, + Success = 10, + //Canceled = -100, // already defined above + Failed = -101 + }; + + /** + * Default constructor. + * + * Creates an empty Resolver object. You should set the wanted + * names and flags using the member functions before starting + * the name resolution. + */ + KResolver(QObject * = 0L, const char * = 0L); + + /** + * Constructor with host and service names. + * + * Creates a Resolver object with the given host and + * service names. Flags are initialised to 0 and any address family + * will be accepted. + * + * @param nodename The host name we want resolved. + * @param servicename The service name associated, like "http". + */ + KResolver(const QString& nodename, const QString& servicename = QString::null, + QObject * = 0L, const char * = 0L); + + /** + * Destructor. + * + * When this object is deleted, it'll destroy all associated + * resources. If the resolution is still in progress, it will be + * cancelled and the signal will \b not be emitted. + */ + virtual ~KResolver(); + + /** + * Retrieve the current status of this object. + * + * @see StatusCodes for the possible status codes. + */ + int status() const; + + /** + * Retrieve the error code in this object. + * + * This function will return NoError if we are not in + * an error condition. See @ref status and @ref StatusCodes to + * find out what the current status is. + * + * @see errorString for getting a textual representation of + * this error + */ + int error() const; + + /** + * Retrieve the associated system error code in this object. + * + * Many resolution operations may generate an extra error code + * as given by the C errno variable. That value is stored in the + * object and can be retrieved by this function. + */ + int systemError() const; + + /** + * Returns the textual representation of the error in this object. + */ + inline QString errorString() const + { return errorString(error(), systemError()); } + + /** + * Returns true if this object is currently running + */ + bool isRunning() const; + + /** + * The nodename to which the resolution was/is to be performed. + */ + QString nodeName() const; + + /** + * The service name to which the resolution was/is to be performed. + */ + QString serviceName() const; + + /** + * Sets the nodename for the resolution. + * + * Set the nodename to QString::null to unset it. + * @param nodename The nodename to be resolved. + */ + void setNodeName(const QString& nodename); + + /** + * Sets the service name to be resolved. + * + * Set it to QString::null to unset it. + * @param service The service to be resolved. + */ + void setServiceName(const QString& service); + + /** + * Sets both the host and the service names. + * + * Setting either value to QString::null will unset them. + * @param node The nodename + * @param service The service name + */ + void setAddress(const QString& node, const QString& service); + + /** + * Retrieves the flags set for the resolution. + * + * @see Flags for an explanation on what flags are possible + */ + int flags() const; + + /** + * Sets the flags. + * + * @param flags the new flags + * @return the old flags + * @see Flags for an explanation on the flags + */ + int setFlags(int flags); + + /** + * Sets the allowed socket families. + * + * @param families the families that we want/accept + * @see SocketFamilies for possible values + */ + void setFamily(int families); + + /** + * Sets the socket type we want. + * + * The values for the @p type parameter are the SOCK_* + * constants, defined in <sys/socket.h>. The most common + * values are: + * @li SOCK_STREAM streaming socket (= reliable, sequenced, + * connection-based) + * @li SOCK_DGRAM datagram socket (= unreliable, connectionless) + * @li SOCK_RAW raw socket, with direct access to the + * container protocol (such as IP) + * + * These three are the only values to which it is guaranteed that + * resolution will work. Some systems may define other constants (such as + * SOCK_RDM for reliable datagrams), but support is implementation-defined. + * + * @param type the wanted socket type (SOCK_* constants). Set + * 0 to use the default. + */ + void setSocketType(int type); + + /** + * Sets the protocol we want. + * + * Protocols are dependant on the selected address family, so you should know + * what you are doing if you use this function. Besides, protocols generally + * are either stream-based or datagram-based, so the value of the socket + * type is also important. The resolution will fail if these values don't match. + * + * When using an Internet socket, the values for the protocol are the + * IPPROTO_* constants, defined in <netinet/in.h>. + * + * You may choose to set the protocol either by its number or by its name, or + * by both. If you set: + * @li the number and the name: both values will be stored internally; you + * may set the name to an empty value, if wanted + * @li the number only (name = NULL): the name will be searched in the + * protocols database + * @li the name only (number = 0): the number will be searched in the + * database + * @li neither name nor number: reset to default behaviour + * + * @param protonum the protocol number we want + * @param name the protocol name + */ + void setProtocol(int protonum, const char *name = 0L); + + /** + * Starts the name resolution asynchronously. + * + * This function will queue this object for resolution + * and will return immediately. The status upon exit will either be + * Queued or InProgress or Failed. + * + * This function does nothing if the object is already queued. But if + * it had already succeeded or failed, this function will re-start it. + * + * Note: if both the nodename and the servicename are unset, this function + * will not queue, but will set a success state and emit the signal. Also + * note that in this case and maybe others, the signal @ref finished might + * be emitted before this function returns. + * + * @return true if this request was successfully queued for asynchronous + * resolution + */ + bool start(); + + /** + * Waits for a request to finish resolving. + * + * This function will wait on a running request for its termination. The + * status upon exit will either be Success or Failed or Canceled. + * + * This function may be called from any thread, even one that is not the + * GUI thread or the one that started the resolution process. But note this + * function is not thread-safe nor reentrant: i.e., only one thread can be + * waiting on one given object. + * + * Also note that this function ensures that the @ref finished signal is + * emitted before it returns. That means that, as a side-effect, whenever + * wait() is called, the signal is emitted on the thread calling wait(). + * + * @param msec the time to wait, in milliseconds or 0 to + * wait forever + * @return true if the resolution has finished processing, even when it + * failed or was canceled. False means the wait timed out and + * the resolution is still running. + */ + bool wait(int msec = 0); + + /** + * Cancels a running request + * + * This function will cancel a running request. If the request is not + * currently running or queued, this function does nothing. + * + * Note: if you tell the signal to be emitted, be aware that it might + * or might not be emitted before this function returns. + * + * @param emitSignal whether to emit the @ref finished signal or not + */ + void cancel(bool emitSignal = true); + + /** + * Retrieves the results of this resolution + * + * Use this function to retrieve the results of the resolution. If no + * data was resolved (yet) or if we failed, this function will return + * an empty object. + * + * @return the resolved data + * @see status for information on finding out if the resolution was successful + */ + KResolverResults results() const; + + /** + * Handles events. Reimplemented from QObject. + * + * This function handles the events generated by the manager indicating that + * this object has finished processing. + * + * Do not post events to this object. + */ + virtual bool event(QEvent*); + +signals: + // signals + + /** + * This signal is emitted whenever the resolution is finished, one + * way or another (success or failure). The @p results parameter + * will contain the resolved data. + * + * Note: if you are doing multiple resolutions, you can use the + * QObject::sender() function to distinguish one Resolver object from + * another. + * + * @param results the resolved data; might be empty if the resolution + * failed + * @see results for information on what the results are + * + * @note This signal is @b always delivered in the GUI event thread, even for + * resolutions that were started in secondary threads. + */ + void finished(KResolverResults results); + +private: + void emitFinished(); + +public: + // Static functions + + /** + * Returns the string representation of this error code. + * + * @param errorcode the error code. See @ref ErrorCodes. + * @param syserror the system error code associated. + * @return the string representation. This is already + * i18n'ed. + */ + static QString errorString(int errorcode, int syserror = 0); + + /** + * Resolve the nodename and service name synchronously + * + * This static function is provided as convenience for simplifying + * name resolution. It resolves the given host and service names synchronously + * and returns the results it found. It is equivalent to the following code: + * + * \code + * KResolver qres(host, service); + * qres.setFlags(flags); + * qres.setFamily(families) + * qres.start(); + * qres.wait(); + * return qres.results(); + * \endcode + * + * @param host the nodename to resolve + * @param service the service to resolve + * @param flags flags to be used + * @param families the families to be searched + * @return a KResolverResults object containing the results + * @see KResolverResults for information on how to obtain the error code + */ + static KResolverResults resolve(const QString& host, const QString& service, + int flags = 0, int families = KResolver::InternetFamily); + + /** + * Start an asynchronous name resolution + * + * This function is provided as a convenience to simplify the resolution + * process. It creates an internal KResolver object, connects the + * @ref finished signal to the given slot and starts the resolution + * asynchronously. It is more or less equivalent to the following code: + * + * \b Note: this function may trigger the signal before it returns, so + * your code must be prepared for this situation. + * + * \code + * KResolver* qres = new KResolver(host, service); + * QObject::connect(qres, SIGNAL(finished(KResolverResults)), + * userObj, userSlot); + * qres->setFlags(flags); + * qres->setFamily(families); + * return qres->start(); + * \endcode + * + * You should use it like this in your code: + * \code + * KResolver::resolveAsync(myObj, SLOT(mySlot(KResolverResults)), host, service); + * \endcode + * + * @param userObj the object whose slot @p userSlot we will connect + * @param userSlot the slot to which we'll connect + * @param host the nodename to resolve + * @param service the service to resolve + * @param flags flags to be used + * @param families families to be searcheed + * @return true if the queueing was successful, false if not + * @see KResolverResults for information on how to obtain the error code + */ + static bool resolveAsync(QObject* userObj, const char *userSlot, + const QString& host, const QString& service, + int flags = 0, int families = KResolver::InternetFamily); + + /** + * Returns the domain name in an ASCII Compatible Encoding form, suitable + * for DNS lookups. This is the base for International Domain Name support + * over the Internet. + * + * Note this function may fail, in which case it'll return a null + * QCString. Reasons for failure include use of unknown code + * points (Unicode characters). + * + * Note that the encoding is illegible and, thus, should not be presented + * to the user, except if requested. + * + * @param unicodeDomain the domain name to be encoded + * @return the ACE-encoded suitable for DNS queries if successful, a null + * QCString if failure. + */ + static QCString domainToAscii(const QString& unicodeDomain); + + /** + * Does the inverse of @ref domainToAscii and return an Unicode domain + * name from the given ACE-encoded domain. + * + * This function may fail if the given domain cannot be successfully + * converted back to Unicode. Reasons for failure include a malformed + * domain name or good ones whose reencoding back to ACE don't match + * the form given here (e.g., ACE-encoding of an already + * ASCII-compatible domain). + * + * It is, however, guaranteed that domains returned + * by @ref domainToAscii will work. + * + * @param asciiDomain the ACE-encoded domain name to be decoded + * @return the Unicode representation of the given domain name + * if successful, the original string if not + * @note ACE = ASCII-Compatible Encoding, i.e., 7-bit + */ + static QString domainToUnicode(const QCString& asciiDomain); + + /** + * The same as above, but taking a QString argument. + * + * @param asciiDomain the ACE-encoded domain name to be decoded + * @return the Unicode representation of the given domain name + * if successful, QString::null if not. + */ + static QString domainToUnicode(const QString& asciiDomain); + + /** + * Normalise a domain name. + * + * In order to prevent simple mistakes in International Domain + * Names (IDN), it has been decided that certain code points + * (characters in Unicode) would be instead converted to others. + * This includes turning them all to lower case, as well certain + * other specific operations, as specified in the documents. + * + * For instance, the German 'ß' will be changed into 'ss', while + * the micro symbol 'µ' will be changed to the Greek mu 'μ'. + * + * Two equivalent domains have the same normalised form. And the + * normalised form of a normalised domain is itself (i.e., if + * d is normalised, the following is true: d == normalizeDomain(d) ) + * + * This operation is equivalent to encoding and the decoding a Unicode + * hostname. + * + * @param domain a domain to be normalised + * @return the normalised domain, or QString::null if the domain is + * invalid. + */ + static QString normalizeDomain(const QString& domain); + + /** + * Resolves a protocol number to its names + * + * Note: the returned QStrList operates on deep-copies. + * + * @param protonum the protocol number to be looked for + * @return all the protocol names in a list. The first is the "proper" + * name. + */ + static QStrList protocolName(int protonum); + + /** + * Finds all aliases for a given protocol name + * + * @param protoname the protocol name to be looked for + * @return all the protocol names in a list. The first is the "proper" + * name. + */ + static QStrList protocolName(const char *protoname); + + /** + * Resolves a protocol name to its number + * + * @param protoname the protocol name to be looked for + * @return the protocol number or -1 if we couldn't locate it + */ + static int protocolNumber(const char *protoname); + + /** + * Resolves a service name to its port number + * + * @param servname the service name to be looked for + * @param protoname the protocol it is associated with + * @return the port number in host byte-order or -1 in case of error + */ + static int servicePort(const char *servname, const char *protoname); + + /** + * Finds all the aliases for a given service name + * + * Note: the returned QStrList operates on deep-copies. + * + * @param servname the service alias to be looked for + * @param protoname the protocol it is associated with + * @return all the service names in a list. The first is the "proper" + * name. + */ + static QStrList serviceName(const char *servname, const char *protoname); + + /** + * Resolves a port number to its names + * + * Note: the returned QStrList operates on deep copies. + * + * @param port the port number, in host byte-order + * @param protoname the protocol it is associated with + * @return all the service names in a list. The first is the "proper" + * name. + */ + static QStrList serviceName(int port, const char *protoname); + + /** + * Returns this machine's local hostname. + * + * @return this machine's local hostname + * @since 3.5 + */ + static QString localHostName(); + +protected: + + /** + * Sets the error codes + */ + void setError(int errorcode, int systemerror = 0); + + virtual void virtual_hook( int id, void* data ); +private: + KResolverPrivate* d; + friend class KResolverResults; + friend class ::KNetwork::Internal::KResolverManager; + + static QStringList *idnDomains; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/kresolver_p.h b/kdecore/network/kresolver_p.h new file mode 100644 index 000000000..2fac7eb45 --- /dev/null +++ b/kdecore/network/kresolver_p.h @@ -0,0 +1,353 @@ +/* -*- C++ -*- + * Copyright (C) 2003-2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KRESOLVER_P_H +#define KRESOLVER_P_H + +#include <config.h> +#include <sys/types.h> + +#include <qstring.h> +#include <qcstring.h> +#include <qvaluelist.h> +#include <qptrlist.h> +#include <qptrqueue.h> +#include <qthread.h> +#include <qmutex.h> +#include <qwaitcondition.h> +#include <qsemaphore.h> +#include <qevent.h> + +#include "kresolver.h" + +/* decide whether we need a mutex */ +#if !defined(HAVE_GETPROTOBYNAME_R) || !defined(HAVE_GETSERVBYNAME_R) || !defined(HAVE_GETHOSTBYNAME_R) || !defined(HAVE_GETSERVBYPORT_R) +# define NEED_MUTEX +extern QMutex getXXbyYYmutex; +#endif + +/* some systems have the functions, but don't declare them */ +#if defined(HAVE_GETSERVBYNAME_R) && !HAVE_DECL_GETSERVBYNAME_R +extern "C" { + struct servent; + extern int getservbyname_r(const char* serv, const char* proto, + struct servent* servbuf, + char* buf, size_t buflen, + struct servent** result); + extern int getservbyport_r(int port, const char* proto, + struct servent* servbuf, + char* buf, size_t buflen, + struct servent** result); + + struct protoent; + extern int getprotobyname_r(const char* proto, struct protoent* protobuf, + char *buf, size_t buflen, + struct protoent** result); + extern int getprotobynumber_r(int proto, struct protoent* protobuf, + char *buf, size_t buflen, + struct protoent** result); +} +#endif + +/* decide whether res_init is thread-safe or not */ +#if defined(__GLIBC__) +# undef RES_INIT_THREADSAFE +#endif + +namespace KNetwork +{ + // defined in network/qresolverworkerbase.h + class KResolverWorkerBase; + class KResolverWorkerFactoryBase; + class KResolverPrivate; + + namespace Internal + { + class KResolverManager; + class KResolverThread; + struct RequestData; + + struct InputData + { + QString node, service; + QCString protocolName; + int flags; + int familyMask; + int socktype; + int protocol; + }; + } + + class KResolverPrivate + { + public: + // parent class. Should never be changed! + KResolver* parent; + bool deleteWhenDone : 1; + bool waiting : 1; + + // class status. Should not be changed by worker threads! + volatile int status; + volatile int errorcode, syserror; + + // input data. Should not be changed by worker threads! + Internal::InputData input; + + // mutex + QMutex mutex; + + // output data + KResolverResults results; + + KResolverPrivate(KResolver* _parent, + const QString& _node = QString::null, + const QString& _service = QString::null) + : parent(_parent), deleteWhenDone(false), waiting(false), + status(0), errorcode(0), syserror(0) + { + input.node = _node; + input.service = _service; + input.flags = 0; + input.familyMask = KResolver::AnyFamily; + input.socktype = 0; + input.protocol = 0; + + results.setAddress(_node, _service); + } + }; + + namespace Internal + { + struct RequestData + { + // worker threads should not change values in the input data + KNetwork::KResolverPrivate *obj; + const KNetwork::Internal::InputData *input; + KNetwork::KResolverWorkerBase *worker; // worker class + RequestData *requestor; // class that requested us + + volatile int nRequests; // how many requests that we made we still have left + }; + + /* + * @internal + * This class is the resolver manager + */ + class KResolverManager + { + public: + enum EventTypes + { ResolutionCompleted = 1576 }; // arbitrary value; + + /* + * This wait condition is used to notify wait states (KResolver::wait) that + * the resolver manager has finished processing one or more objects. All + * objects in wait state will be woken up and will check if they are done. + * If they aren't, they will go back to sleeping. + */ + QWaitCondition notifyWaiters; + + private: + /* + * This variable is used to count the number of threads that are running + */ + volatile unsigned short runningThreads; + + /* + * This variable is used to count the number of threads that are currently + * waiting for data. + */ + unsigned short availableThreads; + + /* + * This wait condition is used to notify worker threads that there is new + * data available that has to be processed. All worker threads wait on this + * waitcond for a limited amount of time. + */ + QWaitCondition feedWorkers; + + // this mutex protects the data in this object + QMutex mutex; + + // hold a list of all the current threads we have + QPtrList<KResolverThread> workers; + + // hold a list of all the new requests we have + QPtrList<RequestData> newRequests; + + // hold a list of all the requests in progress we have + QPtrList<RequestData> currentRequests; + + // hold a list of all the workers we have + QPtrList<KNetwork::KResolverWorkerFactoryBase> workerFactories; + + // private constructor + KResolverManager(); + + public: + static KResolverManager* manager() KDE_NO_EXPORT; // creates and returns the global manager + + // destructor + ~KResolverManager(); + + /* + * Register this thread in the pool + */ + void registerThread(KResolverThread* id); + + /* + * Unregister this thread from the pool + */ + void unregisterThread(KResolverThread* id); + + /* + * Requests new data to work on. + * + * This function should only be called from a worker thread. This function + * is thread-safe. + * + * If there is data to be worked on, this function will return it. If there is + * none, this function will return a null pointer. + */ + RequestData* requestData(KResolverThread* id, int maxWaitTime); + + /* + * Releases the resources and returns the resolved data. + * + * This function should only be called from a worker thread. It is + * thread-safe. It does not post the event to the manager. + */ + void releaseData(KResolverThread *id, RequestData* data); + + /* + * Registers a new worker class by way of its factory. + * + * This function is NOT thread-safe. + */ + void registerNewWorker(KNetwork::KResolverWorkerFactoryBase *factory); + + /* + * Enqueues new resolutions. + */ + void enqueue(KNetwork::KResolver *obj, RequestData* requestor); + + /* + * Dispatch a new request + */ + void dispatch(RequestData* data); + + /* + * Dequeues a resolution. + */ + void dequeue(KNetwork::KResolver *obj); + + /* + * Notifies the manager that the given resolution is about to + * be deleted. This function should only be called by the + * KResolver destructor. + */ + void aboutToBeDeleted(KNetwork::KResolver *obj); + + /* + * Notifies the manager that new events are ready. + */ + void newEvent(); + + /* + * This function is called by the manager to receive a new event. It operates + * on the @ref eventSemaphore semaphore, which means it will block till there + * is at least one event to go. + */ + void receiveEvent(); + + private: + /* + * finds a suitable worker for this request + */ + KNetwork::KResolverWorkerBase *findWorker(KNetwork::KResolverPrivate *p); + + /* + * finds data for this request + */ + RequestData* findData(KResolverThread*); + + /* + * Handle completed requests. + * + * This function is called by releaseData above + */ + void handleFinished(); + + /* + * Handle one completed request. + * + * This function is called by handleFinished above. + */ + bool handleFinishedItem(RequestData* item); + + /* + * Notifies the parent class that this request is done. + * + * This function deletes the request + */ + void doNotifying(RequestData *p); + + /* + * Dequeues and notifies an object that is in Queued state + * Returns true if the object is no longer queued; false if it could not + * be dequeued (i.e., it's running) + */ + bool dequeueNew(KNetwork::KResolver* obj); + }; + + /* + * @internal + * This class is a worker thread in the resolver system. + * This class must be thread-safe. + */ + class KResolverThread: public QThread + { + private: + // private constructor. Only the manager can create worker threads + KResolverThread(); + RequestData* data; + + protected: + virtual void run(); // here the thread starts + + friend class KNetwork::Internal::KResolverManager; + friend class KNetwork::KResolverWorkerBase; + + public: + bool checkResolver(); // see KResolverWorkerBase::checkResolver + void acquireResolver(); // see KResolverWorkerBase::acquireResolver + void releaseResolver(); // see KResolverWorkerBase::releaseResolver + }; + + } // namespace Internal + +} // namespace KNetwork + + +#endif diff --git a/kdecore/network/kresolvermanager.cpp b/kdecore/network/kresolvermanager.cpp new file mode 100644 index 000000000..204031915 --- /dev/null +++ b/kdecore/network/kresolvermanager.cpp @@ -0,0 +1,822 @@ +/* -*- C++ -*- + * Copyright (C) 2003-2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <sys/types.h> +#include <netinet/in.h> +#include <limits.h> +#include <unistd.h> // only needed for pid_t + +#ifdef HAVE_RES_INIT +# include <sys/stat.h> +extern "C" { +# include <arpa/nameser.h> +} +# include <time.h> +# include <resolv.h> +#endif + +#include <qapplication.h> +#include <qstring.h> +#include <qcstring.h> +#include <qptrlist.h> +#include <qtimer.h> +#include <qmutex.h> +#include <qthread.h> +#include <qwaitcondition.h> +#include <qsemaphore.h> + +#include <kde_file.h> +#include <kdebug.h> +#include "kresolver.h" +#include "kresolver_p.h" +#include "kresolverworkerbase.h" + +namespace KNetwork +{ + namespace Internal + { + void initSrvWorker(); + void initStandardWorkers(); + } +} + +using namespace KNetwork; +using namespace KNetwork::Internal; + +/* + * Explanation on how the resolver system works + + When KResolver::start is called, it calls KResolverManager::enqueue to add + an entry to the queue. KResolverManager::enqueue will verify the availability + of a worker thread: if one is available, it will dispatch the request to it. + If no threads are available, it will then decide whether to launch a thread + or to queue for the future. + + (This process is achieved by always queueing the new request, starting a + new thread if necessary and then notifying of the availability of data + to all worker threads). + + * Worker thread + A new thread, when started, will enter its event loop + immediately. That is, it'll first try to acquire new data to + process, which means it will lock and unlock the manager mutex in + the process. + + If it finds no new data, it'll wait on the feedWorkers condition + for a certain maximum time. If that time expires and there's still + no data, the thread will exit, in order to save system resources. + + If it finds data, however, it'll set up and call the worker class + that has been selected by the manager. Once that worker is done, + the thread releases the data through KResolverManager::releaseData. + + * Data requesting/releasing + A worker thread always calls upon functions on the resolver manager + in order to acquire and release data. + + When data is being requested, the KResolverManager::requestData + function will look the currentRequests list and return the first + Queued request it finds, while marking it to be InProgress. + + When the worker class has returned, the worker thread will release + that data through the KResolverManager::releaseData function. If the + worker class has requested no further data (nRequests == 0), the + request's status is marked to be Done. It'll then look at the + requestor for that data: if it was requested by another worker, + it'll decrement the requests count for that one and add the results + to a list. And, finally, if the requests count for the requestor + becomes 0, it'll repeat this process for the requestor as well + (change status to Done, check for a requestor). + */ + +namespace +{ + +/* + * This class is used to control the access to the + * system's resolver API. + * + * It is necessary to periodically poll /etc/resolv.conf and reload + * it if any changes are noticed. This class does exactly that. + * + * However, there's also the problem of reloading the structure while + * some threads are in progress. Therefore, we keep a usage reference count. + */ +class ResInitUsage +{ +public: + +#ifdef HAVE_RES_INIT + time_t mTime; + int useCount; + +# ifndef RES_INIT_THREADSAFE + QWaitCondition cond; + QMutex mutex; +# endif + + bool shouldResInit() + { + // check if /etc/resolv.conf has changed + KDE_struct_stat st; + if (KDE_stat("/etc/resolv.conf", &st) != 0) + return false; + + if (mTime != st.st_mtime) + { + kdDebug(179) << "shouldResInit: /etc/resolv.conf updated" << endl; + return true; + } + return false; + } + + void callResInit() + { + if (mTime != 0) + { + // don't call it the first time + // let it be initialised naturally + kdDebug(179) << "callResInit: calling res_init()" << endl; + res_init(); + } + + KDE_struct_stat st; + if (KDE_stat("/etc/resolv.conf", &st) == 0) + mTime = st.st_mtime; + } + + ResInitUsage() + : mTime(0), useCount(0) + { } + + /* + * Marks the end of usage to the resolver tools + */ + void release() + { +# ifndef RES_INIT_THREADSAFE + QMutexLocker locker(&mutex); + if (--useCount == 0) + { + if (shouldResInit()) + callResInit(); + + // we've reached 0, wake up anyone that's waiting to call res_init + cond.wakeAll(); + } +# else + // do nothing +# endif + } + + /* + * Marks the beginning of usage of the resolver API + */ + void acquire() + { +# ifndef RES_INIT_THREADSAFE + mutex.lock(); + + if (shouldResInit()) + { + if (useCount) + { + // other threads are already using the API, so wait till + // it's all clear + // the thread that emits this condition will also call res_init + //qDebug("ResInitUsage: waiting for libresolv to be clear"); + cond.wait(&mutex); + } + else + // we're clear + callResInit(); + } + useCount++; + mutex.unlock(); + +# else + if (shouldResInit()) + callResInit(); + +# endif + } + +#else + ResInitUsage() + { } + + bool shouldResInit() + { return false; } + + void acquire() + { } + + void release() + { } +#endif + +} resInit; + +} // anonymous namespace + +/* + * parameters + */ +// a thread will try maxThreadRetries to get data, waiting at most +// maxThreadWaitTime milliseconds between each attempt. After that, it'll +// exit +static const int maxThreadWaitTime = 2000; // 2 seconds +static const int maxThreads = 5; + +static pid_t pid; // FIXME -- disable when everything is ok + +KResolverThread::KResolverThread() + : data(0L) +{ +} + +// remember! This function runs in a separate thread! +void KResolverThread::run() +{ + // initialisation + // enter the loop already + + //qDebug("KResolverThread(thread %u/%p): started", pid, (void*)QThread::currentThread()); + KResolverManager::manager()->registerThread(this); + while (true) + { + data = KResolverManager::manager()->requestData(this, ::maxThreadWaitTime); + //qDebug("KResolverThread(thread %u/%p) got data %p", KResolverManager::pid, + // (void*)QThread::currentThread(), (void*)data); + if (data) + { + // yes, we got data + // process it! + + // 1) set up + ; + + // 2) run it + data->worker->run(); + + // 3) release data + KResolverManager::manager()->releaseData(this, data); + + // now go back to the loop + } + else + break; + } + + KResolverManager::manager()->unregisterThread(this); + //qDebug("KResolverThread(thread %u/%p): exiting", pid, (void*)QThread::currentThread()); +} + +bool KResolverThread::checkResolver() +{ + return resInit.shouldResInit(); +} + +void KResolverThread::acquireResolver() +{ +#if defined(NEED_MUTEX) && !defined(Q_OS_FREEBSD) + getXXbyYYmutex.lock(); +#endif + + resInit.acquire(); +} + +void KResolverThread::releaseResolver() +{ +#if defined(NEED_MUTEX) && !defined(Q_OS_FREEBSD) + getXXbyYYmutex.unlock(); +#endif + + resInit.release(); +} + +static KResolverManager *globalManager; + +KResolverManager* KResolverManager::manager() +{ + if (globalManager == 0L) + new KResolverManager(); + return globalManager; +} + +KResolverManager::KResolverManager() + : runningThreads(0), availableThreads(0) +{ + globalManager = this; + workers.setAutoDelete(true); + currentRequests.setAutoDelete(true); + initSrvWorker(); + initStandardWorkers(); + + pid = getpid(); +} + +KResolverManager::~KResolverManager() +{ + // this should never be called + + // kill off running threads + for (workers.first(); workers.current(); workers.next()) + workers.current()->terminate(); +} + +void KResolverManager::registerThread(KResolverThread* ) +{ +} + +void KResolverManager::unregisterThread(KResolverThread*) +{ + runningThreads--; +} + +// this function is called by KResolverThread::run +RequestData* KResolverManager::requestData(KResolverThread *th, int maxWaitTime) +{ + ///// + // This function is called in a worker thread!! + ///// + + // lock the mutex, so that the manager thread or other threads won't + // interfere. + QMutexLocker locker(&mutex); + RequestData *data = findData(th); + + if (data) + // it found something, that's good + return data; + + // nope, nothing found; sleep for a while + availableThreads++; + feedWorkers.wait(&mutex, maxWaitTime); + availableThreads--; + + data = findData(th); + return data; +} + +RequestData* KResolverManager::findData(KResolverThread* th) +{ + ///// + // This function is called by @ref requestData above and must + // always be called with a locked mutex + ///// + + // now find data to be processed + for (RequestData *curr = newRequests.first(); curr; curr = newRequests.next()) + if (!curr->worker->m_finished) + { + // found one + if (curr->obj) + curr->obj->status = KResolver::InProgress; + curr->worker->th = th; + + // move it to the currentRequests list + currentRequests.append(newRequests.take()); + + return curr; + } + + // found nothing! + return 0L; +} + +// this function is called by KResolverThread::run +void KResolverManager::releaseData(KResolverThread *, RequestData* data) +{ + ///// + // This function is called in a worker thread!! + ///// + + //qDebug("KResolverManager::releaseData(%u/%p): %p has been released", pid, +// (void*)QThread::currentThread(), (void*)data); + + if (data->obj) + { + data->obj->status = KResolver::PostProcessing; + } + + data->worker->m_finished = true; + data->worker->th = 0L; // this releases the object + + // handle finished requests + handleFinished(); +} + +// this function is called by KResolverManager::releaseData above +void KResolverManager::handleFinished() +{ + bool redo = false; + QPtrQueue<RequestData> doneRequests; + + mutex.lock(); + + // loop over all items on the currently running list + // we loop from the last to the first so that we catch requests with "requestors" before + // we catch the requestor itself. + RequestData *curr = currentRequests.last(); + while (curr) + { + if (curr->worker->th == 0L) + { + if (handleFinishedItem(curr)) + { + doneRequests.enqueue(currentRequests.take()); + if (curr->requestor && + curr->requestor->nRequests == 0 && + curr->requestor->worker->m_finished) + // there's a requestor that is now finished + redo = true; + } + } + + curr = currentRequests.prev(); + } + + //qDebug("KResolverManager::handleFinished(%u): %d requests to notify", pid, doneRequests.count()); + while (RequestData *d = doneRequests.dequeue()) + doNotifying(d); + + mutex.unlock(); + + if (redo) + { + //qDebug("KResolverManager::handleFinished(%u): restarting processing to catch requestor", + // pid); + handleFinished(); + } +} + +// This function is called by KResolverManager::handleFinished above +bool KResolverManager::handleFinishedItem(RequestData* curr) + +{ + // for all items that aren't currently running, remove from the list + // this includes all finished or cancelled requests + + if (curr->worker->m_finished && curr->nRequests == 0) + { + // this one has finished + if (curr->obj) + curr->obj->status = KResolver::PostProcessing; // post-processing is run in doNotifying() + + if (curr->requestor) + --curr->requestor->nRequests; + + //qDebug("KResolverManager::handleFinishedItem(%u): removing %p since it's done", + // pid, (void*)curr); + return true; + } + return false; +} + + + +void KResolverManager::registerNewWorker(KResolverWorkerFactoryBase *factory) +{ + workerFactories.append(factory); +} + +KResolverWorkerBase* KResolverManager::findWorker(KResolverPrivate* p) +{ + ///// + // this function can be called on any user thread + ///// + + // this function is called with an unlocked mutex and it's expected to be + // thread-safe! + // but the factory list is expected not to be changed asynchronously + + // This function is responsible for finding a suitable worker for the given + // input. That means we have to do a costly operation to create each worker + // class and call their preprocessing functions. The first one that + // says they can process (i.e., preprocess() returns true) will get the job. + + KResolverWorkerBase *worker; + for (KResolverWorkerFactoryBase *factory = workerFactories.first(); factory; + factory = workerFactories.next()) + { + worker = factory->create(); + + // set up the data the worker needs to preprocess + worker->input = &p->input; + + if (worker->preprocess()) + { + // good, this one says it can process + if (worker->m_finished) + p->status = KResolver::PostProcessing; + else + p->status = KResolver::Queued; + return worker; + } + + // no, try again + delete worker; + } + + // found no worker + return 0L; +} + +void KResolverManager::doNotifying(RequestData *p) +{ + ///// + // This function may be called on any thread + // any thread at all: user threads, GUI thread, manager thread or worker thread + ///// + + // Notification and finalisation + // + // Once a request has finished the normal processing, we call the + // post processing function. + // + // After that is done, we will consolidate all results in the object's + // KResolverResults and then post an event indicating that the signal + // be emitted + // + // In case we detect that the object is waiting for completion, we do not + // post the event, for KResolver::wait will take care of emitting the + // signal. + // + // Once we release the mutex on the object, we may no longer reference it + // for it might have been deleted. + + // "User" objects are those that are not created by the manager. Note that + // objects created by worker threads are considered "user" objects. Objects + // created by the manager are those created for KResolver::resolveAsync. + // We should delete them. + + if (p->obj) + { + // lock the object + p->obj->mutex.lock(); + KResolver* parent = p->obj->parent; // is 0 for non-"user" objects + KResolverResults& r = p->obj->results; + + if (p->obj->status == KResolver::Canceled) + { + p->obj->status = KResolver::Canceled; + p->obj->errorcode = KResolver::Canceled; + p->obj->syserror = 0; + r.setError(KResolver::Canceled, 0); + } + else if (p->worker) + { + // post processing + p->worker->postprocess(); // ignore the result + + // copy the results from the worker thread to the final + // object + r = p->worker->results; + + // reset address + r.setAddress(p->input->node, p->input->service); + + //qDebug("KResolverManager::doNotifying(%u/%p): for %p whose status is %d and has %d results", + //pid, (void*)QThread::currentThread(), (void*)p, p->obj->status, r.count()); + + p->obj->errorcode = r.error(); + p->obj->syserror = r.systemError(); + p->obj->status = !r.isEmpty() ? + KResolver::Success : KResolver::Failed; + } + else + { + r.empty(); + r.setError(p->obj->errorcode, p->obj->syserror); + } + + // check whether there's someone waiting + if (!p->obj->waiting && parent) + // no, so we must post an event requesting that the signal be emitted + // sorry for the C-style cast, but neither static nor reintepret cast work + // here; I'd have to do two casts + QApplication::postEvent(parent, new QEvent((QEvent::Type)(ResolutionCompleted))); + + // release the mutex + p->obj->mutex.unlock(); + } + else + { + // there's no object! + if (p->worker) + p->worker->postprocess(); + } + + delete p->worker; + + // ignore p->requestor and p->nRequests + // they have been dealt with by the main loop + + delete p; + + // notify any objects waiting in KResolver::wait + notifyWaiters.wakeAll(); +} + +// enqueue a new request +// this function is called from KResolver::start and +// from KResolverWorkerBase::enqueue +void KResolverManager::enqueue(KResolver *obj, RequestData *requestor) +{ + RequestData *newrequest = new RequestData; + newrequest->nRequests = 0; + newrequest->obj = obj->d; + newrequest->input = &obj->d->input; + newrequest->requestor = requestor; + + // when processing a new request, find the most + // suitable worker + if ((newrequest->worker = findWorker(obj->d)) == 0L) + { + // oops, problem + // cannot find a worker class for this guy + obj->d->status = KResolver::Failed; + obj->d->errorcode = KResolver::UnsupportedFamily; + obj->d->syserror = 0; + + doNotifying(newrequest); + return; + } + + // no, queue it + // p->status was set in findWorker! + if (requestor) + requestor->nRequests++; + + if (!newrequest->worker->m_finished) + dispatch(newrequest); + else if (newrequest->nRequests > 0) + { + mutex.lock(); + currentRequests.append(newrequest); + mutex.unlock(); + } + else + // already done + doNotifying(newrequest); +} + +// a new request has been created +// dispatch it +void KResolverManager::dispatch(RequestData *data) +{ + // As stated in the beginning of the file, this function + // is supposed to verify the availability of threads, start + // any if necessary + + QMutexLocker locker(&mutex); + + // add to the queue + newRequests.append(data); + + // check if we need to start a new thread + // + // we depend on the variables availableThreads and runningThreads to + // know if we are supposed to start any threads: + // - if availableThreads > 0, then there is at least one thread waiting, + // blocked in KResolverManager::requestData. It can't unblock + // while we are holding the mutex locked, therefore we are sure that + // our event will be handled + // - if availableThreads == 0: + // - if runningThreads < maxThreads + // we will start a new thread, which will certainly block in + // KResolverManager::requestData because we are holding the mutex locked + // - if runningThreads == maxThreads + // This situation generally means that we have already maxThreads running + // and that all of them are processing. We will not start any new threads, + // but will instead wait for one to finish processing and request new data + // + // There's a possible race condition here, which goes unhandled: if one of + // threads has timed out waiting for new data and is in the process of + // exiting. In that case, availableThreads == 0 and runningThreads will not + // have decremented yet. This means that we will not start a new thread + // that we could have. However, since there are other threads working, our + // event should be handled soon. + // It won't be handled if and only if ALL threads are in the process of + // exiting. That situation is EXTREMELY unlikely and is not handled either. + // + if (availableThreads == 0 && runningThreads < maxThreads) + { + // yes, a new thread should be started + + // find if there's a finished one + KResolverThread *th = workers.first(); + while (th && th->running()) + th = workers.next(); + + if (th == 0L) + // no, create one + th = new KResolverThread; + else + workers.take(); + + th->start(); + workers.append(th); + runningThreads++; + } + + feedWorkers.wakeAll(); + + // clean up idle threads + workers.first(); + while (workers.current()) + { + if (!workers.current()->running()) + workers.remove(); + else + workers.next(); + } +} + +// this function is called by KResolverManager::dequeue +bool KResolverManager::dequeueNew(KResolver* obj) +{ + // This function must be called with a locked mutex + // Deadlock warning: + // always lock the global mutex first if both mutexes must be locked + + KResolverPrivate *d = obj->d; + + // check if it's in the new request list + RequestData *curr = newRequests.first(); + while (curr) + if (curr->obj == d) + { + // yes, this object is still in the list + // but it has never been processed + d->status = KResolver::Canceled; + d->errorcode = KResolver::Canceled; + d->syserror = 0; + newRequests.take(); + + delete curr->worker; + delete curr; + + return true; + } + else + curr = newRequests.next(); + + // check if it's running + curr = currentRequests.first(); + while (curr) + if (curr->obj == d) + { + // it's running. We cannot simply take it out of the list. + // it will be handled when the thread that is working on it finishes + d->mutex.lock(); + + d->status = KResolver::Canceled; + d->errorcode = KResolver::Canceled; + d->syserror = 0; + + // disengage from the running threads + curr->obj = 0L; + curr->input = 0L; + if (curr->worker) + curr->worker->input = 0L; + + d->mutex.unlock(); + } + else + curr = currentRequests.next(); + + return false; +} + +// this function is called by KResolver::cancel +// it's expected to be thread-safe +void KResolverManager::dequeue(KResolver *obj) +{ + QMutexLocker locker(&mutex); + dequeueNew(obj); +} diff --git a/kdecore/network/kresolverstandardworkers.cpp b/kdecore/network/kresolverstandardworkers.cpp new file mode 100644 index 000000000..93c706306 --- /dev/null +++ b/kdecore/network/kresolverstandardworkers.cpp @@ -0,0 +1,1028 @@ +/* -*- C++ -*- + * Copyright (C) 2003,2004 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <netdb.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef HAVE_NET_IF_H +#include <net/if.h> +#endif + +#include <qthread.h> +#include <qmutex.h> +#include <qstrlist.h> +#include <qfile.h> + +#include "kdebug.h" +#include "kglobal.h" +#include "kstandarddirs.h" +#include "kapplication.h" + +#include "kresolver.h" +#include "ksocketaddress.h" +#include "kresolverstandardworkers_p.h" + +struct hostent; +struct addrinfo; + +using namespace KNetwork; +using namespace KNetwork::Internal; + +static bool hasIPv6() +{ +#ifndef AF_INET6 + return false; +#else + if (getenv("KDE_NO_IPV6") != 0L) + return false; + + int fd = ::socket(AF_INET6, SOCK_STREAM, 0); + if (fd == -1) + return false; + + ::close(fd); + return true; +#endif +} + +// blacklist management +static QMutex blacklistMutex; // KDE4: change to a QReadWriteLock +QStringList KBlacklistWorker::blacklist; + +void KBlacklistWorker::init() +{ + // HACK! + // FIXME KDE4: How do I detect there is an instance, without triggering + // its creation or an assertion fault? + if (!KGlobal::_instance) + return; + + static bool beenhere = false; + + if (beenhere) + return; + + beenhere = true; + loadBlacklist(); +} + +void KBlacklistWorker::loadBlacklist() +{ + QMutexLocker locker(&blacklistMutex); + QStringList filelist = KGlobal::dirs()->findAllResources("config", "ipv6blacklist"); + + QStringList::ConstIterator it = filelist.constBegin(), + end = filelist.constEnd(); + for ( ; it != end; ++it) + { + // for each file, each line is a domainname to be blacklisted + QFile f(*it); + if (!f.open(IO_ReadOnly)) + continue; + + QTextStream stream(&f); + stream.setEncoding(QTextStream::Latin1); + for (QString line = stream.readLine(); !line.isNull(); + line = stream.readLine()) + { + if (line.isEmpty()) + continue; + + // make sure there are no surrounding whitespaces + // and that it starts with . + line = line.stripWhiteSpace(); + if (line[0] != '.') + line.prepend('.'); + + blacklist.append(line.lower()); + } + } +} + +// checks the blacklist to see if the domain is listed +// it matches the domain ending part +bool KBlacklistWorker::isBlacklisted(const QString& host) +{ + KBlacklistWorker::init(); + + // empty hostnames cannot be blacklisted + if (host.isEmpty()) + return false; + + // KDE4: QLatin1String + QString ascii = QString::fromLatin1(KResolver::domainToAscii(host)); + + QMutexLocker locker(&blacklistMutex); + + // now find out if this hostname is present + QStringList::ConstIterator it = blacklist.constBegin(), + end = blacklist.constEnd(); + for ( ; it != end; ++it) + if (ascii.endsWith(*it)) + return true; + + // no match: + return false; +} + +bool KBlacklistWorker::preprocess() +{ + if (isBlacklisted(nodeName())) + { + results.setError(KResolver::NoName); + finished(); + return true; + } + return false; +} + +bool KBlacklistWorker::run() +{ + results.setError(KResolver::NoName); + finished(); + return false; // resolution failure +} + +namespace +{ + /* + * Note on the use of the system resolver functions: + * + * In all cases, we prefer to use the new getaddrinfo(3) call. That means + * it will always be used if it is found. + * + * If it's not found, we have the option to use gethostbyname2_r, + * gethostbyname_r, gethostbyname2 and gethostbyname. If gethostbyname2_r + * is defined, we will use it. + * + * If it's not defined, we have to choose between the non-reentrant + * gethostbyname2 and the reentrant but IPv4-only gethostbyname_r: + * we will choose gethostbyname2 if AF_INET6 is defined. + * + * Lastly, gethostbyname will be used if nothing else is present. + */ + +#ifndef HAVE_GETADDRINFO + +# if defined(HAVE_GETHOSTBYNAME2_R) +# define USE_GETHOSTBYNAME2_R +# elif defined(HAVE_GETHOSTBYNAME_R) && (!defined(AF_INET6) || !defined(HAVE_GETHOSTBYNAME2)) +# define USE_GETHOSTBYNAME_R +# elif defined(HAVE_GETHOSTBYNAME2) +# define USE_GETHOSTBYNAME2) +# else +# define USE_GETHOSTBYNAME +# endif + + class GetHostByNameThread: public KResolverWorkerBase + { + public: + QCString m_hostname; // might be different! + Q_UINT16 m_port; + int m_scopeid; + int m_af; + KResolverResults& results; + + GetHostByNameThread(const char * hostname, Q_UINT16 port, + int scopeid, int af, KResolverResults* res) : + m_hostname(hostname), m_port(port), m_scopeid(scopeid), m_af(af), + results(*res) + { } + + ~GetHostByNameThread() + { } + + virtual bool preprocess() + { return true; } + + virtual bool run(); + + void processResults(hostent* he, int my_h_errno); + }; + + bool GetHostByNameThread::run() + { + + hostent *resultptr; + hostent my_results; + unsigned buflen = 1024; + int res; + int my_h_errno; + char *buf = 0L; + + // qDebug("ResolveThread::run(): started threaded gethostbyname for %s (af = %d)", + // m_hostname.data(), m_af); + + ResolverLocker resLock( this ); + do + { + res = 0; + my_h_errno = HOST_NOT_FOUND; + + // check blacklist + if (m_af != AF_INET && + KBlacklistWorker::isBlacklisted(QString::fromLatin1(m_hostname))) + break; + +# ifdef USE_GETHOSTBYNAME2_R + buf = new char[buflen]; + res = gethostbyname2_r(m_hostname, m_af, &my_results, buf, buflen, + &resultptr, &my_h_errno); + +# elif defined(USE_GETHOSTBYNAME_R) + if (m_af == AF_INET) + { + buf = new char[buflen]; + res = gethostbyname_r(m_hostname, &my_results, buf, buflen, + &resultptr, &my_h_errno); + } + else + resultptr = 0; // signal error + +# elif defined(USE_GETHOSTBYNAME2) + // must lock mutex + resultptr = gethostbyname2(m_hostname, m_af); + my_h_errno = h_errno; + +# else + if (m_af == AF_INET) + { + // must lock mutex + resultptr = gethostbyname(m_hostname); + my_h_errno = h_errno; + } + else + resultptr = 0; +# endif + + if (resultptr != 0L) + my_h_errno = 0; + // qDebug("GetHostByNameThread::run(): gethostbyname for %s (af = %d) returned: %d", + // m_hostname.data(), m_af, my_h_errno); + + if (res == ERANGE) + { + // Enlarge the buffer + buflen += 1024; + delete [] buf; + buf = new char[buflen]; + } + + if ((res == ERANGE || my_h_errno != 0) && checkResolver()) + { + // resolver needs updating, so we might as well do it now + resLock.openClose(); + } + } + while (res == ERANGE); + processResults(resultptr, my_h_errno); + + delete [] buf; + + finished(); + return results.error() == KResolver::NoError; + } + + void GetHostByNameThread::processResults(hostent *he, int herrno) + { + if (herrno) + { + qDebug("KStandardWorker::processResults: got error %d", herrno); + switch (herrno) + { + case HOST_NOT_FOUND: + results.setError(KResolver::NoName); + return; + + case TRY_AGAIN: + results.setError(KResolver::TryAgain); + return; + + case NO_RECOVERY: + results.setError(KResolver::NonRecoverable); + return; + + case NO_ADDRESS: + results.setError(KResolver::NoName); + return; + + default: + results.setError(KResolver::UnknownError); + return; + } + } + else if (he == 0L) + { + results.setError(KResolver::NoName); + return; // this was an error + } + + // clear any errors + setError(KResolver::NoError); + results.setError(KResolver::NoError); + + // we process results in the reverse order + // that is, we prepend each result to the list of results + int proto = protocol(); + int socktype = socketType(); + if (socktype == 0) + socktype = SOCK_STREAM; // default + + QString canon = KResolver::domainToUnicode(QString::fromLatin1(he->h_name)); + KInetSocketAddress sa; + sa.setPort(m_port); + if (he->h_addrtype != AF_INET) + sa.setScopeId(m_scopeid); // this will also change the socket into IPv6 + + for (int i = 0; he->h_addr_list[i]; i++) + { + sa.setHost(KIpAddress(he->h_addr_list[i], he->h_addrtype == AF_INET ? 4 : 6)); + results.prepend(KResolverEntry(sa, socktype, proto, canon, m_hostname)); + // qDebug("KStandardWorker::processResults: adding %s", sa.toString().latin1()); + } + // qDebug("KStandardWorker::processResults: added %d entries", i); + } + +#else // HAVE_GETADDRINFO + + class GetAddrInfoThread: public KResolverWorkerBase + { + public: + QCString m_node; + QCString m_serv; + int m_af; + int m_flags; + KResolverResults& results; + + GetAddrInfoThread(const char* node, const char* serv, int af, int flags, + KResolverResults* res) : + m_node(node), m_serv(serv), m_af(af), m_flags(flags), results(*res) + { } + + ~GetAddrInfoThread() + { } + + virtual bool preprocess() + { return true; } + + virtual bool run(); + + void processResults(addrinfo* ai, int ret_code, KResolverResults& rr); + }; + + bool GetAddrInfoThread::run() + { + // check blacklist + if ((m_af != AF_INET && m_af != AF_UNSPEC) && + KBlacklistWorker::isBlacklisted(QString::fromLatin1(m_node))) + { + results.setError(KResolver::NoName); + finished(); + return false; // failed + } + + do + { + ResolverLocker resLock( this ); + + // process hints + addrinfo hint; + memset(&hint, 0, sizeof(hint)); + hint.ai_family = m_af; + hint.ai_socktype = socketType(); + hint.ai_protocol = protocol(); + + if (hint.ai_socktype == 0) + hint.ai_socktype = SOCK_STREAM; // default + + if (m_flags & KResolver::Passive) + hint.ai_flags |= AI_PASSIVE; + if (m_flags & KResolver::CanonName) + hint.ai_flags |= AI_CANONNAME; +# ifdef AI_NUMERICHOST + if (m_flags & KResolver::NoResolve) + hint.ai_flags |= AI_NUMERICHOST; +# endif +# ifdef AI_ADDRCONFIG + hint.ai_flags |= AI_ADDRCONFIG; +# endif + + // now we do the blocking processing + if (m_node.isEmpty()) + m_node = "*"; + + addrinfo *result; + int res = getaddrinfo(m_node, m_serv, &hint, &result); + // kdDebug(179) << k_funcinfo << "getaddrinfo(\"" + // << m_node << "\", \"" << m_serv << "\", af=" + // << m_af << ") returned " << res << endl; + + if (res != 0) + { + if (checkResolver()) + { + // resolver requires reinitialisation + resLock.openClose(); + continue; + } + + switch (res) + { + case EAI_BADFLAGS: + results.setError(KResolver::BadFlags); + break; + +#ifdef EAI_NODATA + // In some systems, EAI_NODATA was #define'd to EAI_NONAME which would break this case. +#if EAI_NODATA != EAI_NONAME + case EAI_NODATA: // it was removed in RFC 3493 +#endif +#endif + case EAI_NONAME: + results.setError(KResolver::NoName); + break; + + case EAI_AGAIN: + results.setError(KResolver::TryAgain); + break; + + case EAI_FAIL: + results.setError(KResolver::NonRecoverable); + break; + + case EAI_FAMILY: + results.setError(KResolver::UnsupportedFamily); + break; + + case EAI_SOCKTYPE: + results.setError(KResolver::UnsupportedSocketType); + break; + + case EAI_SERVICE: + results.setError(KResolver::UnsupportedService); + break; + + case EAI_MEMORY: + results.setError(KResolver::Memory); + break; + + case EAI_SYSTEM: + results.setError(KResolver::SystemError, errno); + break; + + default: + results.setError(KResolver::UnknownError, errno); + break; + } + + finished(); + return false; // failed + } + + // if we are here, lookup succeeded + QString canon; + const char *previous_canon = 0L; + + for (addrinfo* p = result; p; p = p->ai_next) + { + // cache the last canon name to avoid doing the ToUnicode processing unnecessarily + if ((previous_canon && !p->ai_canonname) || + (!previous_canon && p->ai_canonname) || + (p->ai_canonname != previous_canon && + strcmp(p->ai_canonname, previous_canon) != 0)) + { + canon = KResolver::domainToUnicode(QString::fromAscii(p->ai_canonname)); + previous_canon = p->ai_canonname; + } + + results.append(KResolverEntry(p->ai_addr, p->ai_addrlen, p->ai_socktype, + p->ai_protocol, canon, m_node)); + } + + freeaddrinfo(result); + results.setError(KResolver::NoError); + finished(); + return results.error() == KResolver::NoError; + } + while (true); + } + +#endif // HAVE_GETADDRINFO +} // namespace + +bool KStandardWorker::sanityCheck() +{ + // check that the requested values are sensible + + if (!nodeName().isEmpty()) + { + QString node = nodeName(); + if (node.find('%') != -1) + node.truncate(node.find('%')); + + if (node.isEmpty() || node == QString::fromLatin1("*") || + node == QString::fromLatin1("localhost")) + m_encodedName.truncate(0); + else + { + m_encodedName = KResolver::domainToAscii(node); + + if (m_encodedName.isNull()) + { + qDebug("could not encode hostname '%s' (UTF-8)", node.utf8().data()); + setError(KResolver::NoName); + return false; // invalid hostname! + } + + // qDebug("Using encoded hostname '%s' for '%s' (UTF-8)", m_encodedName.data(), + // node.utf8().data()); + } + } + else + m_encodedName.truncate(0); // just to be sure, but it should be clear already + + if (protocol() == -1) + { + setError(KResolver::NonRecoverable); + return false; // user passed invalid protocol name + } + + return true; // it's sane +} + +bool KStandardWorker::resolveScopeId() +{ + // we must test the original name, not the encoded one + scopeid = 0; + int pos = nodeName().findRev('%'); + if (pos == -1) + return true; + + QString scopename = nodeName().mid(pos + 1); + + bool ok; + scopeid = scopename.toInt(&ok); + if (!ok) + { + // it's not a number + // therefore, it's an interface name +#ifdef HAVE_IF_NAMETOINDEX + scopeid = if_nametoindex(scopename.latin1()); +#else + scopeid = 0; +#endif + } + + return true; +} + +bool KStandardWorker::resolveService() +{ + // find the service first + bool ok; + port = serviceName().toUInt(&ok); + if (!ok) + { + // service name does not contain a port number + // must be a name + + if (serviceName().isEmpty() || serviceName().compare(QString::fromLatin1("*")) == 0) + port = 0; + else + { + // it's a name. We need the protocol name in order to lookup. + QCString protoname = protocolName(); + + if (protoname.isEmpty() && protocol()) + { + protoname = KResolver::protocolName(protocol()).first(); + + // if it's still empty... + if (protoname.isEmpty()) + { + // lookup failed! + setError(KResolver::NoName); + return false; + } + } + else + protoname = "tcp"; + + // it's not, so we can do a port lookup + int result = KResolver::servicePort(serviceName().latin1(), protoname); + if (result == -1) + { + // lookup failed! + setError(KResolver::NoName); + return false; + } + + // it worked, we have a port number + port = (Q_UINT16)result; + } + } + + // we found a port + return true; +} + +KResolver::ErrorCodes KStandardWorker::addUnix() +{ + // before trying to add, see if the user wants Unix sockets + if ((familyMask() & KResolver::UnixFamily) == 0) + // no, Unix sockets are not wanted + return KResolver::UnsupportedFamily; + + // now check if the requested data are good for a Unix socket + if (!m_encodedName.isEmpty()) + return KResolver::AddrFamily; // non local hostname + + if (protocol() || protocolName()) + return KResolver::BadFlags; // cannot have Unix sockets with protocols + + QString pathname = serviceName(); + if (pathname.isEmpty()) + return KResolver::NoName;; // no path? + + if (pathname[0] != '/') + // non absolute pathname + // put it in /tmp + pathname.prepend("/tmp/"); + + // qDebug("QNoResolveWorker::addUnix(): adding Unix socket for %s", pathname.local8Bit().data()); + KUnixSocketAddress sa(pathname); + int socktype = socketType(); + if (socktype == 0) + socktype = SOCK_STREAM; // default + + results.append(KResolverEntry(sa, socktype, 0)); + setError(KResolver::NoError); + + return KResolver::NoError; +} + +bool KStandardWorker::resolveNumerically() +{ + // if the NoResolve flag is active, our result from this point forward + // will always be true, even if the resolution failed. + // that indicates that our result is authoritative. + + bool wantV4 = familyMask() & KResolver::IPv4Family, + wantV6 = familyMask() & KResolver::IPv6Family; + + if (!wantV6 && !wantV4) + // no Internet address is wanted! + return (flags() & KResolver::NoResolve); + + // now try to find results + if (!resolveScopeId() || !resolveService()) + return (flags() & KResolver::NoResolve); + + // we have scope IDs and port numbers + // now try to resolve the hostname numerically + KInetSocketAddress sa; + setError(KResolver::NoError); + sa.setHost(KIpAddress(QString::fromLatin1(m_encodedName))); + + // if it failed, the length was reset to 0 + bool ok = sa.length() != 0; + + sa.setPort(port); + if (sa.ipVersion() == 6) + sa.setScopeId(scopeid); + int proto = protocol(); + int socktype = socketType(); + if (socktype == 0) + socktype = SOCK_STREAM; + + if (ok) + { + // the given hostname was successfully converted to an IP address + // check if the user wanted this kind of address + + if ((sa.ipVersion() == 4 && wantV4) || + (sa.ipVersion() == 6 && wantV6)) + results.append(KResolverEntry(sa, socktype, proto)); + else + { + // Note: the address *IS* a numeric IP + // but it's not of the kind the user asked for + // + // that means that it cannot be a Unix socket (because it's an IP) + // and that means that no resolution will tell us otherwise + // + // This is a failed resolution + + setError(KResolver::AddrFamily); + return true; + } + } + else if (m_encodedName.isEmpty()) + { + // user wanted localhost + if (flags() & KResolver::Passive) + { + if (wantV6) + { + sa.setHost(KIpAddress::anyhostV6); + results.append(KResolverEntry(sa, socktype, proto)); + } + + if (wantV4) + { + sa.setHost(KIpAddress::anyhostV4); + results.append(KResolverEntry(sa, socktype, proto)); + } + } + else + { + if (wantV6) + { + sa.setHost(KIpAddress::localhostV6); + results.append(KResolverEntry(sa, socktype, proto)); + } + + if (wantV4) + { + sa.setHost(KIpAddress::localhostV4); + results.append(KResolverEntry(sa, socktype, proto)); + } + } + + ok = true; + } + else + { + // probably bad flags, since the address is not convertible without + // resolution + + setError(KResolver::BadFlags); + ok = false; + } + + return ok || (flags() & KResolver::NoResolve); +} + +bool KStandardWorker::preprocess() +{ + // check sanity + if (!sanityCheck()) + return false; + + // this worker class can only handle known families + if (familyMask() & KResolver::UnknownFamily) + { + setError(KResolver::UnsupportedFamily); + return false; // we don't know about this + } + + // check the socket types + if (socketType() != SOCK_STREAM && socketType() != SOCK_DGRAM && socketType() != 0) + { + setError(KResolver::UnsupportedSocketType); + return false; + } + + // check if we can resolve all numerically + // resolveNumerically always returns true if the NoResolve flag is set + if (resolveNumerically() || m_encodedName.isEmpty()) + { + // indeed, we have resolved numerically + setError(addUnix()); + if (results.count()) + setError(KResolver::NoError); + finished(); + return true; + } + + // check if the user wants something we know about +#ifdef AF_INET6 +# define mask (KResolver::IPv6Family | KResolver::IPv4Family | KResolver::UnixFamily) +#else +# define mask (KResolver::IPv4Family | KResolver::UnixFamily) +#endif + + if ((familyMask() & mask) == 0) + // errr... nothing we know about + return false; + +#undef mask + + return true; // it's ok +} + +bool KStandardWorker::run() +{ +#ifndef HAVE_GETADDRINFO + // check the scope id first + // since most of the resolutions won't have a scope id, this should be fast + // and we won't have wasted time on services if this fails + if (!resolveScopeId()) + return false; + + // resolve the service now, before entering the blocking operation + if (!resolveService()) + return false; +#endif + + // good + // now we need the hostname + setError(KResolver::NoName); + + // these are the family types that we know of + struct + { + KResolver::SocketFamilies mask; + int af; + } families[] = { { KResolver::IPv4Family, AF_INET } +#ifdef AF_INET6 + , { KResolver::IPv6Family, AF_INET6 } +#endif + }; + int familyCount = sizeof(families)/sizeof(families[0]); + bool skipIPv6 = !hasIPv6(); + resultList.setAutoDelete(true); + + for (int i = 0; i < familyCount; i++) + if (familyMask() & families[i].mask) + { +#ifdef AF_INET6 + if (skipIPv6 && families[i].af == AF_INET6) + continue; +#endif + + KResolverWorkerBase *worker; + KResolverResults *res = new KResolverResults; + resultList.append(res); +#ifdef HAVE_GETADDRINFO + worker = new GetAddrInfoThread(m_encodedName, + serviceName().latin1(), + families[i].af, flags(), res); +#else + worker = new GetHostByNameThread(m_encodedName, port, scopeid, + families[i].af, res); +#endif + + enqueue(worker); + } + + // not finished + return true; +} + +bool KStandardWorker::postprocess() +{ + if (results.count()) + return true; // no need + // now copy over what we need from the underlying results + + // start backwards because IPv6 was launched later (if at all) + if (resultList.isEmpty()) + { + results.setError(KResolver::NoName); + return true; + } + + KResolverResults *rr = resultList.last(); + while (rr) + { + if (!rr->isEmpty()) + { + results.setError(KResolver::NoError); + KResolverResults::Iterator it = rr->begin(); + for ( ; it != rr->end(); ++it) + results.append(*it); + } + else if (results.isEmpty()) + // this generated an error + // copy the error code over + setError(rr->error(), rr->systemError()); + + rr = resultList.prev(); + } + + resultList.clear(); + return true; +} + +#ifdef HAVE_GETADDRINFO +KGetAddrinfoWorker::~KGetAddrinfoWorker() +{ +} + +bool KGetAddrinfoWorker::preprocess() +{ + // getaddrinfo(3) can always handle any kind of request that makes sense + if (!sanityCheck()) + return false; + + if (flags() & KResolver::NoResolve) + // oops, numeric resolution? + return run(); + + return true; +} + +bool KGetAddrinfoWorker::run() +{ + // make an AF_UNSPEC getaddrinfo(3) call + GetAddrInfoThread worker(m_encodedName, serviceName().latin1(), + AF_UNSPEC, flags(), &results); + + if (!worker.run()) + { + if (wantThis(AF_UNIX)) + { + if (addUnix() == KResolver::NoError) + setError(KResolver::NoError); + } + else + setError(worker.results.error(), worker.results.systemError()); + + return false; + } + + // The worker has finished working + // now copy over only what we may want + // keep track of any Unix-domain sockets + + bool seen_unix = false; + KResolverResults::Iterator it = results.begin(); + for ( ; it != results.end(); ) + { + if ((*it).family() == AF_UNIX) + seen_unix = true; + if (!wantThis((*it).family())) + it = results.remove(it); + else + ++it; + } + + if (!seen_unix) + addUnix(); + + finished(); + return true; +} + +bool KGetAddrinfoWorker::wantThis(int family) +{ + // tells us if the user wants a socket of this family + +#ifdef AF_INET6 + if (family == AF_INET6 && familyMask() & KResolver::IPv6Family) + return true; +#endif + if (family == AF_INET && familyMask() & KResolver::IPv4Family) + return true; + if (family == AF_UNIX && familyMask() & KResolver::UnixFamily) + return true; + + // it's not a family we know about... + if (familyMask() & KResolver::UnknownFamily) + return true; + + return false; +} + +#endif + +void KNetwork::Internal::initStandardWorkers() +{ + //KResolverWorkerFactoryBase::registerNewWorker(new KResolverWorkerFactory<KBlacklistWorker>); + KResolverWorkerFactoryBase::registerNewWorker(new KResolverWorkerFactory<KStandardWorker>); + +#ifdef HAVE_GETADDRINFO + KResolverWorkerFactoryBase::registerNewWorker(new KResolverWorkerFactory<KGetAddrinfoWorker>); +#endif +} diff --git a/kdecore/network/kresolverstandardworkers_p.h b/kdecore/network/kresolverstandardworkers_p.h new file mode 100644 index 000000000..30c8f3c1e --- /dev/null +++ b/kdecore/network/kresolverstandardworkers_p.h @@ -0,0 +1,111 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KRESOLVERSTANDARDWORKERS_P_H +#define KRESOLVERSTANDARDWORKERS_P_H + +#include <sys/types.h> +#include <netdb.h> + +#include <qptrlist.h> +#include <qcstring.h> +#include <qstringlist.h> + +#include "kresolver.h" +#include "kresolverworkerbase.h" + +#include <config.h> + +namespace KNetwork { namespace Internal +{ + extern void initStandardWorkers() KDE_NO_EXPORT; + + /** + * @internal + * The blacklist worker. + */ + class KBlacklistWorker: public KNetwork::KResolverWorkerBase + { + public: + static QStringList blacklist; + + static void loadBlacklist(); + static void init(); + static bool isBlacklisted(const QString&); + + virtual bool preprocess(); + virtual bool run(); + virtual bool postprocess() { return true; } + }; + + /** @internal + * Standard worker. + */ + class KStandardWorker: public KNetwork::KResolverWorkerBase + { + protected: + mutable QCString m_encodedName; + Q_UINT16 port; + int scopeid; + QPtrList<KNetwork::KResolverResults> resultList; + + public: + bool sanityCheck(); + + virtual bool preprocess(); + virtual bool run(); + virtual bool postprocess(); + + bool resolveScopeId(); + bool resolveService(); + bool resolveNumerically(); + + KNetwork::KResolver::ErrorCodes addUnix(); + }; + +#if defined(HAVE_GETADDRINFO) + /** @internal + * Worker class based on getaddrinfo(3). + * + * This class does not do post-processing. + */ + class KGetAddrinfoWorker: public KStandardWorker + { + public: + KGetAddrinfoWorker() + { } + + virtual ~KGetAddrinfoWorker(); + virtual bool preprocess(); + virtual bool run(); + virtual bool postprocess() { return true; } + + bool wantThis(int family); + }; +#endif // HAVE_GETADDRINFO + +} } // namespace KNetwork::Internal + + +#endif diff --git a/kdecore/network/kresolverworkerbase.cpp b/kdecore/network/kresolverworkerbase.cpp new file mode 100644 index 000000000..32a32da2a --- /dev/null +++ b/kdecore/network/kresolverworkerbase.cpp @@ -0,0 +1,150 @@ +/* -*- C++ -*- + * Copyright (C) 2003,2004 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <assert.h> + +#include <qcstring.h> +#include <qstring.h> + +#include "kresolver.h" +#include "kresolver_p.h" +#include "kresolverworkerbase.h" + +using namespace KNetwork; +using namespace KNetwork::Internal; + +KResolverWorkerBase::KResolverWorkerBase() + : th(0L), input(0L), m_finished(0), m_reserved(0) +{ +} + +KResolverWorkerBase::~KResolverWorkerBase() +{ +} + +QString KResolverWorkerBase::nodeName() const +{ + if (input) + return input->node; + return QString::null; +} + +QString KResolverWorkerBase::serviceName() const +{ + if (input) + return input->service; + return QString::null; +} + +int KResolverWorkerBase::flags() const +{ + if (input) + return input->flags; + return 0; +} + +int KResolverWorkerBase::familyMask() const +{ + if (input) + return input->familyMask; + return 0; +} + +int KResolverWorkerBase::socketType() const +{ + if (input) + return input->socktype; + return 0; +} + +int KResolverWorkerBase::protocol() const +{ + if (input) + return input->protocol; + return 0; +} + +QCString KResolverWorkerBase::protocolName() const +{ + QCString res; + if (input) + res = input->protocolName; + return res; +} + +void KResolverWorkerBase::finished() +{ + m_finished = true; +} + +bool KResolverWorkerBase::postprocess() +{ + return true; // no post-processing is a always successful postprocessing +} + +bool KResolverWorkerBase::enqueue(KResolver* res) +{ + KResolverManager::manager()->enqueue(res, th->data); + return true; +} + +bool KResolverWorkerBase::enqueue(KResolverWorkerBase* worker) +{ + RequestData *myself = th->data; + RequestData *newrequest = new RequestData; + newrequest->obj = 0; + newrequest->input = input; // same input + newrequest->requestor = myself; + newrequest->nRequests = 0; + newrequest->worker = worker; + myself->nRequests++; + KResolverManager::manager()->dispatch(newrequest); + return true; +} + +bool KResolverWorkerBase::checkResolver() +{ + assert(th != 0L); + return th->checkResolver(); +} + +void KResolverWorkerBase::acquireResolver() +{ + assert(th != 0L); + th->acquireResolver(); +} + +void KResolverWorkerBase::releaseResolver() +{ + assert(th != 0L); + th->releaseResolver(); +} + +void KResolverWorkerFactoryBase::registerNewWorker(KResolverWorkerFactoryBase* factory) +{ + KResolverManager::manager()->registerNewWorker(factory); +} + diff --git a/kdecore/network/kresolverworkerbase.h b/kdecore/network/kresolverworkerbase.h new file mode 100644 index 000000000..8776e9142 --- /dev/null +++ b/kdecore/network/kresolverworkerbase.h @@ -0,0 +1,317 @@ +/* -*- C++ -*- + * Copyright (C) 2003,2004 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KRESOLVERWORKERBASE_H +#define KRESOLVERWORKERBASE_H + +#include "kresolver.h" + +// forward declarations +class QString; +template <class T> class QValueList; + +namespace KNetwork { + + namespace Internal + { + class KResolverThread; + struct InputData; + } + +/** @internal + * This class is the base functionality for a resolver worker. That is, + * the class that does the actual work. + * + * In the future, this class might be exposed to allow plug-ins. So, try and + * make it binary compatible. + * + * Note that hostnames are still encoded in Unicode at this point. It's up to + * the worker class to decide which encoding to use. In the case of DNS, + * an ASCII Compatible Encoding (ACE) must be used. + * See @ref KResolver::domainToAscii. + * + * Also specially note that the run method in this class is called in a + * thread that is not the program's main thread. So do not do anything there + * that you shouldn't! + * + * @class KResolverWorkerBase kresolverworkerbase.h kresolverworkerbase.h + */ +class KResolverWorkerBase +{ +public: + + /** + * Helper class for locking the resolver subsystem. + * Similar to QMutexLocker. + * + * @author LuÃs Pedro Coelho + * @since 3.4 + */ + class ResolverLocker + { + public: + /** + * Constructor. Acquires a lock. + */ + ResolverLocker(KResolverWorkerBase* parent) + : parent( parent ) + { + parent->acquireResolver(); + } + + /** + * Destructor. Releases the lock. + */ + ~ResolverLocker() + { + parent->releaseResolver(); + } + + /** + * Releases the lock and then reacquires it. + * It may be necessary to call this if the resolving function + * decides to retry. + */ + void openClose() + { + parent->releaseResolver(); + parent->acquireResolver(); + } + + private: + /// @internal + KResolverWorkerBase* parent; + }; +private: + // this will be like our d pointer + KNetwork::Internal::KResolverThread *th; + const KNetwork::Internal::InputData *input; + friend class KNetwork::Internal::KResolverThread; + friend class KNetwork::Internal::KResolverManager; + friend class KResolverWorkerBase::ResolverLocker; + + int m_finished : 1; + int m_reserved : 31; // reserved + +public: + /** + * Derived classes will put their resolved data in this list, or will + * leave it empty in case of error. + * + * Status and error codes should also be stored in this object (the + * @ref setError function does that). + */ + KResolverResults results; + +public: + // default constructor + KResolverWorkerBase(); + + // virtual destructor + virtual ~KResolverWorkerBase(); + + /** + * This is the hostname to be looked for + */ + QString nodeName() const; + + /** + * And this is the service name + */ + QString serviceName() const; + + /** + * gets the flags + */ + int flags() const; + + /** + * gets the family mask + */ + int familyMask() const; + + /** + * gets the socket type + */ + int socketType() const; + + /** + * gets the protocol number + */ + int protocol() const; + + /** + * gets the protocol name, if applicable + */ + QCString protocolName() const; + + /** + * Call this function to indicate that processing + * has finished. This is useful in the preprocessing + * stage, to indicate that @ref run doesn't have to be + * called. + */ + void finished(); + +protected: + // like a QThread + /** + * This is the function that should be overriden in derived classes. + * + * Derived classes will do their blocking job in this function and return + * either success or failure to work (not the lookup). That is, whether the + * lookup result was a domain found or not, if we got our answer, we should + * indicate success. The error itself must be set with @ref setError. + * + * \b Important: this function gets called in a separate thread! + * + * @return true on success + */ + virtual bool run() = 0; + + /** + * This function gets called during pre processing for this class and you must + * override it. + * + * \b Important: this function gets called in the main event thread. And it MUST + * NOT block. + * + * This function can be used for an object to determine if it will be able + * to resolve the given data or not even before launching into a blocking + * operation. This function should return true if the object is capable of + * handling this kind of data; false otherwise. Note that the return value + * of 'true' means that the object's blocking answer will be considered authoritative. + * + * This function MUST NOT queue further requests. Leave that to @ref run. + * + * This function is pure virtual; you must override it. + * + * @return true on success + */ + virtual bool preprocess() = 0; + + /** + * This function gets called during post processing for this class. + * + * \b Important: this function gets called in the main event thread. And it MUST + * NOT block. + * + * @returns true on success + */ + virtual bool postprocess(); + + /** + * Sets the error + */ + inline void setError(int errorcode, int syserror = 0) + { results.setError(errorcode, syserror); } + + /** + * Enqueue the given resolver for post-processing. + * + * Use this function to make the manager call for another resolution. + * This is suitable for workers that do post-processing. + * + * The manager will make sure that any requests enqueued by this function + * are done before calling the postprocessing function, which you should + * override. + * + * \b Important: do use KResolver's own enqueueing functions (i.e., @ref KResolver::start). + * Instead, use this function. + * + * @returns true on successful queueing or false if a problem ocurred + */ + bool enqueue(KResolver* other); + + /** + * @overload + */ + bool enqueue(KResolverWorkerBase* worker); + + /** + * Checks the resolver subsystem status. + * @returns true if the resolver subsystem changed, false otherwise. + * If this function returns true, it might be necessary to + * restart the resolution altogether. + * @since 3.4 + */ + bool checkResolver(); + + /** + * This function has to be called from the resolver workers that require + * use of the DNS resolver code (i.e., res_* functions, generally in + * libresolv). It indicates that the function is starting a resolution + * and that the resolver backend shouldn't change asynchronously. + * + * If any pending res_init's are required, they will be performed before + * this function returns. + * + * @since 3.4 + */ + void acquireResolver(); + + /** + * This function is the counterpart for @ref acquireResolver: the worker + * thread indicates that it's done with the resolver. + * + * @since 3.4 + */ + void releaseResolver(); + +}; + +/** @internal + * This class provides functionality for creating and registering worker classes. + * + * @class KResolverWorkerFactoryBase kresolverworkerbase.h kresolverworkerbase.h + */ +class KResolverWorkerFactoryBase +{ +public: + virtual KResolverWorkerBase* create() const = 0; + + /** + * Wrapper call to register workers + * + * It is NOT thread-safe! + */ + static void registerNewWorker(KResolverWorkerFactoryBase* factory); +}; + +/** @internal + * This class provides functionality for creating and registering worker classes. + * + * @class KResolverWorkerFactory kresolverworkerbase.h kresolverworkerbase.h + */ +template<class Worker> +class KResolverWorkerFactory: public KResolverWorkerFactoryBase +{ +public: + virtual KResolverWorkerBase* create() const + { return new Worker; } +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/kreverseresolver.cpp b/kdecore/network/kreverseresolver.cpp new file mode 100644 index 000000000..dbcc23d79 --- /dev/null +++ b/kdecore/network/kreverseresolver.cpp @@ -0,0 +1,263 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +// System includes +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <signal.h> + +// Qt +#include <qevent.h> +#include <qmutex.h> +#include <qapplication.h> + +// Us +#include "kreverseresolver.h" +#include "kresolver_p.h" +#include "kresolverworkerbase.h" +#include "ksocketaddress.h" + +#ifndef HAVE_GETNAMEINFO +// FIXME KDE4: +// move to syssocket or adapt +# include "netsupp.h" +#endif + +using namespace KNetwork; +using namespace KNetwork::Internal; + +namespace +{ + class ReverseThread: public KResolverWorkerBase + { + public: + ReverseThread(const KSocketAddress& addr, int flags) + : m_addr(addr), m_flags(flags), m_parent(0L) + { } + + virtual ~ReverseThread() + { } + + virtual bool preprocess() + { return true; } + virtual bool run(); + virtual bool postprocess(); + + // input: + KSocketAddress m_addr; + int m_flags; + KReverseResolver *m_parent; + + // output: + QString node; + QString service; + bool success; + }; + + class KReverseResolverEvent: public QEvent + { + public: + static const int myType = QEvent::User + 63; // arbitrary value + QString node; + QString service; + bool success; + + KReverseResolverEvent(const QString& _node, const QString& _service, + bool _success) + : QEvent((Type)myType), node(_node), + service(_service), success(_success) + { } + }; +} + +class KNetwork::KReverseResolverPrivate +{ +public: + QString node; + QString service; + KSocketAddress addr; + int flags; + + ReverseThread* worker; + bool success; + + inline KReverseResolverPrivate(const KSocketAddress& _addr) + : addr(_addr), worker(0L), success(false) + { } +}; + +KReverseResolver::KReverseResolver(const KSocketAddress& addr, int flags, + QObject *parent, const char* name) + : QObject(parent, name), d(new KReverseResolverPrivate(addr)) +{ + d->flags = flags; +} + +KReverseResolver::~KReverseResolver() +{ + if (d->worker) + d->worker->m_parent = 0L; +} + +bool KReverseResolver::isRunning() const +{ + return d->worker != 0L; +} + +bool KReverseResolver::success() const +{ + return !isRunning() && d->success; +} + +bool KReverseResolver::failure() const +{ + return !isRunning() && !d->success; +} + +QString KReverseResolver::node() const +{ + return d->node; +} + +QString KReverseResolver::service() const +{ + return d->service; +} + +const KSocketAddress& KReverseResolver::address() const +{ + return d->addr; +} + +bool KReverseResolver::start() +{ + if (d->worker != 0L) + return true; // already started + + d->worker = new ReverseThread(d->addr, d->flags); + d->worker->m_parent = this; + + RequestData *req = new RequestData; + req->obj = 0L; + req->input = 0L; + req->requestor = 0L; + req->worker = d->worker; + KResolverManager::manager()->dispatch(req); + return true; +} + +bool KReverseResolver::event(QEvent *e) +{ + if (e->type() != KReverseResolverEvent::myType) + return QObject::event(e); // call parent + + KReverseResolverEvent *re = static_cast<KReverseResolverEvent*>(e); + d->node = re->node; + d->service = re->service; + d->success = re->success; + + // don't delete d->worker! + // KResolverManager::doNotifying takes care of that, if it hasn't already + d->worker = 0L; + + // emit signal + emit finished(*this); + + return true; +} + +bool KReverseResolver::resolve(const KSocketAddress& addr, QString& node, + QString& serv, int flags) +{ + ReverseThread th(addr, flags); + if (th.run()) + { + node = th.node; + serv = th.service; + return true; + } + return false; +} + +bool KReverseResolver::resolve(const struct sockaddr* sa, Q_UINT16 salen, + QString& node, QString& serv, int flags) +{ + return resolve(KSocketAddress(sa, salen), node, serv, flags); +} + +bool ReverseThread::run() +{ + int err; + char h[NI_MAXHOST], s[NI_MAXSERV]; + int niflags = 0; + + h[0] = s[0] = '\0'; + + if (m_flags & KReverseResolver::NumericHost) + niflags |= NI_NUMERICHOST; + if (m_flags & KReverseResolver::NumericService) + niflags |= NI_NUMERICSERV; + if (m_flags & KReverseResolver::NodeNameOnly) + niflags |= NI_NOFQDN; + if (m_flags & KReverseResolver::Datagram) + niflags |= NI_DGRAM; + if (m_flags & KReverseResolver::ResolutionRequired) + niflags |= NI_NAMEREQD; + + { +#ifdef NEED_MUTEX + QMutexLocker locker(&::getXXbyYYmutex); +#endif + err = ::getnameinfo(m_addr, m_addr.length(), + h, sizeof(h) - 1, s, sizeof(s) - 1, niflags); + } + + if (err == 0) + { + node = KResolver::domainToUnicode(QString::fromLatin1(h)); + service = QString::fromLatin1(s); + success = true; + } + else + { + node = service = QString::null; + success = false; + } + + return success; +} + +bool ReverseThread::postprocess() +{ + // post an event + if (m_parent) + QApplication::postEvent(m_parent, + new KReverseResolverEvent(node, service, success)); + return true; +} + +#include "kreverseresolver.moc" diff --git a/kdecore/network/kreverseresolver.h b/kdecore/network/kreverseresolver.h new file mode 100644 index 000000000..325d97d82 --- /dev/null +++ b/kdecore/network/kreverseresolver.h @@ -0,0 +1,195 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KREVERSERESOLVER_H +#define KREVERSERESOLVER_H + +////////////////// +// Needed includes +#include <qobject.h> +#include <qstring.h> + +#include "ksocketaddress.h" + +namespace KNetwork { + +class KReverseResolverPrivate; +/** @class KReverseResolver kreverseresolver.h kreverseresolver.h + * @brief Run a reverse-resolution on a socket address. + * + * This class is provided as a counterpart to KResolver in such a way + * as it produces a reverse resolution: it resolves a socket address + * from its binary representations into a textual representation. + * + * Most users will use the static functions @ref resolve, which work + * both synchronously (blocking) and asynchronously (non-blocking). + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KReverseResolver: public QObject +{ + Q_OBJECT + +public: + /** + * Flags for the reverse resolution. + * + * These flags are used by the reverse resolution functions for + * setting resolution parameters. The possible values are: + * @li NumericHost: don't try to resolve the host address to a text form. + * Instead, convert the address to its numeric textual representation. + * @li NumericService: the same as NumericHost, but for the service name + * @li NodeNameOnly: returns the node name only (i.e., not the Fully + * Qualified Domain Name) + * @li Datagram: in case of ambiguity in the service name, prefer the + * name associated with the datagram protocol + * @li NumericScope: for those addresses which have the concept of scope, + * resolve using the numeric value instead of the proper scope name. + * @li ResolutionRequired: normally, when resolving, if the name resolution + * fails, the process normally converts the numeric address into its + * presentation forms. This flag causes the function to return + * with error instead. + */ + enum Flags + { + NumericHost = 0x01, + NumericService = 0x02, + NodeNameOnly = 0x04, + Datagram = 0x08, + NumericScope = 0x10, + ResolutionRequired = 0x20 + }; + + /** + * Constructs this object to resolve the given socket address. + * + * @param addr the address to resolve + * @param flags the flags to use, see @ref Flags + */ + KReverseResolver(const KSocketAddress& addr, int flags = 0, + QObject * = 0L, const char * = 0L); + + /** + * Destructor. + */ + virtual ~KReverseResolver(); + + /** + * This function returns 'true' if the processing is still running. + */ + bool isRunning() const; + + /** + * This function returns true if the processing has finished with + * success, false if it's still running or failed. + */ + bool success() const; + + /** + * This function returns true if the processing has finished with + * failure, false if it's still running or succeeded. + */ + bool failure() const; + + /** + * Returns the resolved node name, if the resolution has finished + * successfully, or QString::null otherwise. + */ + QString node() const; + + /** + * Returns the resolved service name, if the resolution has finished + * successfully, or QString::null otherwise. + */ + QString service() const; + + /** + * Returns the socket address which was subject to resolution. + */ + const KSocketAddress& address() const; + + /** + * Starts the resolution. This function returns 'true' + * if the resolution has started successfully. + */ + bool start(); + + /** + * Overrides event handling + */ + virtual bool event(QEvent* ); + +signals: + /** + * This signal is emitted when the resolution has finished. + * + * @param obj this class, which contains the results + */ + void finished(const KReverseResolver& obj); + +public: + /** + * Resolves a socket address to its textual representation + * + * FIXME!! How can we do this in a non-blocking manner!? + * + * This function is used to resolve a socket address from its + * binary representation to a textual form, even if numeric only. + * + * @param addr the socket address to be resolved + * @param node the QString where we will store the resolved node + * @param serv the QString where we will store the resolved service + * @param flags flags to be used for this resolution. + * @return true if the resolution succeeded, false if not + * @see ReverseFlags for the possible values for @p flags + */ + static bool resolve(const KSocketAddress& addr, QString& node, + QString& serv, int flags = 0); + + /** + * Resolves a socket address to its textual representation + * + * FIXME!! How can we do this in a non-blocking manner!? + * + * This function behaves just like the above one, except it takes + * a sockaddr structure and its size as parameters. + * + * @param sa the sockaddr structure containing the address to be resolved + * @param salen the length of the sockaddr structure + * @param node the QString where we will store the resolved node + * @param serv the QString where we will store the resolved service + * @param flags flags to be used for this resolution. + * @return true if the resolution succeeded, false if not + * @see ReverseFlags for the possible values for @p flags + */ + static bool resolve(const struct sockaddr* sa, Q_UINT16 salen, + QString& node, QString& serv, int flags = 0); + +private: + KReverseResolverPrivate* d; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/kserversocket.cpp b/kdecore/network/kserversocket.cpp new file mode 100644 index 000000000..9f32b4119 --- /dev/null +++ b/kdecore/network/kserversocket.cpp @@ -0,0 +1,413 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <qsocketnotifier.h> +#include <qmutex.h> + +#include "ksocketaddress.h" +#include "kresolver.h" +#include "ksocketbase.h" +#include "ksocketdevice.h" +#include "kstreamsocket.h" +#include "kbufferedsocket.h" +#include "kserversocket.h" + +using namespace KNetwork; + +class KNetwork::KServerSocketPrivate +{ +public: + KResolver resolver; + KResolverResults resolverResults; + + enum { None, LookupDone, Bound, Listening } state; + int backlog; + int timeout; + + bool bindWhenFound : 1, listenWhenBound : 1, useKBufferedSocket : 1; + + KServerSocketPrivate() + : state(None), timeout(0), bindWhenFound(false), listenWhenBound(false), + useKBufferedSocket(true) + { + resolver.setFlags(KResolver::Passive); + resolver.setFamily(KResolver::KnownFamily); + } +}; + +KServerSocket::KServerSocket(QObject* parent, const char *name) + : QObject(parent, name), d(new KServerSocketPrivate) +{ + QObject::connect(&d->resolver, SIGNAL(finished(KResolverResults)), + this, SLOT(lookupFinishedSlot())); +} + +KServerSocket::KServerSocket(const QString& service, QObject* parent, const char *name) + : QObject(parent, name), d(new KServerSocketPrivate) +{ + QObject::connect(&d->resolver, SIGNAL(finished(KResolverResults)), + this, SLOT(lookupFinishedSlot())); + d->resolver.setServiceName(service); +} + +KServerSocket::KServerSocket(const QString& node, const QString& service, + QObject* parent, const char* name) + : QObject(parent, name), d(new KServerSocketPrivate) +{ + QObject::connect(&d->resolver, SIGNAL(finished(KResolverResults)), + this, SLOT(lookupFinishedSlot())); + setAddress(node, service); +} + +KServerSocket::~KServerSocket() +{ + close(); + delete d; +} + +bool KServerSocket::setSocketOptions(int opts) +{ + QMutexLocker locker(mutex()); + KSocketBase::setSocketOptions(opts); // call parent + bool result = socketDevice()->setSocketOptions(opts); // and set the implementation + copyError(); + return result; +} + +KResolver& KServerSocket::resolver() const +{ + return d->resolver; +} + +const KResolverResults& KServerSocket::resolverResults() const +{ + return d->resolverResults; +} + +void KServerSocket::setResolutionEnabled(bool enable) +{ + if (enable) + d->resolver.setFlags(d->resolver.flags() & ~KResolver::NoResolve); + else + d->resolver.setFlags(d->resolver.flags() | KResolver::NoResolve); +} + +void KServerSocket::setFamily(int families) +{ + d->resolver.setFamily(families); +} + +void KServerSocket::setAddress(const QString& service) +{ + d->resolver.setNodeName(QString::null); + d->resolver.setServiceName(service); + d->resolverResults.empty(); + if (d->state <= KServerSocketPrivate::LookupDone) + d->state = KServerSocketPrivate::None; +} + +void KServerSocket::setAddress(const QString& node, const QString& service) +{ + d->resolver.setNodeName(node); + d->resolver.setServiceName(service); + d->resolverResults.empty(); + if (d->state <= KServerSocketPrivate::LookupDone) + d->state = KServerSocketPrivate::None; +} + +void KServerSocket::setTimeout(int msec) +{ + d->timeout = msec; +} + +bool KServerSocket::lookup() +{ + setError(NoError); + if (d->resolver.isRunning() && !blocking()) + return true; // already doing lookup + + if (d->state >= KServerSocketPrivate::LookupDone) + return true; // results are already available + + // make sure we have at least one parameter for lookup + if (d->resolver.serviceName().isNull() && + !d->resolver.nodeName().isNull()) + d->resolver.setServiceName(QString::fromLatin1("")); + + // don't restart the lookups if they had succeeded and + // the input values weren't changed + + // reset results + d->resolverResults = KResolverResults(); + + if (d->resolver.status() <= 0) + // if it's already running, there's no harm in calling again + d->resolver.start(); // signal may emit + + if (blocking()) + { + // we're in blocking mode operation + // wait for the results + + d->resolver.wait(); // signal may be emitted again + // lookupFinishedSlot has been called + } + + return true; +} + +bool KServerSocket::bind(const KResolverEntry& address) +{ + if (socketDevice()->bind(address)) + { + setError(NoError); + + d->state = KServerSocketPrivate::Bound; + emit bound(address); + return true; + } + copyError(); + return false; +} + +bool KServerSocket::bind(const QString& node, const QString& service) +{ + setAddress(node, service); + return bind(); +} + +bool KServerSocket::bind(const QString& service) +{ + setAddress(service); + return bind(); +} + +bool KServerSocket::bind() +{ + if (d->state >= KServerSocketPrivate::Bound) + return true; + + if (d->state < KServerSocketPrivate::LookupDone) + { + if (!blocking()) + { + d->bindWhenFound = true; + bool ok = lookup(); // will call doBind + if (d->state >= KServerSocketPrivate::Bound) + d->bindWhenFound = false; + return ok; + } + + // not blocking + if (!lookup()) + return false; + } + + return doBind(); +} + +bool KServerSocket::listen(int backlog) +{ + // WARNING + // this function has to be reentrant + // due to the mechanisms used for binding, this function might + // end up calling itself + + if (d->state == KServerSocketPrivate::Listening) + return true; // already listening + + d->backlog = backlog; + + if (d->state < KServerSocketPrivate::Bound) + { + // we must bind + // note that we can end up calling ourselves here + d->listenWhenBound = true; + if (!bind()) + { + d->listenWhenBound = false; + return false; + } + + if (d->state < KServerSocketPrivate::Bound) + // asynchronous lookup in progress... + // we can't be blocking here anyways + return true; + + d->listenWhenBound = false; + } + + if (d->state < KServerSocketPrivate::Listening) + return doListen(); + + return true; +} + +void KServerSocket::close() +{ + socketDevice()->close(); + if (d->resolver.isRunning()) + d->resolver.cancel(false); + d->state = KServerSocketPrivate::None; + emit closed(); +} + +void KServerSocket::setAcceptBuffered(bool enable) +{ + d->useKBufferedSocket = enable; +} + +KActiveSocketBase* KServerSocket::accept() +{ + if (d->state < KServerSocketPrivate::Listening) + { + if (!blocking()) + { + listen(); + setError(WouldBlock); + return NULL; + } + else if (!listen()) + // error happened during listen + return false; + } + + // check to see if we're doing a timeout + if (blocking() && d->timeout > 0) + { + bool timedout; + if (!socketDevice()->poll(d->timeout, &timedout)) + { + copyError(); + return NULL; + } + + if (timedout) + return 0L; + } + + // we're listening here + KSocketDevice* accepted = socketDevice()->accept(); + if (!accepted) + { + // error happened during accept + copyError(); + return NULL; + } + + KStreamSocket* streamsocket; + if (d->useKBufferedSocket) + streamsocket = new KBufferedSocket(); + else + streamsocket = new KStreamSocket(); + streamsocket->setSocketDevice(accepted); + + // FIXME! + // when KStreamSocket can find out the state of the socket passed through + // setSocketDevice, this will probably be unnecessary: + streamsocket->setState(KStreamSocket::Connected); + streamsocket->setFlags(IO_Sequential | IO_Raw | IO_ReadWrite | IO_Open | IO_Async); + + return streamsocket; +} + +KSocketAddress KServerSocket::localAddress() const +{ + return socketDevice()->localAddress(); +} + +KSocketAddress KServerSocket::externalAddress() const +{ + return socketDevice()->externalAddress(); +} + +void KServerSocket::lookupFinishedSlot() +{ + if (d->resolver.isRunning() || d->state > KServerSocketPrivate::LookupDone) + return; + + if (d->resolver.status() < 0) + { + setError(LookupFailure); + emit gotError(LookupFailure); + d->bindWhenFound = d->listenWhenBound = false; + d->state = KServerSocketPrivate::None; + return; + } + + // lookup succeeded + d->resolverResults = d->resolver.results(); + d->state = KServerSocketPrivate::LookupDone; + emit hostFound(); + + if (d->bindWhenFound) + doBind(); +} + +void KServerSocket::copyError() +{ + setError(socketDevice()->error()); +} + +bool KServerSocket::doBind() +{ + d->bindWhenFound = false; + // loop through the results and bind to the first that works + + KResolverResults::ConstIterator it = d->resolverResults.begin(); + for ( ; it != d->resolverResults.end(); ++it) + if (bind(*it)) + { + if (d->listenWhenBound) + return doListen(); + return true; + } + else + socketDevice()->close(); // didn't work, try again + + // failed to bind + emit gotError(error()); + return false; +} + +bool KServerSocket::doListen() +{ + if (!socketDevice()->listen(d->backlog)) + { + copyError(); + emit gotError(error()); + return false; // failed to listen + } + + // set up ready accept signal + QObject::connect(socketDevice()->readNotifier(), SIGNAL(activated(int)), + this, SIGNAL(readyAccept())); + d->state = KServerSocketPrivate::Listening; + return true; +} + + +#include "kserversocket.moc" diff --git a/kdecore/network/kserversocket.h b/kdecore/network/kserversocket.h new file mode 100644 index 000000000..27b3df1cc --- /dev/null +++ b/kdecore/network/kserversocket.h @@ -0,0 +1,435 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago@kde.org> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KSERVERSOCKET_H +#define KSERVERSOCKET_H + +#include <qobject.h> +#include "ksocketbase.h" + +namespace KNetwork { + +class KSocketDevice; +class KStreamSocket; +class KResolver; +class KResolverResults; + +class KServerSocketPrivate; +/** + * @class KServerSocket kserversocket.h kserversocket.h + * @brief A server socket for accepting connections. + * + * This class provides functionality for creating a socket to + * listen for incoming connections and subsequently accept them. + * + * To use this class, you must first set the parameters for the listening + * socket's address, then place it in listening mode. + * + * A typical example would look like: + * \code + * QString service = "http"; + * KServerSocket *ss = new KServerSocket(service); + * connect(ss, SIGNAL(readyAccept()), this, SLOT(slotReadyAccept())); + * connect(ss, SIGNAL(gotError(int)), this, SLOT(slotSocketError(int))); + * ss->listen(); + * \endcode + * + * In this case, this class will place the socket into listening mode on the + * service pointed to by @p service and will emit the @ref readyAccept signal + * when a connection is ready for accepting. The called slot is responsible for + * calling @ref accept. + * + * The location of the services file (where @p service is looked up) + * is defined by _PATH_SERVICES in /usr/include/netdb.h. This is + * usually set to /etc/services. + * See RFC 1700 for more information on services. + * You can specify @p service as a port number directly, rather than as a service + * name. This is discouraged as it prevents the end user from easily modifying + * the port number. + * + * For another example of usage, this below code attempts to make a connection on any port within a range: + * \code + * KServerSocket *ss = new KServerSocket(); + * ss->setFamily(KResolver::InetFamily); + * bool found = false; + * for( unsigned int port = firstport; port <= lastport; ++port) { + * ss->setAddress( QString::number( port ) ); + * bool success = ss->listen(); + * if( found = ( success && ss->error() == + * KSocketBase::NoError ) ) + * break; + * ss->close(); + * } + * if( !found ) { + * // Couldn't connect to any port. + * } else { + * connect(ss, SIGNAL(readyAccept()), this, SLOT(slotReadyAccept())); + * connect(ss, SIGNAL(gotError(int)), this, SLOT(slotSocketError(int))); + * ss->listen(); + * } + * \endcode + * + * The called slot slotReadyAccept() is responsible for calling + * @ref accept. + * + * It is important to note that @ref accept can return either an + * object of type KNetwork::KStreamSocket or + * KNetwork::KBufferedSocket (default). If you want to accept a + * non-buffered socket, you must first call setAcceptBuffered. + * + * @warning If you use KServerSocket in an auxiliary (non-GUI) thread, + * you need to accept only KNetwork::KStreamSocket objects. + * + * @see KNetwork::KStreamSocket, KNetwork::KBufferedSocket + * @author Thiago Macieira <thiago@kde.org> + */ +class KDECORE_EXPORT KServerSocket: public QObject, public KPassiveSocketBase +{ + Q_OBJECT +public: + /** + * Default constructor. + * + * If the binding address isn't changed by setAddress, this socket will + * bind to all interfaces on this node and the port will be selected by the + * operating system. + * + * @param parent the parent QObject object + * @param name the name of this object + */ + KServerSocket(QObject* parent = 0L, const char *name = 0L); + + /** + * Construct this object specifying the service to listen on. + * + * If the binding address isn't changed by setAddress, this socket will + * bind to all interfaces and will listen on the port specified by + * @p service. This is either a service name (e.g. 'www') or a port + * number (e.g. '80'). + * + * The location of the services file (where @p service is looked up) + * is defined by _PATH_SERVICES in /usr/include/netdb.h. This is + * usually set to /etc/services. + * See RFC 1700 for more information on services. + * + * @param service the service name to listen on + * @param parent the parent QObject object + * @param name the name of this object + */ + KServerSocket(const QString& service, QObject* parent = 0L, const char *name = 0L); + + /** + * Construct this object specifying the node and service names to listen on. + * + * If the binding address isn't changed by setAddress, this socket will + * bind to the interface specified by @p node and the port specified by + * @p service. This is either a service name (e.g. 'www') or a port + * number (e.g. '80'). + * + * The location of the services file (where @p service is looked up) + * is defined by _PATH_SERVICES in /usr/include/netdb.h. This is + * usually set to /etc/services. + * See RFC 1700 for more information on services. + * + * @param node the node to bind to + * @param service the service port to listen on + * @param parent the parent QObject object + * @param name the name of this object + */ + KServerSocket(const QString& node, const QString& service, + QObject* parent = 0L, const char *name = 0L); + + /** + * Destructor. This will close the socket, if open. + * + * Note, however, that accepted sockets do not get closed when this + * object closes. + */ + ~KServerSocket(); + +protected: + /** + * Sets the socket options. Reimplemented from KSocketBase. + */ + virtual bool setSocketOptions(int opts); + +public: + /** + * Returns the internal KResolver object used for + * looking up the host name and service. + * + * This can be used to set extra options to the + * lookup process other than the default values, as well + * as obtaining the error codes in case of lookup failure. + */ + KResolver& resolver() const; + + /** + * Returns the internal list of resolved results for the binding address. + */ + const KResolverResults& resolverResults() const; + + /** + * Enables or disables name resolution. If this flag is set to true, + * the @ref bind operation will trigger name lookup + * operations (i.e., converting a hostname into its binary form). + * If the flag is set to false, those operations will instead + * try to convert a string representation of an address without + * attempting name resolution. + * + * This is useful, for instance, when IP addresses are in + * their string representation (such as "1.2.3.4") or come + * from other sources like @ref KSocketAddress. + * + * @param enable whether to enable + */ + void setResolutionEnabled(bool enable); + + /** + * Sets the allowed families for the resolutions. + * + * @param families the families that we want/accept + * @see KResolver::SocketFamilies for possible values + */ + void setFamily(int families); + + /** + * Sets the address on which we will listen. The port to listen on is given by + * @p service, and we will bind to all interfaces. To let the operating system choose a + * port, set the service to "0". @p service can either be a service name + * (e.g. 'www') or a port number (e.g. '80'). + * + * The location of the services file (where @p service is looked up) + * is defined by _PATH_SERVICES in /usr/include/netdb.h. This is + * usually set to /etc/services. + * See RFC 1700 for more information on services. + * + * @param service the service name to listen on + */ + void setAddress(const QString& service); + + /** + * @overload + * Sets the address on which we will listen. This will cause the socket to listen + * only on the interface given by @p node and on the port given by @p service. + * @p service can either be a service name (e.g. 'www') or a port number + * (e.g. '80'). + * + * The location of the services file (where @p service is looked up) + * is defined by _PATH_SERVICES in /usr/include/netdb.h. This is + * usually set to /etc/services. + * See RFC 1700 for more information on services. + * + * @param node the node to bind to + * @param service the service port to listen on + */ + void setAddress(const QString& node, const QString& service); + + /** + * Sets the timeout for accepting. When you call @ref accept, + * it will wait at most @p msecs milliseconds or return with an error + * (returning a NULL object). + * + * @param msecs the time in milliseconds to wait, 0 to wait forever + */ + void setTimeout(int msecs); + + /** + * Starts the lookup for peer and local hostnames as + * well as their services. + * + * If the blocking mode for this object is on, this function will + * wait for the lookup results to be available (by calling the + * @ref KResolver::wait method on the resolver objects). + * + * When the lookup is done, the signal @ref hostFound will be + * emitted (only once, even if we're doing a double lookup). + * If the lookup failed (for any of the two lookups) the + * @ref gotError signal will be emitted with the appropriate + * error condition (see @ref KSocketBase::SocketError). + * + * This function returns true on success and false on error. Note that + * this is not the lookup result! + */ + virtual bool lookup(); + + /** + * Binds this socket to the given nodename and service, + * or use the default ones if none are given. + * + * Upon successful binding, the @ref bound signal will be + * emitted. If an error is found, the @ref gotError + * signal will be emitted. + * + * This function returns true on success. + * + * @param node the nodename + * @param service the service + */ + virtual bool bind(const QString& node, const QString& service); + + /** + * Binds the socket to the given service name. + * @overload + * + * @param service the service + */ + virtual bool bind(const QString& service); + + /** + * Binds the socket to the addresses previously set with @ref setAddress. + * @overload + * + */ + virtual bool bind(); + + /** + * Connect this socket to this specific address. Reimplemented from KSocketBase. + * + * Unlike @ref bind(const QString&, const QString&) above, this function + * really does bind the socket. No lookup is performed. The @ref bound signal + * will be emitted. + */ + virtual bool bind(const KResolverEntry& address); + + /** + * Puts this socket into listening mode. Reimplemented from @ref KPassiveSocketBase. + * + * Placing a socket into listening mode means it will be able to receive incoming + * connections through the @ref accept method. + * + * If you do not call this method but call @ref accept directly, the socket will + * be placed into listening mode automatically. + * + * @param backlog the number of connection the system is to + * queue without @ref accept being called + * @returns true if the socket is now in listening mode. + */ + virtual bool listen(int backlog = 5); // 5 is arbitrary + + /** + * Closes this socket. + */ + virtual void close(); + + /** + * Toggles whether the accepted socket will be buffered or not. + * That is, the @ref accept function will always return a KStreamSocket + * object or descended from it. If buffering is enabled, the class + * to be returned will be KBufferedSocket. + * + * By default, this flag is set to true. + * + * @param enable whether to set the accepted socket to + * buffered mode + */ + void setAcceptBuffered(bool enable); + + /** + * Accepts one incoming connection and return the associated, open + * socket. + * + * If this function cannot accept a new connection, it will return NULL. + * The specific object class returned by this function may vary according + * to the implementation: derived classes may return specialised objects + * descended from KStreamSocket. + * + * @note This function should return a KStreamSocket object, but compiler + * deficiencies prevent such an adjustment. Therefore, we return + * the base class for active sockets, but it is guaranteed + * that the object will be a KStreamSocket or derived from it. + * + * @sa KBufferedSocket + * @sa setAcceptBuffered + */ + virtual KActiveSocketBase* accept(); + + /** + * Returns this socket's local address. + */ + virtual KSocketAddress localAddress() const; + + /** + * Returns this socket's externally-visible address if know. + */ + virtual KSocketAddress externalAddress() const; + +private slots: + void lookupFinishedSlot(); + +signals: + /** + * This signal is emitted when this object finds an error. + * The @p code parameter contains the error code that can + * also be found by calling @ref error. + */ + void gotError(int code); + + /** + * This signal is emitted when the lookup is successfully completed. + */ + void hostFound(); + + /** + * This signal is emitted when the socket successfully binds + * to an address. + * + * @param local the local address we bound to + */ + void bound(const KResolverEntry& local); + + /** + * This signal is emitted when the socket completes the + * closing/shut down process. + */ + void closed(); + + /** + * This signal is emitted whenever the socket is ready for + * accepting -- i.e., there is at least one connection waiting to + * be accepted. + */ + void readyAccept(); + +protected: + /** + * Convenience function to set this object's error code to match + * that of the socket device. + */ + void copyError(); + +private: + bool doBind(); + bool doListen(); + +private: + KServerSocket(const KServerSocket&); + KServerSocket& operator=(const KServerSocket&); + + KServerSocketPrivate *d; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/ksocketaddress.cpp b/kdecore/network/ksocketaddress.cpp new file mode 100644 index 000000000..6c1316e94 --- /dev/null +++ b/kdecore/network/ksocketaddress.cpp @@ -0,0 +1,957 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include <qfile.h> +#include <qobject.h> + +#include "klocale.h" +#include "ksocketaddress.h" + +#include "netsupp.h" + +using namespace KNetwork; + +#if 0 +class KIpAddress_localhostV4 : public KIpAddress +{ +public: + KIpAddress_localhostV4() + { + *m_data = htonl(0x7f000001); + m_version = 4; + } +}; + +class KIpAddress_localhostV6 : public KIpAddress +{ +public: + KIpAddress_localhostV6() + : KIpAddress(0L, 6) + { + m_data[3] = htonl(1); + } +}; +#endif + +static const char localhostV4_data[] = { 127, 0, 0, 1 }; +static const char localhostV6_data[] = { 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,0, 0,1 }; + +const KIpAddress KIpAddress::localhostV4(&localhostV4_data, 4); +const KIpAddress KIpAddress::localhostV6(&localhostV6_data, 6); +const KIpAddress KIpAddress::anyhostV4(0L, 4); +const KIpAddress KIpAddress::anyhostV6(0L, 6); + +// helper function to test if an IPv6 v4-mapped address is equal to its IPv4 counterpart +static bool check_v4mapped(const Q_UINT32* v6addr, Q_UINT32 v4addr) +{ + // check that the v6 is a v4-mapped address + if (!(v6addr[0] == 0 && v6addr[1] == 0 && v6addr[2] == htonl(0x0000ffff))) + return false; // not a v4-mapped address + + return v6addr[3] == v4addr; +} + +// copy operator +KIpAddress& KIpAddress::operator =(const KIpAddress& other) +{ + m_version = other.m_version; + if (m_version == 4 || m_version == 6) + memcpy(m_data, other.m_data, sizeof(m_data)); + return *this; +} + +// comparison +bool KIpAddress::compare(const KIpAddress& other, bool checkMapped) const +{ + if (m_version == other.m_version) + switch (m_version) + { + case 0: + // both objects are empty + return true; + + case 4: + // IPv4 address + return *m_data == *other.m_data; + + case 6: + // IPv6 address + // they are 128-bit long, that is, 16 bytes + return memcmp(m_data, other.m_data, 16) == 0; + } + + if (checkMapped) + { + // check the possibility of a v4-mapped address being compared to an IPv4 one + if (m_version == 6 && other.m_version == 4 && check_v4mapped(m_data, *other.m_data)) + return true; + + if (other.m_version == 6 && m_version == 4 && check_v4mapped(other.m_data, *m_data)) + return true; + } + + return false; +} + +// sets the address to the given address +bool KIpAddress::setAddress(const QString& address) +{ + m_version = 0; + + // try to guess the address version + if (address.find(':') != -1) + { +#ifdef AF_INET6 + // guessing IPv6 + + Q_UINT32 buf[4]; + if (inet_pton(AF_INET6, address.latin1(), buf)) + { + memcpy(m_data, buf, sizeof(m_data)); + m_version = 6; + return true; + } +#endif + + return false; + } + else + { + Q_UINT32 buf; + if (inet_pton(AF_INET, address.latin1(), &buf)) + { + *m_data = buf; + m_version = 4; + return true; + } + + return false; + } + + return false; // can never happen! +} + +bool KIpAddress::setAddress(const char* address) +{ + return setAddress(QString::fromLatin1(address)); +} + +// set from binary data +bool KIpAddress::setAddress(const void* raw, int version) +{ + // this always succeeds + // except if version is invalid + if (version != 4 && version != 6) + return false; + + m_version = version; + if (raw != 0L) + memcpy(m_data, raw, version == 4 ? 4 : 16); + else + memset(m_data, 0, 16); + + return true; +} + +// presentation form +QString KIpAddress::toString() const +{ + char buf[sizeof "1111:2222:3333:4444:5555:6666:255.255.255.255" + 2]; + buf[0] = '\0'; + switch (m_version) + { + case 4: + inet_ntop(AF_INET, m_data, buf, sizeof(buf) - 1); + return QString::fromLatin1(buf); + + case 6: +#ifdef AF_INET6 + inet_ntop(AF_INET6, m_data, buf, sizeof(buf) - 1); +#endif + return QString::fromLatin1(buf); + } + + return QString::null; +} + +Q_UINT32 KIpAddress::hostIPv4Addr(bool convertMapped) const +{ + Q_UINT32 addr = IPv4Addr(convertMapped); + return ntohl(addr); +} + +/* + * An IPv6 socket address + * This is taken from RFC 2553. + */ +struct our_sockaddr_in6 +{ +# ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + Q_UINT8 sin6_len; + Q_UINT8 sin6_family; +# else //!HAVE_STRUCT_SOCKADDR_SA_LEN + Q_UINT16 sin6_family; +# endif + Q_UINT16 sin6_port; /* RFC says in_port_t */ + Q_UINT32 sin6_flowinfo; + Q_UINT8 sin6_addr[16]; // 24 bytes up to here + Q_UINT32 sin6_scope_id; // 28 bytes total +}; + +// useful definitions +#define MIN_SOCKADDR_LEN sizeof(Q_UINT16) +#define SOCKADDR_IN_LEN sizeof(sockaddr_in) +#define MIN_SOCKADDR_IN6_LEN ((unsigned long) &(((our_sockaddr_in6*)0)->sin6_scope_id)) +#define SOCKADDR_IN6_LEN sizeof(our_sockaddr_in6) +#define MIN_SOCKADDR_UN_LEN (sizeof(Q_UINT16) + sizeof(char)) + + +class KNetwork::KSocketAddressData +{ +public: + /* + * Note: maybe this should be virtual + * But since the data is shared via the d pointer, it doesn't really matter + * what one class sees, so will the other + */ + class QMixSocketAddressRef : public KInetSocketAddress, public KUnixSocketAddress + { + public: + QMixSocketAddressRef(KSocketAddressData* d) + : KInetSocketAddress(d), KUnixSocketAddress(d) + { + } + }; + QMixSocketAddressRef ref; + + union + { + struct sockaddr *generic; + struct sockaddr_in *in; + struct our_sockaddr_in6 *in6; + struct sockaddr_un *un; + } addr; + Q_UINT16 curlen, reallen; + + KSocketAddressData() + : ref(this) + { + addr.generic = 0L; + curlen = 0; + invalidate(); + } + + ~KSocketAddressData() + { + if (addr.generic != 0L) + free(addr.generic); + } + + inline bool invalid() const + { return reallen == 0; } + + inline void invalidate() + { reallen = 0; } + + void dup(const sockaddr* sa, Q_UINT16 len, bool clear = true); + + void makeipv4() + { + short oldport = 0; + if (!invalid()) + switch (addr.generic->sa_family) + { + case AF_INET: + return; // nothing to do here +#ifdef AF_INET6 + case AF_INET6: + oldport = addr.in6->sin6_port; + break; +#endif + } + + // create new space + dup(0L, SOCKADDR_IN_LEN); + + addr.in->sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + addr.in->sin_len = SOCKADDR_IN_LEN; +#endif + addr.in->sin_port = oldport; + } + + void makeipv6() + { + short oldport = 0; + if (!invalid()) + switch (addr.generic->sa_family) + { + case AF_INET: + oldport = addr.in->sin_port; + break; + +#ifdef AF_INET6 + case AF_INET6: + return; // nothing to do here +#endif + } + + // make room + dup(0L, SOCKADDR_IN6_LEN); +#ifdef AF_INET6 + addr.in6->sin6_family = AF_INET6; +#endif +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + addr.in6->sin6_len = SOCKADDR_IN6_LEN; +#endif + addr.in6->sin6_port = oldport; + // sin6_scope_id and sin6_flowid are zero + } + +}; + +// create duplicates of +void KSocketAddressData::dup(const sockaddr* sa, Q_UINT16 len, bool clear) +{ + if (len < MIN_SOCKADDR_LEN) + { + // certainly invalid + invalidate(); + return; + } + + if (sa && ((sa->sa_family == AF_INET && len < SOCKADDR_IN_LEN) || +#ifdef AF_INET6 + (sa->sa_family == AF_INET6 && len < MIN_SOCKADDR_IN6_LEN) || +#endif + (sa->sa_family == AF_UNIX && len < MIN_SOCKADDR_UN_LEN))) + { + // also invalid + invalidate(); + return; + } + + // good + reallen = len; + if (len > curlen) + { + if (len < 32) + curlen = 32; // big enough for sockaddr_in and sockaddr_in6 + else + curlen = len; + addr.generic = (sockaddr*)realloc(addr.generic, curlen); + } + + if (sa != 0L) + { + memcpy(addr.generic, sa, len); // copy + + // now, normalise the data + if (addr.generic->sa_family == AF_INET) + reallen = SOCKADDR_IN_LEN; // no need to be larger +#ifdef AF_INET6 + else if (addr.generic->sa_family == AF_INET6) + { + // set the extra field (sin6_scope_id) + + // the buffer is never smaller than 32 bytes, so this is always + // allowed + if (reallen < SOCKADDR_IN6_LEN) + addr.in6->sin6_scope_id = 0; + + reallen = SOCKADDR_IN6_LEN; + } +#endif + else if (addr.generic->sa_family == AF_UNIX) + reallen = MIN_SOCKADDR_UN_LEN + strlen(addr.un->sun_path); + } + else if (clear) + { + memset(addr.generic, 0, len); + addr.generic->sa_family = AF_UNSPEC; + } +} + +// default constructor +KSocketAddress::KSocketAddress() + : d(new KSocketAddressData) +{ +} + +// constructor from binary data +KSocketAddress::KSocketAddress(const sockaddr *sa, Q_UINT16 len) + : d(new KSocketAddressData) +{ + setAddress(sa, len); +} + +KSocketAddress::KSocketAddress(const KSocketAddress& other) + : d(new(KSocketAddressData)) +{ + *this = other; +} + +KSocketAddress::KSocketAddress(KSocketAddressData *d2) + : d(d2) +{ +} + +KSocketAddress::~KSocketAddress() +{ + // prevent double-deletion, since we're already being deleted + if (d) + { + d->ref.KInetSocketAddress::d = 0L; + d->ref.KUnixSocketAddress::d = 0L; + delete d; + } +} + +KSocketAddress& KSocketAddress::operator =(const KSocketAddress& other) +{ + if (other.d && !other.d->invalid()) + d->dup(other.d->addr.generic, other.d->reallen); + else + d->invalidate(); + return *this; +} + +const sockaddr* KSocketAddress::address() const +{ + if (d->invalid()) + return 0L; + return d->addr.generic; +} + +sockaddr* KSocketAddress::address() +{ + if (d->invalid()) + return 0L; + return d->addr.generic; +} + +KSocketAddress& KSocketAddress::setAddress(const sockaddr* sa, Q_UINT16 len) +{ + if (sa != 0L && len >= MIN_SOCKADDR_LEN) + d->dup(sa, len); + else + d->invalidate(); + + return *this; +} + +Q_UINT16 KSocketAddress::length() const +{ + if (d->invalid()) + return 0; + return d->reallen; +} + +KSocketAddress& KSocketAddress::setLength(Q_UINT16 len) +{ + d->dup((sockaddr*)0L, len, false); + + return *this; +} + +int KSocketAddress::family() const +{ + if (d->invalid()) + return AF_UNSPEC; + return d->addr.generic->sa_family; +} + +KSocketAddress& KSocketAddress::setFamily(int family) +{ + if (d->invalid()) + d->dup((sockaddr*)0L, MIN_SOCKADDR_LEN); + d->addr.generic->sa_family = family; + + return *this; +} + +bool KSocketAddress::operator ==(const KSocketAddress& other) const +{ + // if this is invalid, it's only equal if the other one is invalid as well + if (d->invalid()) + return other.d->invalid(); + + // check the family to make sure we don't do unnecessary comparison + if (d->addr.generic->sa_family != other.d->addr.generic->sa_family) + return false; // not the same family, not equal + + // same family then + // check the ones we know already + switch (d->addr.generic->sa_family) + { + case AF_INET: + Q_ASSERT(d->reallen == SOCKADDR_IN_LEN); + Q_ASSERT(other.d->reallen == SOCKADDR_IN_LEN); + return memcmp(d->addr.in, other.d->addr.in, SOCKADDR_IN_LEN) == 0; + +#ifdef AF_INET6 + case AF_INET6: + Q_ASSERT(d->reallen >= MIN_SOCKADDR_IN6_LEN); + Q_ASSERT(other.d->reallen >= MIN_SOCKADDR_IN6_LEN); + +# if !defined(HAVE_STRUCT_SOCKADDR_IN6) || defined(HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID) + // check for the case where sin6_scope_id isn't present + if (d->reallen != other.d->reallen) + { + if (memcmp(d->addr.in6, other.d->addr.in6, MIN_SOCKADDR_IN6_LEN) != 0) + return false; // not equal + if (d->reallen > other.d->reallen) + return d->addr.in6->sin6_scope_id == 0; + else + return other.d->addr.in6->sin6_scope_id == 0; + } +# endif + + return memcmp(d->addr.in6, other.d->addr.in6, d->reallen) == 0; +#endif + + case AF_UNIX: + Q_ASSERT(d->reallen >= MIN_SOCKADDR_UN_LEN); + Q_ASSERT(other.d->reallen >= MIN_SOCKADDR_UN_LEN); + + // do a string comparison here + return strcmp(d->addr.un->sun_path, other.d->addr.un->sun_path) == 0; + + default: + // something else we don't know about + // they are equal if and only if they are exactly equal + if (d->reallen == other.d->reallen) + return memcmp(d->addr.generic, other.d->addr.generic, d->reallen) == 0; + } + + return false; // not equal in any other case +} + +QString KSocketAddress::nodeName() const +{ + if (d->invalid()) + return QString::null; + + switch (d->addr.generic->sa_family) + { + case AF_INET: +#ifdef AF_INET6 + case AF_INET6: + + QString scopeid("%"); + if (d->addr.generic->sa_family == AF_INET6 && d->addr.in6->sin6_scope_id) + scopeid += QString::number(d->addr.in6->sin6_scope_id); + else + scopeid.truncate(0); + return d->ref.ipAddress().toString() + scopeid; +#else + return d->ref.ipAddress().toString(); +#endif + } + + // any other case, including AF_UNIX + return QString::null; +} + +QString KSocketAddress::serviceName() const +{ + if (d->invalid()) + return QString::null; + + switch (d->addr.generic->sa_family) + { + case AF_INET: +#ifdef AF_INET6 + case AF_INET6: +#endif + return QString::number(d->ref.port()); + + case AF_UNIX: + return d->ref.pathname(); + } + + return QString::null; +} + +QString KSocketAddress::toString() const +{ + if (d->invalid()) + return QString::null; + + QString fmt; + + if (d->addr.generic->sa_family == AF_INET) + fmt = "%1:%2"; +#ifdef AF_INET6 + else if (d->addr.generic->sa_family == AF_INET6) + fmt = "[%1]:%2"; +#endif + else if (d->addr.generic->sa_family == AF_UNIX) + return QString::fromLatin1("unix:%1").arg(serviceName()); + else + return i18n("1: the unknown socket address family number", + "Unknown family %1").arg(d->addr.generic->sa_family); + + return fmt.arg(nodeName()).arg(serviceName()); +} + +KInetSocketAddress& KSocketAddress::asInet() +{ + return d->ref; +} + +KInetSocketAddress KSocketAddress::asInet() const +{ + return d->ref; +} + +KUnixSocketAddress& KSocketAddress::asUnix() +{ + return d->ref; +} + +KUnixSocketAddress KSocketAddress::asUnix() const +{ + return d->ref; +} + +int KSocketAddress::ianaFamily(int af) +{ + switch (af) + { + case AF_INET: + return 1; + +#ifdef AF_INET6 + case AF_INET6: + return 2; +#endif + + default: + return 0; + } +} + +int KSocketAddress::fromIanaFamily(int iana) +{ + switch (iana) + { + case 1: + return AF_INET; + +#ifdef AF_INET6 + case 2: + return AF_INET6; +#endif + + default: + return AF_UNSPEC; + } +} + +// default constructor +KInetSocketAddress::KInetSocketAddress() +{ +} + +// binary data constructor +KInetSocketAddress::KInetSocketAddress(const sockaddr* sa, Q_UINT16 len) + : KSocketAddress(sa, len) +{ + if (!d->invalid()) + update(); +} + +// create from IP and port +KInetSocketAddress::KInetSocketAddress(const KIpAddress& host, Q_UINT16 port) +{ + setHost(host); + setPort(port); +} + +// copy constructor +KInetSocketAddress::KInetSocketAddress(const KInetSocketAddress& other) + : KSocketAddress(other) +{ +} + +// special copy constructor +KInetSocketAddress::KInetSocketAddress(const KSocketAddress& other) + : KSocketAddress(other) +{ + if (!d->invalid()) + update(); +} + +// special constructor +KInetSocketAddress::KInetSocketAddress(KSocketAddressData *d) + : KSocketAddress(d) +{ +} + +// destructor +KInetSocketAddress::~KInetSocketAddress() +{ + /* nothing to do */ +} + +// copy operator +KInetSocketAddress& KInetSocketAddress::operator =(const KInetSocketAddress& other) +{ + KSocketAddress::operator =(other); + return *this; +} + +// IP version +int KInetSocketAddress::ipVersion() const +{ + if (d->invalid()) + return 0; + + switch (d->addr.generic->sa_family) + { + case AF_INET: + return 4; + +#ifdef AF_INET6 + case AF_INET6: + return 6; +#endif + } + + return 0; // for all other cases +} + +KIpAddress KInetSocketAddress::ipAddress() const +{ + if (d->invalid()) + return KIpAddress(); // return an empty address as well + + switch (d->addr.generic->sa_family) + { + case AF_INET: + return KIpAddress(&d->addr.in->sin_addr, 4); +#ifdef AF_INET6 + case AF_INET6: + return KIpAddress(&d->addr.in6->sin6_addr, 6); +#endif + } + + return KIpAddress(); // empty in all other cases +} + +KInetSocketAddress& KInetSocketAddress::setHost(const KIpAddress& ip) +{ + switch (ip.version()) + { + case 4: + makeIPv4(); + memcpy(&d->addr.in->sin_addr, ip.addr(), sizeof(d->addr.in->sin_addr)); + break; + + case 6: + makeIPv6(); + memcpy(&d->addr.in6->sin6_addr, ip.addr(), sizeof(d->addr.in6->sin6_addr)); + break; + + default: + // empty + d->invalidate(); + } + + return *this; +} + +// returns the port +Q_UINT16 KInetSocketAddress::port() const +{ + if (d->invalid()) + return 0; + + switch (d->addr.generic->sa_family) + { + case AF_INET: + return ntohs(d->addr.in->sin_port); + +#ifdef AF_INET6 + case AF_INET6: + return ntohs(d->addr.in6->sin6_port); +#endif + } + + return 0; +} + +KInetSocketAddress& KInetSocketAddress::setPort(Q_UINT16 port) +{ + if (d->invalid()) + makeIPv4(); + + switch (d->addr.generic->sa_family) + { + case AF_INET: + d->addr.in->sin_port = htons(port); + break; + +#ifdef AF_INET6 + case AF_INET6: + d->addr.in6->sin6_port = htons(port); + break; +#endif + + default: + d->invalidate(); // setting the port on something else + } + + return *this; +} + +KInetSocketAddress& KInetSocketAddress::makeIPv4() +{ + d->makeipv4(); + return *this; +} + +KInetSocketAddress& KInetSocketAddress::makeIPv6() +{ + d->makeipv6(); + return *this; +} + +Q_UINT32 KInetSocketAddress::flowinfo() const +{ +#ifndef AF_INET6 + return 0; +#else + + if (!d->invalid() && d->addr.in6->sin6_family == AF_INET6) + return d->addr.in6->sin6_flowinfo; + return 0; +#endif +} + +KInetSocketAddress& KInetSocketAddress::setFlowinfo(Q_UINT32 flowinfo) +{ + makeIPv6(); // must set here + d->addr.in6->sin6_flowinfo = flowinfo; + return *this; +} + +int KInetSocketAddress::scopeId() const +{ +#ifndef AF_INET6 + return 0; +#else + + if (!d->invalid() && d->addr.in6->sin6_family == AF_INET6) + return d->addr.in6->sin6_scope_id; + return 0; +#endif +} + +KInetSocketAddress& KInetSocketAddress::setScopeId(int scopeid) +{ + makeIPv6(); // must set here + d->addr.in6->sin6_scope_id = scopeid; + return *this; +} + +void KInetSocketAddress::update() +{ + if (d->addr.generic->sa_family == AF_INET) + return; +#ifdef AF_INET6 + else if (d->addr.generic->sa_family == AF_INET6) + return; +#endif + else + d->invalidate(); +} + +KUnixSocketAddress::KUnixSocketAddress() +{ +} + +KUnixSocketAddress::KUnixSocketAddress(const sockaddr* sa, Q_UINT16 len) + : KSocketAddress(sa, len) +{ + if (!d->invalid() && d->addr.un->sun_family != AF_UNIX) + d->invalidate(); +} + +KUnixSocketAddress::KUnixSocketAddress(const KUnixSocketAddress& other) + : KSocketAddress(other) +{ +} + +KUnixSocketAddress::KUnixSocketAddress(const QString& pathname) +{ + setPathname(pathname); +} + +KUnixSocketAddress::KUnixSocketAddress(KSocketAddressData* d) + : KSocketAddress(d) +{ +} + +KUnixSocketAddress::~KUnixSocketAddress() +{ +} + +KUnixSocketAddress& KUnixSocketAddress::operator =(const KUnixSocketAddress& other) +{ + KSocketAddress::operator =(other); + return *this; +} + +QString KUnixSocketAddress::pathname() const +{ + if (!d->invalid() && d->addr.un->sun_family == AF_UNIX) + return QFile::decodeName(d->addr.un->sun_path); + return QString::null; +} + +KUnixSocketAddress& KUnixSocketAddress::setPathname(const QString& path) +{ + d->dup(0L, MIN_SOCKADDR_UN_LEN + path.length()); + d->addr.un->sun_family = AF_UNIX; + strcpy(d->addr.un->sun_path, QFile::encodeName(path)); + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + d->addr.un->sun_len = d->reallen; +#endif + + return *this; +} diff --git a/kdecore/network/ksocketaddress.h b/kdecore/network/ksocketaddress.h new file mode 100644 index 000000000..456422f9f --- /dev/null +++ b/kdecore/network/ksocketaddress.h @@ -0,0 +1,912 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KSOCKETADDRESS_H +#define KSOCKETADDRESS_H + +#include <qstring.h> +#include <qcstring.h> + +#include <kdelibs_export.h> + +struct sockaddr; +struct sockaddr_in; +struct sockaddr_in6; +struct sockaddr_un; + +namespace KNetwork { + +class KIpAddress; +class KSocketAddress; +class KInetSocketAddress; +class KUnixSocketAddress; + +/** @class KIpAddress ksocketaddress.h ksocketaddress.h + * @brief An IP address. + * + * This class represents one IP address, version 4 or 6. This is only + * the address, not including port information or other data. + * + * It is not a good programming practice to create address from objects + * like this. Instead, prefer a more thorough function like + * @ref KResolver::resolve, which also handle extra information like scope + * ids. + * + * This is a light-weight class. Most of the member functions are inlined and + * there are no virtual functions. This object's size should be less than 20 + * bytes. Also note that there is no sharing of data. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KIpAddress +{ +public: + /** + * Default constructor. Creates an empty address. + * It defaults to IP version 4. + */ + inline KIpAddress() : m_version(0) + { } + + /** + * Copy constructor. Copies the data from the other + * object. + * + * Data is not shared. + * + * @param other the other + */ + inline KIpAddress(const KIpAddress& other) + { *this = other; } + + /** + * Creates an object from the given string representation. + * + * The IP version is guessed from the address format. + * + * @param addr the address + */ + inline KIpAddress(const QString& addr) + { setAddress(addr); } + + /** + * Creates an object from the given string representation. + * + * The IP version is guessed from the address format. + * + * @param addr the address + */ + inline KIpAddress(const char* addr) + { setAddress(addr); } + + /** + * Creates an object from the given raw data and IP version. + * + * @param addr the raw data + * @param version the IP version (4 or 6) + */ + inline KIpAddress(const void* addr, int version = 4) + { setAddress(addr, version); } + + /** + * This is a convenience constructor. Constructs an object + * from the given IPv4 address in the form of an integer. + * + * Note: do not write code to depend on IPv4 addresses being + * integer types. Instead, treat them as a special type, like + * a KIpAddress or the system's in_addr. + * + * @param ip4addr the IPv4 address + */ + inline KIpAddress(Q_UINT32 ip4addr) + { setAddress(&ip4addr, 4); } + + /** + * Destructor. This frees resources associated with this object. + * + * Note: destructor is non-virtual. The compiler will happily optimise it + * out of the way. + */ + inline ~KIpAddress() + { } + + /** + * Copy operator. + * + * Copies the data from the other object into this one. + * + * @param other the object to copy + */ + KIpAddress& operator =(const KIpAddress& other); + + /** + * Returns true if the two addresses match. + * This function performs a v4-mapped check. + * @see compare + */ + inline bool operator ==(const KIpAddress& other) const + { return compare(other, true); } + + /** + * Compares this address against the other, supplied one and return + * true if they match. The @p checkMapped parameter controls whether + * a check for an IPv6 v4-mapped address will be performed. + * + * An IPv6 v4-mapped address is an IPv6 address that is, for all purposes, + * equivalent to an IPv4 one. The default behaviour of this function + * is to take that into account. If you want a strict matching, + * pass @b false to the @p checkMapped parameter. + * + * @param other the other IP address + * @param checkMapped whether v4-mapped addresses will be taken into account + */ + bool compare(const KIpAddress& other, bool checkMapped = true) const; + + /** + * Retrieves the IP version in this object. + * + * @returns the version: 4 or 6 + */ + inline int version() const + { return m_version; } + + /** + * Returns true if this is an IPv4 address. + */ + inline bool isIPv4Addr() const + { return version() == 4; } + + /** + * Returns true if this is an IPv6 address. + */ + inline bool isIPv6Addr() const + { return version() == 6; } + + /** + * Sets the address to the given string representation. + * + * @return true if the address was successfully parsed; otherwise returns + * false and leaves the object unchanged. + */ + bool setAddress(const QString& address); + + /** + * Sets the address to the given string representation. + * + * @return true if the address was successfully parsed; otherwise returns + * false and leaves the object unchanged. + */ + bool setAddress(const char* address); + + /** + * Sets the address to the given raw binary representation. + * + * @param raw a pointer to the raw binary data + * @param version the IP version + * @return true if the address was successfully parsed; otherwise returns + * false and leaves the object unchanged. + */ + bool setAddress(const void* raw, int version = 4); + + /** + * Returns the address as a string. + */ + QString toString() const; + + /** + * Returns a pointer to binary raw data representing the address. + */ + inline const void *addr() const + { return m_data; } + + /** + * This is a convenience function. Returns the IPv4 address in a + * 32-bit integer. The result is only valid if @ref isIPv4Addr returns + * true. Alternatively, if the contained IPv6 address is a v4-mapped one + * and the @p convertMapped parameter is true, the result will also be + * valid. The address returned is in network byte order. + * + * Note: you should not treat IP addresses as integers. Instead, + * use types defined for that purpose, such as KIpAddress or the + * system's in_addr type. + * + */ + inline Q_UINT32 IPv4Addr(bool convertMapped = true) const + { + return (convertMapped && isV4Mapped()) ? m_data[3] : m_data[0]; + } + + /** + * This is a convenience function. Returns the IPv4 address in a + * 32-bit integer. The result is only valid if @ref isIPv4Addr returns + * true. Alternatively, if the contained IPv6 address is a v4-mapped one + * and the @p convertMapped parameter is true, the result will also be + * valid. The address returned is in host byte order. + * + */ + Q_UINT32 hostIPv4Addr(bool convertMapped = true) const; + +public: + /*-- tests --*/ + + /** + * Returns true if this is the IPv4 or IPv6 unspecified address. + */ + inline bool isUnspecified() const + { return version() == 0 ? true : (*this == anyhostV4 || *this == anyhostV6); } + + /** + * Returns true if this is either the IPv4 or the IPv6 localhost address. + */ + inline bool isLocalhost() const + { return version() == 0 ? false : (*this == localhostV4 || *this == localhostV6); } + + /** + * This is an alias for @ref isLocalhost. + */ + inline bool isLoopback() const + { return isLocalhost(); } + + /** + * Returns true if this is an IPv4 class A address, i.e., + * from 0.0.0.0 to 127.255.255.255. + * + * This function does not test for v4-mapped addresses. + */ + inline bool isClassA() const + { return version() != 4 ? false : (hostIPv4Addr() & 0x80000000) == 0; } + + /** + * Returns true if this is an IPv4 class B address, i.e., one from + * 128.0.0.0 to 191.255.255.255. + * + * This function does not test for v4-mapped addresses. + */ + inline bool isClassB() const + { return version() != 4 ? false : (hostIPv4Addr() & 0xc0000000) == 0x80000000; } + + /** + * Returns true if this is an IPv4 class C address, i.e., one from + * 192.0.0.0 to 223.255.255.255. + * + * This function does not test for v4-mapped addresses. + */ + inline bool isClassC() const + { return version() != 4 ? false : (hostIPv4Addr() & 0xe0000000) == 0xc0000000; } + + /** + * Returns true if this is an IPv4 class D (a.k.a. multicast) address. + * + * Note: this function is not the same as @ref isMulticast. isMulticast also + * tests for IPv6 multicast addresses. + */ + inline bool isClassD() const + { return version() != 4 ? false : (hostIPv4Addr() & 0xf0000000) == 0xe0000000; } + + /** + * Returns true if this is a multicast address, be it IPv4 or IPv6. + */ + inline bool isMulticast() const + { + if (version() == 4) return isClassD(); + if (version() == 6) return ((Q_UINT8*)addr())[0] == 0xff; + return false; + } + + /** + * Returns true if this is an IPv6 link-local address. + */ + inline bool isLinkLocal() const + { + if (version() != 6) return false; + Q_UINT8* addr = (Q_UINT8*)this->addr(); + return (addr[0] & 0xff) == 0xfe && + (addr[1] & 0xc0) == 0x80; + } + + /** + * Returns true if this is an IPv6 site-local address. + */ + inline bool isSiteLocal() const + { + if (version() != 6) return false; + Q_UINT8* addr = (Q_UINT8*)this->addr(); + return (addr[0] & 0xff) == 0xfe && + (addr[1] & 0xc0) == 0xc0; + } + + /** + * Returns true if this is a global IPv6 address. + */ + inline bool isGlobal() const + { return version() != 6 ? false : !(isMulticast() || isLinkLocal() || isSiteLocal()); } + + /** + * Returns true if this is a v4-mapped IPv6 address. + */ + inline bool isV4Mapped() const + { + if (version() != 6) return false; + Q_UINT32* addr = (Q_UINT32*)this->addr(); + return addr[0] == 0 && addr[1] == 0 && + ((Q_UINT16*)&addr[2])[0] == 0 && + ((Q_UINT16*)&addr[2])[1] == 0xffff; + } + + /** + * Returns true if this is a v4-compat IPv6 address. + */ + inline bool isV4Compat() const + { + if (version() != 6 || isLocalhost()) return false; + Q_UINT32* addr = (Q_UINT32*)this->addr(); + return addr[0] == 0 && addr[1] == 0 && addr[2] == 0 && addr[3] != 0; + } + + /** + * Returns true if this is an IPv6 node-local multicast address. + */ + inline bool isMulticastNodeLocal() const + { return version() == 6 && isMulticast() && (((Q_UINT32*)addr())[0] & 0xf) == 0x1; } + + /** + * Returns true if this is an IPv6 link-local multicast address. + */ + inline bool isMulticastLinkLocal() const + { return version() == 6 && isMulticast() && (((Q_UINT32*)addr())[0] & 0xf) == 0x2; } + + /** + * Returns true if this is an IPv6 site-local multicast address. + */ + inline bool isMulticastSiteLocal() const + { return version() == 6 && isMulticast() && (((Q_UINT32*)addr())[0] & 0xf) == 0x5; } + + /** + * Returns true if this is an IPv6 organisational-local multicast address. + */ + inline bool isMulticastOrgLocal() const + { return version() == 6 && isMulticast() && (((Q_UINT32*)addr())[0] & 0xf) == 0x8; } + + /** + * Returns true if this is an IPv6 global multicast address. + */ + inline bool isMulticastGlobal() const + { return version() == 6 && isMulticast() && (((Q_UINT32*)addr())[0] & 0xf) == 0xe; } + +protected: + Q_UINT32 m_data[4]; // 16 bytes, needed for an IPv6 address + + char m_version; + +public: + /// localhost in IPv4 (127.0.0.1) + static const KIpAddress localhostV4; + /// the any host or undefined address in IPv4 (0.0.0.0) + static const KIpAddress anyhostV4; + + /// localhost in IPv6 (::1) + static const KIpAddress localhostV6; + /// the any host or undefined address in IPv6 (::) + static const KIpAddress anyhostV6; +}; + + +class KSocketAddressData; +/** @class KSocketAddress ksocketaddress.h ksocketaddress.h + * @brief A generic socket address. + * + * This class holds one generic socket address. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KSocketAddress +{ +public: + /** + * Default constructor. + * + * Creates an empty object + */ + KSocketAddress(); + + /** + * Creates this object with the given data. + * The raw socket address is copied into this object. + * + * @param sa the socket address structure + * @param len the socket address length + */ + KSocketAddress(const sockaddr* sa, Q_UINT16 len); + + /** + * Copy constructor. This creates a copy of the other + * object. + * + * Data is not shared. + * + * @param other the object to copy from + */ + KSocketAddress(const KSocketAddress& other); + + /** + * Destructor. Frees any associated resources. + */ + virtual ~KSocketAddress(); + + /** + * Performs a shallow copy of the other object into this one. + * Data will be copied. + * + * @param other the object to copy from + */ + KSocketAddress& operator =(const KSocketAddress& other); + + /** + * Returns the socket address structure, to be passed down to + * low level functions. + * + * Note that this function returns NULL for invalid or empty sockets, + * so you may use to to test for validity. + */ + const sockaddr* address() const; + + /** + * Returns the socket address structure, to be passed down to + * low level functions. + * + * Note that this function returns NULL for invalid or empty sockets, + * so you may use to to test for validity. + * + * The returned value, if not NULL, is an internal buffer which is guaranteed + * to be at least @ref length() bytes long. + */ + sockaddr* address(); + + /** + * Sets the address to the given address. + * The raw socket address is copied into this object. + * + * @param sa the socket address structure + * @param len the socket address length + */ + KSocketAddress& setAddress(const sockaddr *sa, Q_UINT16 len); + + /** + * Returns the socket address structure, to be passed down to + * low level functions. + */ + inline operator const sockaddr*() const + { return address(); } + + /** + * Returns the length of this socket address structure. + */ + Q_UINT16 length() const; + + /** + * Sets the length of this socket structure. + * + * Use this function with care. It allows you to resize the internal + * buffer to fit needs. This function should not be used except for handling + * unknown socket address structures. + * + * Also note that this function may invalidate the socket if a known + * family is set (Internet or Unix socket) and the new length would be + * too small to hold the system's sockaddr_* structure. If unsure, reset + * the family: + * + * \code + * KSocketAddress qsa; + * [...] + * qsa.setFamily(AF_UNSPEC).setLength(newlen); + * \endcode + * + * @param len the new length + */ + KSocketAddress& setLength(Q_UINT16 len); + + /** + * Returns the family of this address. + * @return the family of this address, AF_UNSPEC if it's undefined + */ + int family() const; + + /** + * Sets the family of this object. + * + * Note: setting the family will probably invalidate any address data + * contained in this object. Use this function with care. + * + * @param family the new family to set + */ + virtual KSocketAddress& setFamily(int family); + + /** + * Returns the IANA family number of this address. + * @return the IANA family number of this address (1 for AF_INET. + * 2 for AF_INET6, otherwise 0) + */ + inline int ianaFamily() const + { return ianaFamily(family()); } + + /** + * Returns true if this equals the other socket. + * + * Socket addresses are considered matching if and only if all data is the same. + * + * @param other the other socket + * @return true if both sockets are equal + */ + bool operator ==(const KSocketAddress& other) const; + + /** + * Returns the node name of this socket. + * + * In the case of Internet sockets, this is string representation of the IP address. + * The default implementation returns QString::null. + * + * @return the node name, can be QString::null + * @bug use KResolver to resolve unknown families + */ + virtual QString nodeName() const; + + /** + * Returns the service name for this socket. + * + * In the case of Internet sockets, this is the port number. + * The default implementation returns QString::null. + * + * @return the service name, can be QString::null + * @bug use KResolver to resolve unknown families + */ + virtual QString serviceName() const; + + /** + * Returns this socket address as a string suitable for + * printing. Family, node and service are part of this address. + * + * @bug use KResolver to resolve unknown families + */ + virtual QString toString() const; + + /** + * Returns an object reference that can be used to manipulate this socket + * as an Internet socket address. Both objects share the same data. + */ + KInetSocketAddress& asInet(); + + /** + * Returns an object is equal to this object's data, but they don't share it. + */ + KInetSocketAddress asInet() const; + + /** + * Returns an object reference that can be used to manipulate this socket + * as a Unix socket address. Both objects share the same data. + */ + KUnixSocketAddress& asUnix(); + + /** + * Returns an object is equal to this object's data, but they don't share it. + */ + KUnixSocketAddress asUnix() const; + +protected: + /// @internal + /// private data + KSocketAddressData *d; + + /// @internal + /// extra constructor + KSocketAddress(KSocketAddressData* d); + +public: // static + /** + * Returns the IANA family number of the given address family. + * Returns 0 if there is no corresponding IANA family number. + * @param af the address family, in AF_* constants + * @return the IANA family number of this address (1 for AF_INET. + * 2 for AF_INET6, otherwise 0) + */ + static int ianaFamily(int af); + + /** + * Returns the address family of the given IANA family number. + * @return the address family, AF_UNSPEC for unknown IANA family numbers + */ + static int fromIanaFamily(int iana); +}; + + +/** @class KInetSocketAddress ksocketaddress.h ksocketaddress.h + * @brief an Internet socket address + * + * An Inet (IPv4 or IPv6) socket address + * + * This is an IPv4 or IPv6 address of the Internet. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KInetSocketAddress: public KSocketAddress +{ + friend class KSocketAddress; +public: + /** + * Public constructor. Creates an empty object. + */ + KInetSocketAddress(); + + /** + * Creates an object from raw data. + * + * Note: if the socket address @p sa does not contain a valid Internet + * socket (IPv4 or IPv6), this object will be empty. + * + * @param sa the sockaddr structure + * @param len the structure's length + */ + KInetSocketAddress(const sockaddr* sa, Q_UINT16 len); + + /** + * Creates an object from an IP address and port. + * + * @param host the IP address + * @param port the port number + */ + KInetSocketAddress(const KIpAddress& host, Q_UINT16 port); + + /** + * Copy constructor. + * + * Data is not shared. + * + * @param other the other object + */ + KInetSocketAddress(const KInetSocketAddress& other); + + /** + * Copy constructor. + * + * If the other, generic socket address contains an Internet address, + * it will be copied. Otherwise, this object will be empty. + * + * @param other the other object + */ + KInetSocketAddress(const KSocketAddress& other); + + /** + * Destroys this object. + */ + virtual ~KInetSocketAddress(); + + /** + * Copy operator. + * + * Copies the other object into this one. + * + * @param other the other object + */ + KInetSocketAddress& operator =(const KInetSocketAddress& other); + + /** + * Cast operator to sockaddr_in. + */ + inline operator const sockaddr_in*() const + { return (const sockaddr_in*)address(); } + + /** + * Cast operator to sockaddr_in6. + */ + inline operator const sockaddr_in6*() const + { return (const sockaddr_in6*)address(); } + + /** + * Returns the IP version of the address this object holds. + * + * @return 4 or 6, if IPv4 or IPv6, respectively; 0 if this object is empty + */ + int ipVersion() const; + + /** + * Returns the IP address component. + */ + KIpAddress ipAddress() const; + + /** + * Sets the IP address to the given raw address. + * + * This call will preserve port numbers accross IP versions, but will lose + * IPv6 specific data if the address is set to IPv4. + * + * @param addr the address to set to + * @return a reference to itself + */ + KInetSocketAddress& setHost(const KIpAddress& addr); + + /** + * Retrieves the port number stored in this object. + * + * @return a port number in the range 0 to 65535, inclusive. An empty or + * invalid object will have a port number of 0. + */ + Q_UINT16 port() const; + + /** + * Sets the port number. If this object is empty, this function will default to + * creating an IPv4 address. + * + * @param port the port number to set + * @return a reference to itself + */ + KInetSocketAddress& setPort(Q_UINT16 port); + + /** + * Converts this object to an IPv4 socket address. It has no effect if the object + * is already an IPv4 socket address. + * + * If this object is an IPv6 address, the port number is preserved. All other information + * is lost. + * + * @return a reference to itself + */ + KInetSocketAddress& makeIPv4(); + + /** + * Converts this object to an IPv6 socket address. It has no effect if the object + * is already an IPv6 socket address. + * + * If this object is an IPv4 address, the port number is preserved. + * + * @return a reference to itself + */ + KInetSocketAddress& makeIPv6(); + + /** + * Returns the flowinfo information from the IPv6 socket address. + * + * @return the flowinfo information or 0 if this object is empty or IPv4 + */ + Q_UINT32 flowinfo() const; + + /** + * Sets the flowinfo information for an IPv6 socket address. If this is not + * an IPv6 socket address, this function converts it to one. See makeIPv6. + * + * @param flowinfo the flowinfo to set + * @return a reference to itself + */ + KInetSocketAddress& setFlowinfo(Q_UINT32 flowinfo); + + /** + * Returns the scope id this IPv6 socket is bound to. + * + * @return the scope id, or 0 if this is not an IPv6 object + */ + int scopeId() const; + + /** + * Sets the scope id for this IPv6 object. If this is not an IPv6 socket + * address, this function converts it to one. See makeIPv6 + * + * @param scopeid the scopeid to set + * @return a reference to itself + */ + KInetSocketAddress& setScopeId(int scopeid); + +protected: + /// @internal + /// extra constructor + KInetSocketAddress(KSocketAddressData* d); + +private: + void update(); +}; + +/* + * External definition + */ + +/** @class KUnixSocketAddress ksocketaddress.h ksocketaddress.h + * @brief A Unix (local) socket address. + * + * This is a Unix socket address. + * + * Note that this class uses QStrings to represent filenames, which means + * the proper encoding is used to translate into valid filesystem file names. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KUnixSocketAddress: public KSocketAddress +{ + friend class KSocketAddress; +public: + /** + * Default constructor. Creates an empty object. + */ + KUnixSocketAddress(); + + /** + * Creates this object with the given raw data. If + * the sockaddr structure does not contain a Local namespace + * (Unix) socket, this object will be created empty. + * + * @param sa the socket address structure + * @param len the structure's length + */ + KUnixSocketAddress(const sockaddr* sa, Q_UINT16 len); + + /** + * Copy constructor. Creates a copy of the other object, + * sharing the data explicitly. + * + * @param other the other object + */ + KUnixSocketAddress(const KUnixSocketAddress& other); + + /** + * Constructs an object from the given pathname. + */ + KUnixSocketAddress(const QString& pathname); + + /** + * Destructor. + */ + virtual ~KUnixSocketAddress(); + + /** + * Copy operator. Copies the contents of the other object into + * this one. Data is explicitly shared. + * + * @param other the other + */ + KUnixSocketAddress& operator =(const KUnixSocketAddress& other); + + /** + * Cast operator to sockaddr_un. + */ + inline operator const sockaddr_un*() const + { return (const sockaddr_un*)address(); } + + /** + * Returns the pathname associated with this object. Will return + * QString::null if this object is empty. + */ + QString pathname() const; + + /** + * Sets the pathname for the object. + * + * @return a reference to itself + */ + KUnixSocketAddress& setPathname(const QString& path); + +protected: + /// @internal + /// extra constructor + KUnixSocketAddress(KSocketAddressData* d); +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/ksocketbase.cpp b/kdecore/network/ksocketbase.cpp new file mode 100644 index 000000000..f3bfa766c --- /dev/null +++ b/kdecore/network/ksocketbase.cpp @@ -0,0 +1,327 @@ +/* -*- C++ -*- + * Copyright (C) 2003-2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> +#include <qmutex.h> +#include "klocale.h" + +#include "ksocketbase.h" +#include "ksocketdevice.h" + +using namespace KNetwork; + +class KNetwork::KSocketBasePrivate +{ +public: + int socketOptions; + int socketError; + int capabilities; + + mutable KSocketDevice* device; + + QMutex mutex; + + KSocketBasePrivate() + : mutex(true) // create recursive + { } +}; + +KSocketBase::KSocketBase() + : d(new KSocketBasePrivate) +{ + d->socketOptions = Blocking; + d->socketError = 0; + d->device = 0L; + d->capabilities = 0; +} + +KSocketBase::~KSocketBase() +{ + delete d->device; + delete d; +} + +bool KSocketBase::setSocketOptions(int opts) +{ + d->socketOptions = opts; + return true; +} + +int KSocketBase::socketOptions() const +{ + return d->socketOptions; +} + +bool KSocketBase::setBlocking(bool enable) +{ + return setSocketOptions((socketOptions() & ~Blocking) | (enable ? Blocking : 0)); +} + +bool KSocketBase::blocking() const +{ + return socketOptions() & Blocking; +} + +bool KSocketBase::setAddressReuseable(bool enable) +{ + return setSocketOptions((socketOptions() & ~AddressReuseable) | (enable ? AddressReuseable : 0)); +} + +bool KSocketBase::addressReuseable() const +{ + return socketOptions() & AddressReuseable; +} + +bool KSocketBase::setIPv6Only(bool enable) +{ + return setSocketOptions((socketOptions() & ~IPv6Only) | (enable ? IPv6Only : 0)); +} + +bool KSocketBase::isIPv6Only() const +{ + return socketOptions() & IPv6Only; +} + +bool KSocketBase::setBroadcast(bool enable) +{ + return setSocketOptions((socketOptions() & ~Broadcast) | (enable ? Broadcast : 0)); +} + +bool KSocketBase::broadcast() const +{ + return socketOptions() & Broadcast; +} + +KSocketDevice* KSocketBase::socketDevice() const +{ + if (d->device) + return d->device; + + // it doesn't exist, so create it + QMutexLocker locker(mutex()); + if (d->device) + return d->device; + + KSocketBase* that = const_cast<KSocketBase*>(this); + KSocketDevice* dev = 0; + if (d->capabilities) + dev = KSocketDevice::createDefault(that, d->capabilities); + if (!dev) + dev = KSocketDevice::createDefault(that); + that->setSocketDevice(dev); + return d->device; +} + +void KSocketBase::setSocketDevice(KSocketDevice* device) +{ + QMutexLocker locker(mutex()); + if (d->device == 0L) + d->device = device; +} + +int KSocketBase::setRequestedCapabilities(int add, int remove) +{ + d->capabilities |= add; + d->capabilities &= ~remove; + return d->capabilities; +} + +bool KSocketBase::hasDevice() const +{ + return d->device != 0L; +} + +void KSocketBase::setError(SocketError error) +{ + d->socketError = error; +} + +KSocketBase::SocketError KSocketBase::error() const +{ + return static_cast<KSocketBase::SocketError>(d->socketError); +} + +// static +QString KSocketBase::errorString(KSocketBase::SocketError code) +{ + QString reason; + switch (code) + { + case NoError: + reason = i18n("Socket error code NoError", "no error"); + break; + + case LookupFailure: + reason = i18n("Socket error code LookupFailure", + "name lookup has failed"); + break; + + case AddressInUse: + reason = i18n("Socket error code AddressInUse", + "address already in use"); + break; + + case AlreadyBound: + reason = i18n("Socket error code AlreadyBound", + "socket is already bound"); + break; + + case AlreadyCreated: + reason = i18n("Socket error code AlreadyCreated", + "socket is already created"); + break; + + case NotBound: + reason = i18n("Socket error code NotBound", + "socket is not bound"); + break; + + case NotCreated: + reason = i18n("Socket error code NotCreated", + "socket has not been created"); + break; + + case WouldBlock: + reason = i18n("Socket error code WouldBlock", + "operation would block"); + break; + + case ConnectionRefused: + reason = i18n("Socket error code ConnectionRefused", + "connection actively refused"); + break; + + case ConnectionTimedOut: + reason = i18n("Socket error code ConnectionTimedOut", + "connection timed out"); + break; + + case InProgress: + reason = i18n("Socket error code InProgress", + "operation is already in progress"); + break; + + case NetFailure: + reason = i18n("Socket error code NetFailure", + "network failure occurred"); + break; + + case NotSupported: + reason = i18n("Socket error code NotSupported", + "operation is not supported"); + break; + + case Timeout: + reason = i18n("Socket error code Timeout", + "timed operation timed out"); + break; + + case UnknownError: + reason = i18n("Socket error code UnknownError", + "an unknown/unexpected error has happened"); + break; + + case RemotelyDisconnected: + reason = i18n("Socket error code RemotelyDisconnected", + "remote host closed connection"); + break; + + default: + reason = QString::null; + break; + } + + return reason; +} + +// static +bool KSocketBase::isFatalError(int code) +{ + switch (code) + { + case WouldBlock: + case InProgress: + case NoError: + case RemotelyDisconnected: + return false; + } + + return true; +} + +void KSocketBase::unsetSocketDevice() +{ + d->device = 0L; +} + +QMutex* KSocketBase::mutex() const +{ + return &d->mutex; +} + +KActiveSocketBase::KActiveSocketBase() +{ +} + +KActiveSocketBase::~KActiveSocketBase() +{ +} + +int KActiveSocketBase::getch() +{ + unsigned char c; + if (readBlock((char*)&c, 1) != 1) + return -1; + + return c; +} + +int KActiveSocketBase::putch(int ch) +{ + unsigned char c = (unsigned char)ch; + if (writeBlock((char*)&c, 1) != 1) + return -1; + + return c; +} + +void KActiveSocketBase::setError(int status, SocketError error) +{ + KSocketBase::setError(error); + setStatus(status); +} + +void KActiveSocketBase::resetError() +{ + KSocketBase::setError(NoError); + resetStatus(); +} + +KPassiveSocketBase::KPassiveSocketBase() +{ +} + +KPassiveSocketBase::~KPassiveSocketBase() +{ +} diff --git a/kdecore/network/ksocketbase.h b/kdecore/network/ksocketbase.h new file mode 100644 index 000000000..acbbdf9b9 --- /dev/null +++ b/kdecore/network/ksocketbase.h @@ -0,0 +1,762 @@ +/* -*- C++ -*- + * Copyright (C) 2003,2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Even before our #ifdef, clean up the namespace + */ +#ifdef socket +#undef socket +#endif + +#ifdef bind +#undef bind +#endif + +#ifdef listen +#undef listen +#endif + +#ifdef connect +#undef connect +#endif + +#ifdef accept +#undef accept +#endif + +#ifdef getpeername +#undef getpeername +#endif + +#ifdef getsockname +#undef getsockname +#endif + +#ifndef KSOCKETBASE_H +#define KSOCKETBASE_H + +#include <qiodevice.h> +#include <qstring.h> + +#include "ksocketaddress.h" +#include <kdelibs_export.h> + +/* + * This is extending QIODevice's error codes + * + * According to qiodevice.h, the last error is IO_UnspecifiedError + * These errors will never occur in functions declared in QIODevice + * (except open, but you shouldn't call open) + */ +#define IO_ListenError (IO_UnspecifiedError+1) +#define IO_AcceptError (IO_UnspecifiedError+2) +#define IO_LookupError (IO_UnspecifiedError+3) +#define IO_SocketCreateError (IO_UnspecifiedError+4) +#define IO_BindError (IO_UnspecifiedError+5) + +class QMutex; + +namespace KNetwork { + +class KResolverEntry; +class KSocketDevice; + +class KSocketBasePrivate; +/** @class KSocketBase ksocketbase.h ksocketbase.h + * @brief Basic socket functionality. + * + * This class provides the basic socket functionlity for descended classes. + * Socket classes are thread-safe and provide a recursive mutex should it be + * needed. + * + * @note This class is abstract. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KSocketBase +{ +public: + /** + * Possible socket options. + * + * These are the options that may be set on a socket: + * - Blocking: whether the socket shall operate in blocking + * or non-blocking mode. This flag defaults to on. + * See @ref setBlocking. + * - AddressReusable: whether the address used by this socket will + * be available for reuse by other sockets. This flag defaults to off. + * See @ref setAddressReuseable. + * - IPv6Only: whether an IPv6 socket will accept IPv4 connections + * through a mapped address. This flag defaults to off. + * See @ref setIPv6Only. + * - KeepAlive: whether TCP should send keepalive probes when a connection + * has gone idle for far too long. + * - Broadcast: whether this socket is allowed to send broadcast packets + * and will receive packets sent to broadcast. + */ + enum SocketOptions + { + Blocking = 0x01, + AddressReuseable = 0x02, + IPv6Only = 0x04, + Keepalive = 0x08, + Broadcast = 0x10 + }; + + /** + * Possible socket error codes. + * + * This is a list of possible error conditions that socket classes may + * be expected to find. + * + * - NoError: no error has been detected + * - LookupFailure: if a name lookup has failed + * - AddressInUse: address is already in use + * - AlreadyBound: cannot bind again + * - AlreadyCreated: cannot recreate the socket + * - NotBound: operation required socket to be bound and it isn't + * - NotCreated: operation required socket to exist and it doesn't + * - WouldBlock: requested I/O operation would block + * - ConnectionRefused: connection actively refused + * - ConnectionTimedOut: connection timed out + * - InProgress: operation (connection) is already in progress + * - NetFailure: a network failure occurred (no route, host down, host unreachable or similar) + * - NotSupported: requested operation is not supported + * - Timeout: a timed operation timed out + * - UnknownError: an unknown/unexpected error has happened + * - RemotelyDisconnected: when a connection is disconnected by the other end (since 3.4) + * + * @sa error, errorString + */ + enum SocketError + { + NoError = 0, + LookupFailure, + AddressInUse, + AlreadyCreated, + AlreadyBound, + AlreadyConnected, + NotConnected, + NotBound, + NotCreated, + WouldBlock, + ConnectionRefused, + ConnectionTimedOut, + InProgress, + NetFailure, + NotSupported, + Timeout, + UnknownError, + RemotelyDisconnected + }; + +public: + /** + * Default constructor. + */ + KSocketBase(); + + /** + * Destructor. + */ + virtual ~KSocketBase(); + + /* + * The following functions are shared by all descended classes and will have + * to be reimplemented. + */ + +protected: + /** + * Set the given socket options. + * + * The default implementation does nothing but store the mask internally. + * Descended classes must override this function to achieve functionality and + * must also call this implementation. + * + * @param opts a mask of @ref SocketOptions or-ed bits of options to set + * or unset + * @returns true on success + * @note this function sets the options corresponding to the bits enabled in @p opts + * but will also unset the optiosn corresponding to the bits not set. + */ + virtual bool setSocketOptions(int opts); + + /** + * Retrieves the socket options that have been set. + * + * The default implementation just retrieves the mask from an internal variable. + * Descended classes may choose to override this function to read the values + * from the operating system. + * + * @returns the mask of the options set + */ + virtual int socketOptions() const; + +public: + /** + * Sets this socket's blocking mode. + * + * In blocking operation, all I/O functions are susceptible to blocking -- + * i.e., will not return unless the I/O can be satisfied. In non-blocking + * operation, if the I/O would block, the function will return an error + * and set the corresponding error code. + * + * The default implementation toggles the Blocking flag with the current + * socket options and calls @ref setSocketOptions. + * + * @param enable whether to set this socket to blocking mode + * @returns whether setting this value was successful; it is NOT the + * final blocking mode. + */ + virtual bool setBlocking(bool enable); + + /** + * Retrieves this socket's blocking mode. + * + * @returns true if this socket is/will be operated in blocking mode, + * false if non-blocking. + */ + bool blocking() const; + + /** + * Sets this socket's address reuseable flag. + * + * When the address reuseable flag is active, the address used by + * this socket is left reuseable for other sockets to bind. If + * the flag is not active, no other sockets may reuse the same + * address. + * + * The default implementation toggles the AddressReuseable flag with the current + * socket options and calls @ref setSocketOptions. + * + * @param enable whether to set the flag on or off + * @returns true if setting this flag was successful + */ + virtual bool setAddressReuseable(bool enable); + + /** + * Retrieves this socket's address reuseability flag. + * + * @returns true if this socket's address can be reused, + * false if it can't. + */ + bool addressReuseable() const; + + /** + * Sets this socket's IPv6 Only flag. + * + * When this flag is on, an IPv6 socket will only accept, connect, send to or + * receive from IPv6 addresses. When it is off, it will also talk to + * IPv4 addresses through v4-mapped addresses. + * + * This option has no effect on non-IPv6 sockets. + * + * The default implementation toggles the IPv6Only flag with the current + * socket options and calls @ref setSocketOptions. + * + * @param enable whether to set the flag on or off + * @returns true if setting this flag was successful + */ + virtual bool setIPv6Only(bool enable); + + /** + * Retrieves this socket's IPv6 Only flag. + * + * @returns true if this socket will ignore IPv4-compatible and IPv4-mapped + * addresses, false if it will accept them. + */ + bool isIPv6Only() const; + + /** + * Sets this socket Broadcast flag. + * + * Datagram-oriented sockets cannot normally send packets to broadcast + * addresses, nor will they receive packets that were sent to a broadcast + * address. To do so, you need to enable the Broadcast flag. + * + * This option has no effect on stream-oriented sockets. + * + * @returns true if setting this flag was successful. + */ + virtual bool setBroadcast(bool enable); + + /** + * Retrieves this socket's Broadcast flag. + * + * @returns true if this socket can send and receive broadcast packets, + * false if it can't. + */ + bool broadcast() const; + + /** + * Retrieves the socket implementation used on this socket. + * + * This function creates the device if none has been set + * using the default factory. + */ + KSocketDevice* socketDevice() const; + + /** + * Sets the socket implementation to be used on this socket. + * + * Note: it is an error to set this if the socket device has + * already been set once. + * + * This function is provided virtual so that derived classes can catch + * the setting of a device and properly set their own states and internal + * variables. The parent class must be called. + * + * This function is called by @ref socketDevice above when the socket is + * first created. + */ + virtual void setSocketDevice(KSocketDevice* device); + + /** + * Sets the internally requested capabilities for a socket device. + * + * Most socket classes can use any back-end implementation. However, a few + * may require specific capabilities not provided in the default + * implementation. By using this function, derived classes can request + * that a backend with those capabilities be created when necessary. + * + * For the possible flags, see @ref KSocketDevice::Capabilities. However, note + * that only the Can* flags make sense in this context. + * + * @note Since socketDevice must always return a valid backend object, it + * is is possible that the created device does not conform to all + * requirements requested. Implementations sensitive to this fact + * should test the object returned by @ref socketDevice (through + * @ref KSocketDevice::capabilities, for instance) the availability. + * + * @param add mask of @ref KSocketDevice::Capabilities to add + * @param remove mask of bits to remove from the requirements + * @return the current mask of requested capabilities + */ + int setRequestedCapabilities(int add, int remove = 0); + +protected: + /** + * Returns true if the socket device has been initialised in this + * object, either by calling @ref socketDevice() or @ref setSocketDevice + */ + bool hasDevice() const; + + /** + * Sets the socket's error code. + * + * @param error the error code + */ + void setError(SocketError error); + +public: + /** + * Retrieves the socket error code. + * @sa errorString + */ + SocketError error() const; + + /** + * Returns the error string corresponding to this error condition. + */ + inline QString errorString() const + { return errorString(error()); } + + /** + * Returns the internal mutex for this class. + * + * Note on multithreaded use of sockets: + * the socket classes are thread-safe by design, but you should be aware of + * problems regarding socket creation, connection and destruction in + * multi-threaded programs. The classes are guaranteed to work while + * the socket exists, but it's not wise to call connect in multiple + * threads. + * + * Also, this mutex must be unlocked before the object is destroyed, which + * means you cannot use it to guard against other threads accessing the object + * while destroying it. You must ensure there are no further references to this + * object when deleting it. + */ + QMutex* mutex() const; + +public: + /** + * Returns the string describing the given error code, i18n'ed. + * + * @param code the error code + */ + static QString errorString(SocketError code); + + /** + * Returns true if the given error code is a fatal one, false + * otherwise. The parameter here is of type int so that + * casting isn't necessary when using the parameter to signal + * QClientSocketBase::gotError. + * + * @param code the code to test + */ + static bool isFatalError(int code); + +private: + /// @internal + /// called by KSocketDevice + void unsetSocketDevice(); + + KSocketBase(const KSocketBase&); + KSocketBase& operator =(const KSocketBase&); + + KSocketBasePrivate *d; + + friend class KSocketDevice; +}; + +/** + * @class KActiveSocketBase ksocketbase.h ksocketbase.h + * @brief Abstract class for active sockets + * + * This class provides the standard interfaces for active sockets, i.e., + * sockets that are used to connect to external addresses. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KActiveSocketBase: public QIODevice, virtual public KSocketBase +{ +public: + /** + * Constructor. + */ + KActiveSocketBase(); + + /** + * Destructor. + */ + virtual ~KActiveSocketBase(); + + /** + * Binds this socket to the given address. + * + * The socket will be constructed with the address family, + * socket type and protocol as those given in the + * @p address object. + * + * @param address the address to bind to + * @returns true if the binding was successful, false otherwise + */ + virtual bool bind(const KResolverEntry& address) = 0; + + /** + * Connect to a remote host. + * + * This will make this socket try to connect to the remote host. + * If the socket is not yet created, it will be created using the + * address family, socket type and protocol specified in the + * @p address object. + * + * If this function returns with error InProgress, calling it + * again with the same address after a time will cause it to test + * if the connection has succeeded in the mean time. + * + * @param address the address to connect to + * @returns true if the connection was successful or has been successfully + * queued; false if an error occurred. + */ + virtual bool connect(const KResolverEntry& address) = 0; + + /** + * Disconnects this socket from a connection, if possible. + * + * If this socket was connected to an endpoint, the connection + * is severed, but the socket is not closed. If the socket wasn't + * connected, this function does nothing. + * + * If the socket hadn't yet been created, this function does nothing + * either. + * + * Not all socket types can disconnect. Most notably, only + * connectionless datagram protocols such as UDP support this operation. + * + * @return true if the socket is now disconnected or false on error. + */ + virtual bool disconnect() = 0; + + /** + * This call is not supported on sockets. Reimplemented from QIODevice. + * This will always return 0. + */ + virtual Offset size() const + { return 0; } + + /** + * This call is not supported on sockets. Reimplemented from QIODevice. + * This will always return 0. + */ + virtual Offset at() const + { return 0; } + + /** + * This call is not supported on sockets. Reimplemented from QIODevice. + * This will always return false. + */ + virtual bool at(Offset) + { return false; } + + /** + * This call is not supported on sockets. Reimplemented from QIODevice. + * This will always return true. + */ + virtual bool atEnd() const + { return true; } + + /** + * Returns the number of bytes available for reading without + * blocking. + */ + virtual Q_LONG bytesAvailable() const = 0; + + /** + * Waits up to @p msecs for more data to be available on this socket. + * + * If msecs is -1, this call will block indefinetely until more data + * is indeed available; if it's 0, this function returns immediately. + * + * If @p timeout is not NULL, this function will set it to indicate + * if a timeout occurred. + * + * @returns the number of bytes available + */ + virtual Q_LONG waitForMore(int msecs, bool *timeout = 0L) = 0; + + /** + * Reads data from the socket. + * + * Reimplemented from QIODevice. See QIODevice::readBlock for + * more information. + */ + virtual Q_LONG readBlock(char *data, Q_ULONG len) = 0; + + /** @overload + * Receives data and the source address. + * + * This call will read data in the socket and will also + * place the sender's address in @p from object. + * + * @param data where to write the read data to + * @param maxlen the maximum number of bytes to read + * @param from the address of the sender will be stored here + * @returns the actual number of bytes read + */ + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen, KSocketAddress& from) = 0; + + /** + * Peeks the data in the socket. + * + * This call will allow you to peek the data to be received without actually + * receiving it -- that is, it will be available for further peekings and + * for the next read call. + * + * @param data where to write the peeked data to + * @param maxlen the maximum number of bytes to peek + * @returns the actual number of bytes copied into @p data + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen) = 0; + + /** @overload + * Peeks the data in the socket and the source address. + * + * This call will allow you to peek the data to be received without actually + * receiving it -- that is, it will be available for further peekings and + * for the next read call. + * + * @param data where to write the peeked data to + * @param maxlen the maximum number of bytes to peek + * @param from the address of the sender will be stored here + * @returns the actual number of bytes copied into @p data + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen, KSocketAddress& from) = 0; + + /** + * Writes the given data to the socket. + * + * Reimplemented from QIODevice. See QIODevice::writeBlock for + * more information. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len) = 0; + + /** @overload + * Writes the given data to the destination address. + * + * Note that not all socket connections allow sending data to different + * addresses than the one the socket is connected to. + * + * @param data the data to write + * @param len the length of the data + * @param to the address to send to + * @returns the number of bytes actually sent + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to) = 0; + + /** + * Reads one character from the socket. + * Reimplementation from QIODevice. See QIODevice::getch for more information. + */ + virtual int getch(); + + /** + * Writes one character to the socket. + * Reimplementation from QIODevice. See QIODevice::putch for more information. + */ + virtual int putch(int ch); + + /** + * This call is not supported on sockets. Reimplemented from QIODevice. + * This will always return -1; + */ + virtual int ungetch(int) + { return -1; } + + /** + * Returns this socket's local address. + */ + virtual KSocketAddress localAddress() const = 0; + + /** + * Return this socket's peer address, if we are connected. + * If the address cannot be retrieved, the returned object will contain + * an invalid address. + */ + virtual KSocketAddress peerAddress() const = 0; + + // FIXME KDE 4.0: + // enable this function +#if 0 + /** + * Returns this socket's externally-visible address, if known. + */ + virtual KSocketAddress externalAddress() const = 0; +#endif + +protected: + /** + * Sets the socket's error code and the I/O Device's status. + * + * @param status the I/O Device status + * @param error the error code + */ + void setError(int status, SocketError error); + + /** + * Resets the socket error code and the I/O Device's status. + */ + void resetError(); +}; + +/** + * @class KPassiveSocketBase ksocketbase.h ksocketbase.h + * @brief Abstract base class for passive sockets. + * + * This socket provides the initial functionality for passive sockets, + * i.e., sockets that accept incoming connections. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KPassiveSocketBase: virtual public KSocketBase +{ +public: + /** + * Constructor + */ + KPassiveSocketBase(); + + /** + * Destructor + */ + virtual ~KPassiveSocketBase(); + + /** + * Binds this socket to the given address. + * + * The socket will be constructed with the address family, + * socket type and protocol as those given in the + * @p address object. + * + * @param address the address to bind to + * @returns true if the binding was successful, false otherwise + */ + virtual bool bind(const KResolverEntry& address) = 0; + + /** + * Puts this socket into listening mode. + * + * Placing a socket in listening mode means that it will + * be allowed to receive incoming connections from + * remote hosts. + * + * Note that some socket types or protocols cannot be + * put in listening mode. + * + * @param backlog the number of accepted connections to + * hold before starting to refuse + * @returns true if the socket is now in listening mode + */ + virtual bool listen(int backlog) = 0; + + /** + * Closes this socket. All resources used are freed. Note that closing + * a passive socket does not close the connections accepted with it. + */ + virtual void close() = 0; + + /** + * Accepts a new incoming connection. + * + * If this socket was in listening mode, you can call this + * function to accept an incoming connection. + * + * If this function cannot accept a new connection (either + * because it is not listening for one or because the operation + * would block), it will return NULL. + * + * Also note that descended classes will override this function + * to return specialised socket classes. + */ + virtual KActiveSocketBase* accept() = 0; + + /** + * Returns this socket's local address. + */ + virtual KSocketAddress localAddress() const = 0; + + /** + * Returns this socket's externally-visible address if known. + */ + virtual KSocketAddress externalAddress() const = 0; + +private: + KPassiveSocketBase(const KPassiveSocketBase&); + KPassiveSocketBase& operator = (const KPassiveSocketBase&); +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/ksocketbuffer.cpp b/kdecore/network/ksocketbuffer.cpp new file mode 100644 index 000000000..d458a4f15 --- /dev/null +++ b/kdecore/network/ksocketbuffer.cpp @@ -0,0 +1,329 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <assert.h> +#include <string.h> + +#include "ksocketbase.h" +#include "ksocketbuffer_p.h" + +using namespace KNetwork; +using namespace KNetwork::Internal; + +KSocketBuffer::KSocketBuffer(Q_LONG size) + : m_mutex(true), m_offset(0), m_size(size), m_length(0) +{ +} + +KSocketBuffer::KSocketBuffer(const KSocketBuffer& other) + : KIOBufferBase(other), m_mutex(true) +{ + *this = other; +} + +KSocketBuffer::~KSocketBuffer() +{ + // QValueList takes care of deallocating memory +} + +KSocketBuffer& KSocketBuffer::operator=(const KSocketBuffer& other) +{ + QMutexLocker locker1(&m_mutex); + QMutexLocker locker2(&other.m_mutex); + + KIOBufferBase::operator=(other); + + m_list = other.m_list; // copy-on-write + m_offset = other.m_offset; + m_size = other.m_size; + m_length = other.m_length; + + return *this; +} + +bool KSocketBuffer::canReadLine() const +{ + QMutexLocker locker(&m_mutex); + + QValueListConstIterator<QByteArray> it = m_list.constBegin(), + end = m_list.constEnd(); + QIODevice::Offset offset = m_offset; + + // walk the buffer + for ( ; it != end; ++it) + { + if ((*it).find('\n', offset) != -1) + return true; + if ((*it).find('\r', offset) != -1) + return true; + offset = 0; + } + + return false; // not found +} + +QCString KSocketBuffer::readLine() +{ + if (!canReadLine()) + return QCString(); // empty + + QMutexLocker locker(&m_mutex); + + // find the offset of the newline in the buffer + int newline = 0; + QValueListConstIterator<QByteArray> it = m_list.constBegin(), + end = m_list.constEnd(); + QIODevice::Offset offset = m_offset; + + // walk the buffer + for ( ; it != end; ++it) + { + int posnl = (*it).find('\n', offset); + if (posnl == -1) + { + // not found in this one + newline += (*it).size(); + offset = 0; + continue; + } + + // we found it + newline += posnl; + break; + } + + QCString result(newline + 2 - m_offset); + consumeBuffer(result.data(), newline + 1 - m_offset); + return result; +} + +Q_LONG KSocketBuffer::length() const +{ + return m_length; +} + +Q_LONG KSocketBuffer::size() const +{ + return m_size; +} + +bool KSocketBuffer::setSize(Q_LONG size) +{ + m_size = size; + if (size == -1 || m_length < m_size) + return true; + + // size is now smaller than length + QMutexLocker locker(&m_mutex); + + // repeat the test + if (m_length < m_size) + return true; + + // discard from the beginning + return (m_length - m_size) == consumeBuffer(0L, m_length - m_size, true); +} + +Q_LONG KSocketBuffer::feedBuffer(const char *data, Q_LONG len) +{ + if (data == 0L || len == 0) + return 0; // nothing to write + if (isFull()) + return -1; // can't write + + QMutexLocker locker(&m_mutex); + + // verify if we can add len bytes + if (m_size != -1 && (m_size - m_length) < len) + len = m_size - m_length; + + QByteArray a(len); + a.duplicate(data, len); + m_list.append(a); + + m_length += len; + return len; +} + +Q_LONG KSocketBuffer::consumeBuffer(char *destbuffer, Q_LONG maxlen, bool discard) +{ + if (maxlen == 0 || isEmpty()) + return 0; + + QValueListIterator<QByteArray> it = m_list.begin(), + end = m_list.end(); + QIODevice::Offset offset = m_offset; + Q_LONG copied = 0; + + // walk the buffer + while (it != end && maxlen) + { + // calculate how much we'll copy + size_t to_copy = (*it).size() - offset; + if (to_copy > maxlen) + to_copy = maxlen; + + // do the copying + if (destbuffer) + memcpy(destbuffer + copied, (*it).data() + offset, to_copy); + maxlen -= to_copy; + copied += to_copy; + + if ((*it).size() - offset > to_copy) + { + // we did not copy everything + offset += to_copy; + break; + } + else + { + // we copied everything + // discard this element; + offset = 0; + if (discard) + it = m_list.remove(it); + else + ++it; + } + } + + if (discard) + { + m_offset = offset; + m_length -= copied; + assert(m_length >= 0); + } + + return copied; +} + +void KSocketBuffer::clear() +{ + QMutexLocker locker(&m_mutex); + m_list.clear(); + m_offset = 0; + m_length = 0; +} + +Q_LONG KSocketBuffer::sendTo(KActiveSocketBase* dev, Q_LONG len) +{ + if (len == 0 || isEmpty()) + return 0; + + QMutexLocker locker(&m_mutex); + + QValueListIterator<QByteArray> it = m_list.begin(), + end = m_list.end(); + QIODevice::Offset offset = m_offset; + Q_LONG written = 0; + + // walk the buffer + while (it != end && (len || len == -1)) + { + // we have to write each element up to len bytes + // but since we can have several very small buffers, we can make things + // better by concatenating a few of them into a big buffer + // question is: how big should that buffer be? 2 kB should be enough + + Q_ULONG bufsize = 1460; + if (len != -1 && len < bufsize) + bufsize = len; + QByteArray buf(bufsize); + Q_LONG count = 0; + + while (it != end && count + ((*it).size() - offset) <= bufsize) + { + memcpy(buf.data() + count, (*it).data() + offset, (*it).size() - offset); + count += (*it).size() - offset; + offset = 0; + ++it; + } + + // see if we can still fit more + if (count < bufsize && it != end) + { + // getting here means this buffer (*it) is larger than + // (bufsize - count) (even for count == 0). + memcpy(buf.data() + count, (*it).data() + offset, bufsize - count); + offset += bufsize - count; + count = bufsize; + } + + // now try to write those bytes + Q_LONG wrote = dev->writeBlock(buf, count); + + if (wrote == -1) + // error? + break; + + written += wrote; + if (wrote != count) + // can't fit more? + break; + } + + // discard data that has been written + // this updates m_length too + if (written) + consumeBuffer(0L, written); + + return written; +} + +Q_LONG KSocketBuffer::receiveFrom(KActiveSocketBase* dev, Q_LONG len) +{ + if (len == 0 || isFull()) + return 0; + + QMutexLocker locker(&m_mutex); + + if (len == -1) + len = dev->bytesAvailable(); + if (len <= 0) + // error or closing socket + return len; + + // see if we can read that much + if (m_size != -1 && len > (m_size - m_length)) + len = m_size - m_length; + + // here, len contains just as many bytes as we're supposed to read + + // now do the reading + QByteArray a(len); + len = dev->readBlock(a.data(), len); + + if (len == -1) + // error? + return -1; + + // success + // resize the buffer and add it + a.truncate(len); + m_list.append(a); + m_length += len; + return len; +} diff --git a/kdecore/network/ksocketbuffer_p.h b/kdecore/network/ksocketbuffer_p.h new file mode 100644 index 000000000..ddcc692bd --- /dev/null +++ b/kdecore/network/ksocketbuffer_p.h @@ -0,0 +1,164 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KSOCKETBUFFER_P_H +#define KSOCKETBUFFER_P_H + +#include <qmutex.h> +#include <qcstring.h> +#include <qvaluelist.h> +#include "kiobuffer.h" + +namespace KNetwork { + +class KActiveSocketBase; + + namespace Internal { + +/** + * @internal + * @class KSocketBuffer ksocketbuffer_p.h ksocketbuffer_p.h + * @brief generic socket buffering code + * + * This class implements generic buffering used by @ref KBufferedSocket. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KSocketBuffer: public KIOBufferBase +{ +public: + /** + * Default constructor. + * + * @param size the maximum size of the buffer + */ + KSocketBuffer(Q_LONG size = -1); + + /** + * Copy constructor. + */ + KSocketBuffer(const KSocketBuffer& other); + + /** + * Virtual destructor. Frees the buffer and discards its contents. + */ + virtual ~KSocketBuffer(); + + /** + * Assignment operator. + */ + KSocketBuffer& operator=(const KSocketBuffer& other); + + /** + * Returns true if a line can be read from the buffer. + */ + virtual bool canReadLine() const; + + /** + * Reads a line from the buffer and discard it from the buffer. + */ + virtual QCString readLine(); + + /** + * Returns the number of bytes in the buffer. Note that this is not + * the size of the buffer. + * + * @sa size + */ + virtual Q_LONG length() const; + + /** + * Retrieves the buffer size. The value of -1 indicates that + * the buffer has no defined upper limit. + * + * @sa length for the length of the data stored + */ + virtual Q_LONG size() const; + + /** + * Sets the size of the buffer, if allowed. + * + * @param size the maximum size, use -1 for unlimited. + * @returns true on success, false if an error occurred. + * @note if the new size is less than length(), the buffer will be truncated + */ + virtual bool setSize(Q_LONG size); + + /** + * Adds data to the end of the buffer. + * + * @param data the data to be added + * @param len the data length, in bytes + * @returns the number of bytes added to the end of the buffer. + */ + virtual Q_LONG feedBuffer(const char *data, Q_LONG len); + + /** + * Clears the buffer. + */ + virtual void clear(); + + /** + * Consumes data from the beginning of the buffer. + * + * @param data where to copy the data to + * @param maxlen the maximum length to copy, in bytes + * @param discard if true, the bytes copied will be discarded + * @returns the number of bytes copied from the buffer + */ + virtual Q_LONG consumeBuffer(char *data, Q_LONG maxlen, bool discard = true); + + /** + * Sends at most @p len bytes of data to the I/O Device. + * + * @param device the device to which to send data + * @param len the amount of data to send; -1 to send everything + * @returns the number of bytes sent and discarded from the buffer, -1 + * indicates an error. + */ + virtual Q_LONG sendTo(KActiveSocketBase* device, Q_LONG len = -1); + + /** + * Tries to receive @p len bytes of data from the I/O device. + * + * @param device the device to receive from + * @param len the number of bytes to receive; -1 to read as much + * as possible + * @returns the number of bytes received and copied into the buffer, + * -1 indicates an error. + */ + virtual Q_LONG receiveFrom(KActiveSocketBase* device, Q_LONG len = -1); + +protected: + mutable QMutex m_mutex; + QValueList<QByteArray> m_list; + QIODevice::Offset m_offset; ///< offset of the start of data in the first element + + Q_LONG m_size; ///< the maximum length of the buffer + mutable Q_LONG m_length; +}; + +} } // namespace KNetwork::Internal + +#endif diff --git a/kdecore/network/ksocketdevice.cpp b/kdecore/network/ksocketdevice.cpp new file mode 100644 index 000000000..b3004ccc2 --- /dev/null +++ b/kdecore/network/ksocketdevice.cpp @@ -0,0 +1,886 @@ +/* -*- C++ -*- + * Copyright (C) 2003,2005 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <qmap.h> + +#ifdef USE_SOLARIS +# include <sys/filio.h> +#endif +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <unistd.h> + +#ifdef HAVE_POLL +# include <sys/poll.h> +#else +# ifdef HAVE_SYS_SELECT +# include <sys/select.h> +# endif +#endif + +// Include syssocket before our local includes +#include "syssocket.h" + +#include <qmutex.h> +#include <qsocketnotifier.h> + +#include "kresolver.h" +#include "ksocketaddress.h" +#include "ksocketbase.h" +#include "ksocketdevice.h" +#include "ksockssocketdevice.h" + +using namespace KNetwork; + +class KNetwork::KSocketDevicePrivate +{ +public: + mutable QSocketNotifier *input, *output, *exception; + KSocketAddress local, peer; + int af; + + inline KSocketDevicePrivate() + { + input = output = exception = 0L; + af = 0; + } +}; + + +KSocketDevice::KSocketDevice(const KSocketBase* parent) + : m_sockfd(-1), d(new KSocketDevicePrivate) +{ + setSocketDevice(this); + if (parent) + setSocketOptions(parent->socketOptions()); +} + +KSocketDevice::KSocketDevice(int fd) + : m_sockfd(fd), d(new KSocketDevicePrivate) +{ + setState(IO_Open); + setFlags(IO_Sequential | IO_Raw | IO_ReadWrite); + setSocketDevice(this); + d->af = localAddress().family(); +} + +KSocketDevice::KSocketDevice(bool, const KSocketBase* parent) + : m_sockfd(-1), d(new KSocketDevicePrivate) +{ + // do not set parent + if (parent) + setSocketOptions(parent->socketOptions()); +} + +KSocketDevice::~KSocketDevice() +{ + close(); // deletes the notifiers + unsetSocketDevice(); // prevent double deletion + delete d; +} + +bool KSocketDevice::setSocketOptions(int opts) +{ + // must call parent + QMutexLocker locker(mutex()); + KSocketBase::setSocketOptions(opts); + + if (m_sockfd == -1) + return true; // flags are stored + + { + int fdflags = fcntl(m_sockfd, F_GETFL, 0); + if (fdflags == -1) + { + setError(IO_UnspecifiedError, UnknownError); + return false; // error + } + + if (opts & Blocking) + fdflags &= ~O_NONBLOCK; + else + fdflags |= O_NONBLOCK; + + if (fcntl(m_sockfd, F_SETFL, fdflags) == -1) + { + setError(IO_UnspecifiedError, UnknownError); + return false; // error + } + } + + { + int on = opts & AddressReuseable ? 1 : 0; + if (setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on)) == -1) + { + setError(IO_UnspecifiedError, UnknownError); + return false; // error + } + } + +#if defined(IPV6_V6ONLY) && defined(AF_INET6) + if (d->af == AF_INET6) + { + // don't try this on non-IPv6 sockets, or we'll get an error + + int on = opts & IPv6Only ? 1 : 0; + if (setsockopt(m_sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&on, sizeof(on)) == -1) + { + setError(IO_UnspecifiedError, UnknownError); + return false; // error + } + } +#endif + + { + int on = opts & Broadcast ? 1 : 0; + if (setsockopt(m_sockfd, SOL_SOCKET, SO_BROADCAST, (char*)&on, sizeof(on)) == -1) + { + setError(IO_UnspecifiedError, UnknownError); + return false; // error + } + } + + return true; // all went well +} + +bool KSocketDevice::open(int) +{ + resetError(); + return false; +} + +void KSocketDevice::close() +{ + resetError(); + if (m_sockfd != -1) + { + delete d->input; + delete d->output; + delete d->exception; + + d->input = d->output = d->exception = 0L; + + d->local.setFamily(AF_UNSPEC); + d->peer.setFamily(AF_UNSPEC); + + ::close(m_sockfd); + } + setState(0); + + m_sockfd = -1; +} + +bool KSocketDevice::create(int family, int type, int protocol) +{ + resetError(); + + if (m_sockfd != -1) + { + // it's already created! + setError(IO_SocketCreateError, AlreadyCreated); + return false; + } + + // no socket yet; we have to create it + m_sockfd = kde_socket(family, type, protocol); + + if (m_sockfd == -1) + { + setError(IO_SocketCreateError, NotSupported); + return false; + } + + d->af = family; + setSocketOptions(socketOptions()); + setState(IO_Open); + return true; // successfully created +} + +bool KSocketDevice::create(const KResolverEntry& address) +{ + return create(address.family(), address.socketType(), address.protocol()); +} + +bool KSocketDevice::bind(const KResolverEntry& address) +{ + resetError(); + + if (m_sockfd == -1 && !create(address)) + return false; // failed creating + + // we have a socket, so try and bind + if (kde_bind(m_sockfd, address.address(), address.length()) == -1) + { + if (errno == EADDRINUSE) + setError(IO_BindError, AddressInUse); + else if (errno == EINVAL) + setError(IO_BindError, AlreadyBound); + else + // assume the address is the cause + setError(IO_BindError, NotSupported); + return false; + } + + return true; +} + +bool KSocketDevice::listen(int backlog) +{ + if (m_sockfd != -1) + { + if (kde_listen(m_sockfd, backlog) == -1) + { + setError(IO_ListenError, NotSupported); + return false; + } + + resetError(); + setFlags(IO_Sequential | IO_Raw | IO_ReadWrite); + return true; + } + + // we don't have a socket + // can't listen + setError(IO_ListenError, NotCreated); + return false; +} + +bool KSocketDevice::connect(const KResolverEntry& address) +{ + resetError(); + + if (m_sockfd == -1 && !create(address)) + return false; // failed creating! + + if (kde_connect(m_sockfd, address.address(), address.length()) == -1) + { + if (errno == EISCONN) + return true; // we're already connected + else if (errno == EALREADY || errno == EINPROGRESS) + { + setError(IO_ConnectError, InProgress); + return true; + } + else if (errno == ECONNREFUSED) + setError(IO_ConnectError, ConnectionRefused); + else if (errno == ENETDOWN || errno == ENETUNREACH || + errno == ENETRESET || errno == ECONNABORTED || + errno == ECONNRESET || errno == EHOSTDOWN || + errno == EHOSTUNREACH) + setError(IO_ConnectError, NetFailure); + else + setError(IO_ConnectError, NotSupported); + + return false; + } + + setFlags(IO_Sequential | IO_Raw | IO_ReadWrite); + return true; // all is well +} + +KSocketDevice* KSocketDevice::accept() +{ + if (m_sockfd == -1) + { + // can't accept without a socket + setError(IO_AcceptError, NotCreated); + return 0L; + } + + struct sockaddr sa; + socklen_t len = sizeof(sa); + int newfd = kde_accept(m_sockfd, &sa, &len); + if (newfd == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + setError(IO_AcceptError, WouldBlock); + else + setError(IO_AcceptError, UnknownError); + return NULL; + } + + return new KSocketDevice(newfd); +} + +bool KSocketDevice::disconnect() +{ + resetError(); + + if (m_sockfd == -1) + return false; // can't create + + KSocketAddress address; + address.setFamily(AF_UNSPEC); + if (kde_connect(m_sockfd, address.address(), address.length()) == -1) + { + if (errno == EALREADY || errno == EINPROGRESS) + { + setError(IO_ConnectError, InProgress); + return false; + } + else if (errno == ECONNREFUSED) + setError(IO_ConnectError, ConnectionRefused); + else if (errno == ENETDOWN || errno == ENETUNREACH || + errno == ENETRESET || errno == ECONNABORTED || + errno == ECONNRESET || errno == EHOSTDOWN || + errno == EHOSTUNREACH) + setError(IO_ConnectError, NetFailure); + else + setError(IO_ConnectError, NotSupported); + + return false; + } + + setFlags(IO_Sequential | IO_Raw | IO_ReadWrite); + setState(IO_Open); + return true; // all is well +} + +Q_LONG KSocketDevice::bytesAvailable() const +{ + if (m_sockfd == -1) + return -1; // there's nothing to read in a closed socket + + int nchars; + if (ioctl(m_sockfd, FIONREAD, &nchars) == -1) + return -1; // error! + + return nchars; +} + +Q_LONG KSocketDevice::waitForMore(int msecs, bool *timeout) +{ + if (m_sockfd == -1) + return -1; // there won't ever be anything to read... + + bool input; + if (!poll(&input, 0, 0, msecs, timeout)) + return -1; // failed polling + + return bytesAvailable(); +} + +static int do_read_common(int sockfd, char *data, Q_ULONG maxlen, KSocketAddress* from, ssize_t &retval, bool peek = false) +{ + socklen_t len; + if (from) + { + from->setLength(len = 128); // arbitrary length + retval = ::recvfrom(sockfd, data, maxlen, peek ? MSG_PEEK : 0, from->address(), &len); + } + else + retval = ::recvfrom(sockfd, data, maxlen, peek ? MSG_PEEK : 0, NULL, NULL); + + if (retval == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return KSocketDevice::WouldBlock; + else + return KSocketDevice::UnknownError; + } + if (retval == 0) + return KSocketDevice::RemotelyDisconnected; + + if (from) + from->setLength(len); + return 0; +} + +Q_LONG KSocketDevice::readBlock(char *data, Q_ULONG maxlen) +{ + resetError(); + if (m_sockfd == -1) + return -1; + + if (maxlen == 0 || data == 0L) + return 0; // can't read + + ssize_t retval; + int err = do_read_common(m_sockfd, data, maxlen, 0L, retval); + + if (err) + { + setError(IO_ReadError, static_cast<SocketError>(err)); + return -1; + } + + return retval; +} + +Q_LONG KSocketDevice::readBlock(char *data, Q_ULONG maxlen, KSocketAddress &from) +{ + resetError(); + if (m_sockfd == -1) + return -1; // nothing to do here + + if (data == 0L || maxlen == 0) + return 0; // user doesn't want to read + + ssize_t retval; + int err = do_read_common(m_sockfd, data, maxlen, &from, retval); + + if (err) + { + setError(IO_ReadError, static_cast<SocketError>(err)); + return -1; + } + + return retval; +} + +Q_LONG KSocketDevice::peekBlock(char *data, Q_ULONG maxlen) +{ + resetError(); + if (m_sockfd == -1) + return -1; + + if (maxlen == 0 || data == 0L) + return 0; // can't read + + ssize_t retval; + int err = do_read_common(m_sockfd, data, maxlen, 0L, retval, true); + + if (err) + { + setError(IO_ReadError, static_cast<SocketError>(err)); + return -1; + } + + return retval; +} + +Q_LONG KSocketDevice::peekBlock(char *data, Q_ULONG maxlen, KSocketAddress& from) +{ + resetError(); + if (m_sockfd == -1) + return -1; // nothing to do here + + if (data == 0L || maxlen == 0) + return 0; // user doesn't want to read + + ssize_t retval; + int err = do_read_common(m_sockfd, data, maxlen, &from, retval, true); + + if (err) + { + setError(IO_ReadError, static_cast<SocketError>(err)); + return -1; + } + + return retval; +} + +Q_LONG KSocketDevice::writeBlock(const char *data, Q_ULONG len) +{ + return writeBlock(data, len, KSocketAddress()); +} + +Q_LONG KSocketDevice::writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to) +{ + resetError(); + if (m_sockfd == -1) + return -1; // can't write to unopen socket + + if (data == 0L || len == 0) + return 0; // nothing to be written + + ssize_t retval = ::sendto(m_sockfd, data, len, 0, to.address(), to.length()); + if (retval == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + setError(IO_WriteError, WouldBlock); + else + setError(IO_WriteError, UnknownError); + return -1; // nothing written + } + else if (retval == 0) + setError(IO_WriteError, RemotelyDisconnected); + + return retval; +} + +KSocketAddress KSocketDevice::localAddress() const +{ + if (m_sockfd == -1) + return KSocketAddress(); // not open, empty value + + if (d->local.family() != AF_UNSPEC) + return d->local; + + socklen_t len; + KSocketAddress localAddress; + localAddress.setLength(len = 32); // arbitrary value + if (kde_getsockname(m_sockfd, localAddress.address(), &len) == -1) + // error! + return d->local = KSocketAddress(); + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + len = localAddress.address()->sa_len; +#endif + + if (len <= localAddress.length()) + { + // it has fit already + localAddress.setLength(len); + return d->local = localAddress; + } + + // no, the socket address is actually larger than we had anticipated + // call again + localAddress.setLength(len); + if (kde_getsockname(m_sockfd, localAddress.address(), &len) == -1) + // error! + return d->local = KSocketAddress(); + + return d->local = localAddress; +} + +KSocketAddress KSocketDevice::peerAddress() const +{ + if (m_sockfd == -1) + return KSocketAddress(); // not open, empty value + + if (d->peer.family() != AF_UNSPEC) + return d->peer; + + socklen_t len; + KSocketAddress peerAddress; + peerAddress.setLength(len = 32); // arbitrary value + if (kde_getpeername(m_sockfd, peerAddress.address(), &len) == -1) + // error! + return d->peer = KSocketAddress(); + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + len = peerAddress.address()->sa_len; +#endif + + if (len <= peerAddress.length()) + { + // it has fit already + peerAddress.setLength(len); + return d->peer = peerAddress; + } + + // no, the socket address is actually larger than we had anticipated + // call again + peerAddress.setLength(len); + if (kde_getpeername(m_sockfd, peerAddress.address(), &len) == -1) + // error! + return d->peer = KSocketAddress(); + + return d->peer = peerAddress; +} + +KSocketAddress KSocketDevice::externalAddress() const +{ + // for normal sockets, the externally visible address is the same + // as the local address + return localAddress(); +} + +QSocketNotifier* KSocketDevice::readNotifier() const +{ + if (d->input) + return d->input; + + QMutexLocker locker(mutex()); + if (d->input) + return d->input; + + if (m_sockfd == -1) + { + // socket doesn't exist; can't create notifier + return 0L; + } + + return d->input = createNotifier(QSocketNotifier::Read); +} + +QSocketNotifier* KSocketDevice::writeNotifier() const +{ + if (d->output) + return d->output; + + QMutexLocker locker(mutex()); + if (d->output) + return d->output; + + if (m_sockfd == -1) + { + // socket doesn't exist; can't create notifier + return 0L; + } + + return d->output = createNotifier(QSocketNotifier::Write); +} + +QSocketNotifier* KSocketDevice::exceptionNotifier() const +{ + if (d->exception) + return d->exception; + + QMutexLocker locker(mutex()); + if (d->exception) + return d->exception; + + if (m_sockfd == -1) + { + // socket doesn't exist; can't create notifier + return 0L; + } + + return d->exception = createNotifier(QSocketNotifier::Exception); +} + +bool KSocketDevice::poll(bool *input, bool *output, bool *exception, + int timeout, bool* timedout) +{ + if (m_sockfd == -1) + { + setError(IO_UnspecifiedError, NotCreated); + return false; + } + + resetError(); +#ifdef HAVE_POLL + struct pollfd fds; + fds.fd = m_sockfd; + fds.events = 0; + + if (input) + { + fds.events |= POLLIN; + *input = false; + } + if (output) + { + fds.events |= POLLOUT; + *output = false; + } + if (exception) + { + fds.events |= POLLPRI; + *exception = false; + } + + int retval = ::poll(&fds, 1, timeout); + if (retval == -1) + { + setError(IO_UnspecifiedError, UnknownError); + return false; + } + if (retval == 0) + { + // timeout + if (timedout) + *timedout = true; + return true; + } + + if (input && fds.revents & POLLIN) + *input = true; + if (output && fds.revents & POLLOUT) + *output = true; + if (exception && fds.revents & POLLPRI) + *exception = true; + if (timedout) + *timedout = false; + + return true; +#else + /* + * We don't have poll(2). We'll have to make do with select(2). + */ + + fd_set readfds, writefds, exceptfds; + fd_set *preadfds = 0L, *pwritefds = 0L, *pexceptfds = 0L; + + if (input) + { + preadfds = &readfds; + FD_ZERO(preadfds); + FD_SET(m_sockfd, preadfds); + *input = false; + } + if (output) + { + pwritefds = &writefds; + FD_ZERO(pwritefds); + FD_SET(m_sockfd, pwritefds); + *output = false; + } + if (exception) + { + pexceptfds = &exceptfds; + FD_ZERO(pexceptfds); + FD_SET(m_sockfd, pexceptfds); + *exception = false; + } + + int retval; + if (timeout < 0) + retval = select(m_sockfd + 1, preadfds, pwritefds, pexceptfds, 0L); + else + { + // convert the milliseconds to timeval + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = timeout % 1000 * 1000; + + retval = select(m_sockfd + 1, preadfds, pwritefds, pexceptfds, &tv); + } + + if (retval == -1) + { + setError(IO_UnspecifiedError, UnknownError); + return false; + } + if (retval == 0) + { + // timeout + if (timedout) + *timedout = true; + return true; + } + + if (input && FD_ISSET(m_sockfd, preadfds)) + *input = true; + if (output && FD_ISSET(m_sockfd, pwritefds)) + *output = true; + if (exception && FD_ISSET(m_sockfd, pexceptfds)) + *exception = true; + + return true; +#endif +} + +bool KSocketDevice::poll(int timeout, bool *timedout) +{ + bool input, output, exception; + return poll(&input, &output, &exception, timeout, timedout); +} + +QSocketNotifier* KSocketDevice::createNotifier(QSocketNotifier::Type type) const +{ + if (m_sockfd == -1) + return 0L; + + return new QSocketNotifier(m_sockfd, type); +} + +namespace +{ + // simple class to avoid pointer stuff + template<class T> class ptr + { + typedef T type; + type* obj; + public: + ptr() : obj(0) + { } + + ptr(const ptr<T>& other) : obj(other.obj) + { } + + ptr(type* _obj) : obj(_obj) + { } + + ~ptr() + { } + + ptr<T>& operator=(const ptr<T>& other) + { obj = other.obj; return *this; } + + ptr<T>& operator=(T* _obj) + { obj = _obj; return *this; } + + type* operator->() const { return obj; } + + operator T*() const { return obj; } + + bool isNull() const + { return obj == 0; } + }; +} + +static KSocketDeviceFactoryBase* defaultImplFactory; +static QMutex defaultImplFactoryMutex; +typedef QMap<int, KSocketDeviceFactoryBase* > factoryMap; +static factoryMap factories; + +KSocketDevice* KSocketDevice::createDefault(KSocketBase* parent) +{ + KSocketDevice* device = dynamic_cast<KSocketDevice*>(parent); + if (device != 0L) + return device; + + KSocksSocketDevice::initSocks(); + + if (defaultImplFactory) + return defaultImplFactory->create(parent); + + // the really default + return new KSocketDevice(parent); +} + +KSocketDevice* KSocketDevice::createDefault(KSocketBase* parent, int capabilities) +{ + KSocketDevice* device = dynamic_cast<KSocketDevice*>(parent); + if (device != 0L) + return device; + + QMutexLocker locker(&defaultImplFactoryMutex); + factoryMap::ConstIterator it = factories.constBegin(); + for ( ; it != factories.constEnd(); ++it) + if ((it.key() & capabilities) == capabilities) + // found a match + return it.data()->create(parent); + + return 0L; // no default +} + +KSocketDeviceFactoryBase* +KSocketDevice::setDefaultImpl(KSocketDeviceFactoryBase* factory) +{ + QMutexLocker locker(&defaultImplFactoryMutex); + KSocketDeviceFactoryBase* old = defaultImplFactory; + defaultImplFactory = factory; + return old; +} + +void KSocketDevice::addNewImpl(KSocketDeviceFactoryBase* factory, int capabilities) +{ + QMutexLocker locker(&defaultImplFactoryMutex); + if (factories.contains(capabilities)) + delete factories[capabilities]; + factories.insert(capabilities, factory); +} + diff --git a/kdecore/network/ksocketdevice.h b/kdecore/network/ksocketdevice.h new file mode 100644 index 000000000..301bc2b07 --- /dev/null +++ b/kdecore/network/ksocketdevice.h @@ -0,0 +1,428 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KSOCKETDEVICE_H +#define KSOCKETDEVICE_H + +#include <qsocketnotifier.h> +#include "ksocketbase.h" + +namespace KNetwork { + +class KSocketDevice; +class KSocketDeviceFactoryBase; + +class KSocketDevicePrivate; +/** @class KSocketDevice ksocketdevice.h ksocketdevice.h + * @brief Low-level socket functionality. + * + * This class provides low-level socket functionality. + * + * Most users will prefer "cooked" interfaces like those of @ref KStreamSocket or + * @ref KServerSocket. + * + * Descended classes from this one provide some other kinds of socket functionality, + * like proxying or specific socket types. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + */ +class KDECORE_EXPORT KSocketDevice: public KActiveSocketBase, public KPassiveSocketBase +{ +public: + /** + * Capabilities for the socket implementation. + * + * KSocketDevice-derived classes can implement certain capabilities that are not + * available in the default class. These capabilities are described by these flags. + * The default KSocketDevice class has none of these capabilities. + * + * For the negative capabilities (inabilities, the CanNot* forms), when a capability + * is not present, the implementation will default to the original behaviour. + */ + enum Capabilities + { + /** Can connect to hostnames. + * If this flag is present, the string form of @ref connect can be used. */ + CanConnectString = 0x01, + + /** Can bind to hostnames. + * If this flag is present, the string form of @ref bind can be used */ + CanBindString = 0x02, + + /** Can not bind. + * If this flag is present, this implementation cannot bind */ + CanNotBind = 0x04, + + /** Can not listen. + * If this flag is present, this implementation cannot listen */ + CanNotListen = 0x08, + + /** + * Can send multicast as well as join/leave multicast groups. + */ + CanMulticast = 0x10, + + /** + * Can not use datagrams. + * Note that this implies multicast capability not being available either. + */ + CanNotUseDatagrams = 0x20 + }; +protected: + /// The socket file descriptor. It is used throughout the implementation + /// and subclasses. + int m_sockfd; + +public: + /** + * Default constructor. + * + * The parameter is used to specify which socket this object is used as + * a device for. + */ + explicit KSocketDevice(const KSocketBase* = 0L); + + /** + * Constructs a new object around an already-open socket. + * + * Note: you should write programs that create sockets through + * the classes whenever possible. + */ + explicit KSocketDevice(int fd); + + /** + * Destructor. This closes the socket if it's open. + */ + virtual ~KSocketDevice(); + + /** + * Returns the file descriptor for this socket. + */ + inline int socket() const + { return m_sockfd; } + + /** + * Returns the set of capabilities this socket class implements. + * The set of capabilities is defined as an OR-ed mask of + * @ref Capabilities bits. + * + * The default implementation is guaranteed to always return 0. That + * is, derived implementations always return bits where they differ + * from the system standard sockets. + */ + virtual int capabilities() const + { return 0; } + + /** + * This implementation sets the options on the socket. + */ + virtual bool setSocketOptions(int opts); + + /** + * Reimplementation from QIODevice. You should not call this function in sockets. + */ + virtual bool open(int mode); + + /** + * Closes the socket. Reimplemented from QIODevice. + * + * Use this function to close the socket this object is holding open. + */ + virtual void close(); + + /** + * This call is not supported on sockets. Reimplemented from QIODevice. + */ + virtual void flush() + { } + + /** + * Creates a socket but don't connect or bind anywhere. + * This function is the equivalent of the system call socket(2). + */ + virtual bool create(int family, int type, int protocol); + + /** + * @overload + * Creates a socket but don't connect or bind anywhere. + */ + bool create(const KResolverEntry& address); + + /** + * Binds this socket to the given address. + */ + virtual bool bind(const KResolverEntry& address); + + /** + * Puts this socket into listening mode. + */ + virtual bool listen(int backlog = 5); // 5 is arbitrary + + /** + * Connect to a remote host. + */ + virtual bool connect(const KResolverEntry& address); + + /** + * Accepts a new incoming connection. + * Note: this function returns a socket of type KSocketDevice. + */ + virtual KSocketDevice* accept(); + + /** + * Disconnects this socket. + */ + virtual bool disconnect(); + + /** + * Returns the number of bytes available for reading without blocking. + */ + virtual Q_LONG bytesAvailable() const; + + /** + * Waits up to @p msecs for more data to be available on this socket. + * + * This function is a wrapper against @ref poll. This function will wait + * for any read events. + */ + virtual Q_LONG waitForMore(int msecs, bool *timeout = 0L); + + /** + * Reads data from this socket. + */ + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen); + + /** + * Reads data and the source address from this socket. + */ + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen, KSocketAddress& from); + + /** + * Peeks data in the socket. + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen); + + /** + * Peeks the data in the socket and the source address. + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen, KSocketAddress& from); + + /** + * Writes data to the socket. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len); + + /** + * Writes the given data to the given destination address. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to); + + /** + * Returns this socket's local address. + */ + virtual KSocketAddress localAddress() const; + + /** + * Returns this socket's peer address. If this implementation does proxying + * of some sort, this is the real external address, not the proxy's address. + */ + virtual KSocketAddress peerAddress() const; + + /** + * Returns this socket's externally visible local address. + * + * If this socket has a local address visible externally different + * from the normal local address (as returned by @ref localAddress), then + * return it. + * + * Certain implementations will use proxies and thus have externally visible + * addresses different from the local socket values. The default implementation + * returns the same value as @ref localAddress. + * + * @note This function may return an empty KSocketAddress. In that case, the + * externally visible address could/can not be determined. + */ + virtual KSocketAddress externalAddress() const; + + /** + * Returns a socket notifier for input on this socket. + * The notifier is created only when requested. Whether + * it is enabled or not depends on the implementation. + * + * This function might return NULL. + */ + QSocketNotifier* readNotifier() const; + + /** + * Returns a socket notifier for output on this socket. + * The is created only when requested. + * + * This function might return NULL. + */ + QSocketNotifier* writeNotifier() const; + + /** + * Returns a socket notifier for exceptional events on this socket. + * The is created only when requested. + * + * This function might return NULL. + */ + QSocketNotifier* exceptionNotifier() const; + + /** + * Executes a poll in the socket, via select(2) or poll(2). + * The events polled are returned in the parameters pointers. + * Set any of them to NULL to disable polling of that event. + * + * On exit, @p input, @p output and @p exception will contain + * true if an event of that kind is waiting on the socket or false + * if not. If a timeout occurred, set @p timedout to true (all other + * parameters are necessarily set to false). + * + * @param input if set, turns on polling for input events + * @param output if set, turns on polling for output events + * @param exception if set, turns on polling for exceptional events + * @param timeout the time in milliseconds to wait for an event; + * 0 for no wait and any negative value to wait forever + * @param timedout on exit, will contain true if the polling timed out + * @return true if the poll call succeeded and false if an error occurred + */ + virtual bool poll(bool* input, bool* output, bool* exception = 0L, + int timeout = -1, bool* timedout = 0L); + + /** + * Shorter version to poll for any events in a socket. This call + * polls for input, output and exceptional events in a socket but + * does not return their states. This is useful if you need to wait for + * any event, but don't need to know which; or for timeouts. + * + * @param timeout the time in milliseconds to wait for an event; + * 0 for no wait and any negative value to wait forever + * @param timedout on exit, will contain true if the polling timed out + * @return true if the poll call succeeded and false if an error occurred + */ + bool poll(int timeout = -1, bool* timedout = 0L); + +protected: + /** + * Special constructor. This constructor will cause the internal + * socket device NOT to be set. Use this if your socket device class + * takes another underlying socket device. + * + * @param parent the parent, if any + */ + KSocketDevice(bool, const KSocketBase* parent = 0L); + + /** + * Creates a socket notifier of the given type. + * + * This function is called by @ref readNotifier, @ref writeNotifier and + * @ref exceptionNotifier when they need to create a socket notifier + * (i.e., the first call to those functions after the socket is open). + * After that call, those functions cache the socket notifier and will + * not need to call this function again. + * + * Reimplement this function in your derived class if your socket type + * requires a different kind of QSocketNotifier. The return value should + * be deleteable with delete. (@ref close deletes them). + * + * @param type the socket notifier type + */ + virtual QSocketNotifier* createNotifier(QSocketNotifier::Type type) const; + +public: + /** + * Creates a new default KSocketDevice object given + * the parent object. + * + * The capabilities flag indicates the desired capabilities the object being + * created should possess. Those capabilities are not guaranteed: if no factory + * can provide such an object, a default object will be created. + * + * @param parent the KSocketBase parent + */ + static KSocketDevice* createDefault(KSocketBase* parent); + + /** + * @overload + * + * This will create an object only if the requested capabilities match. + * + * @param parent the parent + * @param capabilities the requested capabilities + */ + static KSocketDevice* createDefault(KSocketBase* parent, int capabilities); + + /** + * Sets the default KSocketDevice implementation to use and + * return the old factory. + * + * @param factory the factory object for the implementation + */ + static KSocketDeviceFactoryBase* setDefaultImpl(KSocketDeviceFactoryBase* factory); + + /** + * Adds a factory of KSocketDevice objects to the list, along with its + * capabilities flag. + */ + static void addNewImpl(KSocketDeviceFactoryBase* factory, int capabilities); + +private: + KSocketDevice(const KSocketDevice&); + KSocketDevice& operator=(const KSocketDevice&); + + KSocketDevicePrivate *d; +}; + +/** @internal + * This class provides functionality for creating and registering + * socket implementations. + */ +class KSocketDeviceFactoryBase +{ +public: + KSocketDeviceFactoryBase() {} + virtual ~KSocketDeviceFactoryBase() {} + + virtual KSocketDevice* create(KSocketBase*) const = 0; +}; + +/** + * This class provides functionality for creating and registering + * socket implementations. + */ +template<class Impl> +class KSocketDeviceFactory: public KSocketDeviceFactoryBase +{ +public: + KSocketDeviceFactory() {} + virtual ~KSocketDeviceFactory() {} + + virtual KSocketDevice* create(KSocketBase* parent) const + { return new Impl(parent); } +}; + +} // namespaces + +#endif diff --git a/kdecore/network/ksockssocketdevice.cpp b/kdecore/network/ksockssocketdevice.cpp new file mode 100644 index 000000000..f67b90bc9 --- /dev/null +++ b/kdecore/network/ksockssocketdevice.cpp @@ -0,0 +1,487 @@ +/* -*- C++ -*- + * Copyright (C) 2004 Thiago Macieira <thiago.macieira@kdemail.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <config.h> + +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> + +#if defined(HAVE_UNISTD_H) +#include <unistd.h> +#endif + +#ifdef __CYGWIN__ +#undef kde_socklen_t +#define kde_socklen_t ksocklen_t +#endif + +#include "kapplication.h" + +#include "ksocks.h" +#include "ksocketaddress.h" +#include "kresolver.h" +#include "ksockssocketdevice.h" + +using namespace KNetwork; + +// constructor +// nothing to do +KSocksSocketDevice::KSocksSocketDevice(const KSocketBase* obj) + : KSocketDevice(obj) +{ +} + +// constructor with argument +// nothing to do +KSocksSocketDevice::KSocksSocketDevice(int fd) + : KSocketDevice(fd) +{ +} + +// destructor +// also nothing to do +KSocksSocketDevice::~KSocksSocketDevice() +{ +} + +// returns the capabilities +int KSocksSocketDevice::capabilities() const +{ + return 0; // can do everything! +} + +// From here on, the code is almost exactly a copy of KSocketDevice +// the differences are the use of KSocks where appropriate + +bool KSocksSocketDevice::bind(const KResolverEntry& address) +{ + resetError(); + + if (m_sockfd == -1 && !create(address)) + return false; // failed creating + + // we have a socket, so try and bind + if (KSocks::self()->bind(m_sockfd, address.address(), address.length()) == -1) + { + if (errno == EADDRINUSE) + setError(IO_BindError, AddressInUse); + else if (errno == EINVAL) + setError(IO_BindError, AlreadyBound); + else + // assume the address is the cause + setError(IO_BindError, NotSupported); + return false; + } + + return true; +} + + +bool KSocksSocketDevice::listen(int backlog) +{ + if (m_sockfd != -1) + { + if (KSocks::self()->listen(m_sockfd, backlog) == -1) + { + setError(IO_ListenError, NotSupported); + return false; + } + + resetError(); + setFlags(IO_Sequential | IO_Raw | IO_ReadWrite); + setState(IO_Open); + return true; + } + + // we don't have a socket + // can't listen + setError(IO_ListenError, NotCreated); + return false; +} + +bool KSocksSocketDevice::connect(const KResolverEntry& address) +{ + resetError(); + + if (m_sockfd == -1 && !create(address)) + return false; // failed creating! + + int retval; + if (KSocks::self()->hasWorkingAsyncConnect()) + retval = KSocks::self()->connect(m_sockfd, address.address(), + address.length()); + else + { + // work around some SOCKS implementation bugs + // we will do a *synchronous* connection here! + // FIXME: KDE4, write a proper SOCKS implementation + bool isBlocking = blocking(); + setBlocking(true); + retval = KSocks::self()->connect(m_sockfd, address.address(), + address.length()); + setBlocking(isBlocking); + } + + if (retval == -1) + { + if (errno == EISCONN) + return true; // we're already connected + else if (errno == EALREADY || errno == EINPROGRESS) + { + setError(IO_ConnectError, InProgress); + return true; + } + else if (errno == ECONNREFUSED) + setError(IO_ConnectError, ConnectionRefused); + else if (errno == ENETDOWN || errno == ENETUNREACH || + errno == ENETRESET || errno == ECONNABORTED || + errno == ECONNRESET || errno == EHOSTDOWN || + errno == EHOSTUNREACH) + setError(IO_ConnectError, NetFailure); + else + setError(IO_ConnectError, NotSupported); + + return false; + } + + setFlags(IO_Sequential | IO_Raw | IO_ReadWrite); + setState(IO_Open); + return true; // all is well +} + +KSocksSocketDevice* KSocksSocketDevice::accept() +{ + if (m_sockfd == -1) + { + // can't accept without a socket + setError(IO_AcceptError, NotCreated); + return 0L; + } + + struct sockaddr sa; + kde_socklen_t len = sizeof(sa); + int newfd = KSocks::self()->accept(m_sockfd, &sa, &len); + if (newfd == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + setError(IO_AcceptError, WouldBlock); + else + setError(IO_AcceptError, UnknownError); + return NULL; + } + + return new KSocksSocketDevice(newfd); +} + +static int socks_read_common(int sockfd, char *data, Q_ULONG maxlen, KSocketAddress* from, ssize_t &retval, bool peek = false) +{ + kde_socklen_t len; + if (from) + { + from->setLength(len = 128); // arbitrary length + retval = KSocks::self()->recvfrom(sockfd, data, maxlen, peek ? MSG_PEEK : 0, from->address(), &len); + } + else + retval = KSocks::self()->recvfrom(sockfd, data, maxlen, peek ? MSG_PEEK : 0, NULL, NULL); + + if (retval == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return KSocketDevice::WouldBlock; + else + return KSocketDevice::UnknownError; + } + + if (from) + from->setLength(len); + return 0; +} + +Q_LONG KSocksSocketDevice::readBlock(char *data, Q_ULONG maxlen) +{ + resetError(); + if (m_sockfd == -1) + return -1; + + if (maxlen == 0 || data == 0L) + return 0; // can't read + + ssize_t retval; + int err = socks_read_common(m_sockfd, data, maxlen, 0L, retval); + + if (err) + { + setError(IO_ReadError, static_cast<SocketError>(err)); + return -1; + } + + return retval; +} + +Q_LONG KSocksSocketDevice::readBlock(char *data, Q_ULONG maxlen, KSocketAddress &from) +{ + resetError(); + if (m_sockfd == -1) + return -1; // nothing to do here + + if (data == 0L || maxlen == 0) + return 0; // user doesn't want to read + + ssize_t retval; + int err = socks_read_common(m_sockfd, data, maxlen, &from, retval); + + if (err) + { + setError(IO_ReadError, static_cast<SocketError>(err)); + return -1; + } + + return retval; +} + +Q_LONG KSocksSocketDevice::peekBlock(char *data, Q_ULONG maxlen) +{ + resetError(); + if (m_sockfd == -1) + return -1; + + if (maxlen == 0 || data == 0L) + return 0; // can't read + + ssize_t retval; + int err = socks_read_common(m_sockfd, data, maxlen, 0L, retval, true); + + if (err) + { + setError(IO_ReadError, static_cast<SocketError>(err)); + return -1; + } + + return retval; +} + +Q_LONG KSocksSocketDevice::peekBlock(char *data, Q_ULONG maxlen, KSocketAddress& from) +{ + resetError(); + if (m_sockfd == -1) + return -1; // nothing to do here + + if (data == 0L || maxlen == 0) + return 0; // user doesn't want to read + + ssize_t retval; + int err = socks_read_common(m_sockfd, data, maxlen, &from, retval, true); + + if (err) + { + setError(IO_ReadError, static_cast<SocketError>(err)); + return -1; + } + + return retval; +} + +Q_LONG KSocksSocketDevice::writeBlock(const char *data, Q_ULONG len) +{ + return writeBlock(data, len, KSocketAddress()); +} + +Q_LONG KSocksSocketDevice::writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to) +{ + resetError(); + if (m_sockfd == -1) + return -1; // can't write to unopen socket + + if (data == 0L || len == 0) + return 0; // nothing to be written + + ssize_t retval = KSocks::self()->sendto(m_sockfd, data, len, 0, to.address(), to.length()); + if (retval == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + setError(IO_WriteError, WouldBlock); + else + setError(IO_WriteError, UnknownError); + return -1; // nothing written + } + + return retval; +} + +KSocketAddress KSocksSocketDevice::localAddress() const +{ + if (m_sockfd == -1) + return KSocketAddress(); // not open, empty value + + kde_socklen_t len; + KSocketAddress localAddress; + localAddress.setLength(len = 32); // arbitrary value + if (KSocks::self()->getsockname(m_sockfd, localAddress.address(), &len) == -1) + // error! + return KSocketAddress(); + + if (len <= localAddress.length()) + { + // it has fit already + localAddress.setLength(len); + return localAddress; + } + + // no, the socket address is actually larger than we had anticipated + // call again + localAddress.setLength(len); + if (KSocks::self()->getsockname(m_sockfd, localAddress.address(), &len) == -1) + // error! + return KSocketAddress(); + + return localAddress; +} + +KSocketAddress KSocksSocketDevice::peerAddress() const +{ + if (m_sockfd == -1) + return KSocketAddress(); // not open, empty value + + kde_socklen_t len; + KSocketAddress peerAddress; + peerAddress.setLength(len = 32); // arbitrary value + if (KSocks::self()->getpeername(m_sockfd, peerAddress.address(), &len) == -1) + // error! + return KSocketAddress(); + + if (len <= peerAddress.length()) + { + // it has fit already + peerAddress.setLength(len); + return peerAddress; + } + + // no, the socket address is actually larger than we had anticipated + // call again + peerAddress.setLength(len); + if (KSocks::self()->getpeername(m_sockfd, peerAddress.address(), &len) == -1) + // error! + return KSocketAddress(); + + return peerAddress; +} + +KSocketAddress KSocksSocketDevice::externalAddress() const +{ + // return empty, indicating unknown external address + return KSocketAddress(); +} + +bool KSocksSocketDevice::poll(bool *input, bool *output, bool *exception, + int timeout, bool *timedout) +{ + if (m_sockfd == -1) + { + setError(IO_UnspecifiedError, NotCreated); + return false; + } + + resetError(); + fd_set readfds, writefds, exceptfds; + fd_set *preadfds = 0L, *pwritefds = 0L, *pexceptfds = 0L; + + if (input) + { + preadfds = &readfds; + FD_ZERO(preadfds); + FD_SET(m_sockfd, preadfds); + *input = false; + } + if (output) + { + pwritefds = &writefds; + FD_ZERO(pwritefds); + FD_SET(m_sockfd, pwritefds); + *output = false; + } + if (exception) + { + pexceptfds = &exceptfds; + FD_ZERO(pexceptfds); + FD_SET(m_sockfd, pexceptfds); + *exception = false; + } + + int retval; + if (timeout < 0) + retval = KSocks::self()->select(m_sockfd + 1, preadfds, pwritefds, pexceptfds, 0L); + else + { + // convert the milliseconds to timeval + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = timeout % 1000 * 1000; + + retval = select(m_sockfd + 1, preadfds, pwritefds, pexceptfds, &tv); + } + + if (retval == -1) + { + setError(IO_UnspecifiedError, UnknownError); + return false; + } + if (retval == 0) + { + // timeout + if (timedout) + *timedout = true; + return true; + } + + if (input && FD_ISSET(m_sockfd, preadfds)) + *input = true; + if (output && FD_ISSET(m_sockfd, pwritefds)) + *output = true; + if (exception && FD_ISSET(m_sockfd, pexceptfds)) + *exception = true; + + return true; +} + +void KSocksSocketDevice::initSocks() +{ + static bool init = false; + + if (init) + return; + + if (kapp == 0L) + return; // no KApplication, so don't initialise + // this should, however, test for KInstance + + init = true; + + if (KSocks::self()->hasSocks()) + delete KSocketDevice::setDefaultImpl(new KSocketDeviceFactory<KSocksSocketDevice>); +} + +#if 0 +static bool register() +{ + KSocketDevice::addNewImpl(new KSocketDeviceFactory<KSocksSocketDevice>, 0); +} + +static bool register = registered(); +#endif diff --git a/kdecore/network/ksockssocketdevice.h b/kdecore/network/ksockssocketdevice.h new file mode 100644 index 000000000..62de23f05 --- /dev/null +++ b/kdecore/network/ksockssocketdevice.h @@ -0,0 +1,129 @@ +/* -*- C++ -*- + * Copyright (C) 2004 Thiago Macieira <thiago.macieira@kdemail.net> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KSOCKSSOCKETDEVICE_H +#define KSOCKSSOCKETDEVICE_H + +#include "ksocketdevice.h" + +namespace KNetwork { + +/** + * @class KSocksSocketDevice ksockssocketdevice.h ksockssocketdevice.h + * @brief The low-level class for SOCKS proxying. + * + * This class reimplements several functions from @ref KSocketDevice in order + * to implement SOCKS support. + * + * This works by using KSocks. + * + * @author Thiago Macieira <thiago.macieira@kdemail.net> + * + * @warning This code is untested! + */ +class KDECORE_EXPORT KSocksSocketDevice: public KSocketDevice +{ +public: + /** + * Constructor. + */ + KSocksSocketDevice(const KSocketBase* = 0L); + + /** + * Construct from a file descriptor. + */ + explicit KSocksSocketDevice(int fd); + + /** + * Destructor. + */ + virtual ~KSocksSocketDevice(); + + /** + * Sets our capabilities. + */ + virtual int capabilities() const; + + /** + * Overrides binding. + */ + virtual bool bind(const KResolverEntry& address); + + /** + * Overrides listening. + */ + virtual bool listen(int backlog); + + /** + * Overrides connection. + */ + virtual bool connect(const KResolverEntry& address); + + /** + * Overrides accepting. The return type is specialised. + */ + virtual KSocksSocketDevice* accept(); + + /** + * Overrides reading. + */ + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen); + virtual Q_LONG readBlock(char *data, Q_ULONG maxlen, KSocketAddress& from); + + /** + * Overrides peeking. + */ + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen); + virtual Q_LONG peekBlock(char *data, Q_ULONG maxlen, KSocketAddress& from); + + /** + * Overrides writing. + */ + virtual Q_LONG writeBlock(const char *data, Q_ULONG len); + virtual Q_LONG writeBlock(const char *data, Q_ULONG len, const KSocketAddress& to); + + /** + * Overrides getting socket address. + */ + virtual KSocketAddress localAddress() const; + + /** + * Overrides getting peer address. + */ + virtual KSocketAddress peerAddress() const; + + /** + * Overrides getting external address. + */ + virtual KSocketAddress externalAddress() const; + + /** + * Overrides polling. + */ + virtual bool poll(bool* input, bool* output, bool* exception = 0L, + int timeout = -1, bool* timedout = 0L); + +private: + static void initSocks(); + friend class KSocketDevice; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/ksrvresolverworker.cpp b/kdecore/network/ksrvresolverworker.cpp new file mode 100644 index 000000000..bcfee405c --- /dev/null +++ b/kdecore/network/ksrvresolverworker.cpp @@ -0,0 +1,257 @@ +/* -*- C++ -*- + * Copyright (C) 2005 Thiago Macieira <thiago@kde.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <config.h> + +#include "ksrvresolverworker_p.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdlib.h> + +#include <qapplication.h> +#include <qevent.h> + +using namespace KNetwork; +using namespace KNetwork::Internal; + +namespace +{ + struct KSrvStartEvent: public QCustomEvent + { + inline KSrvStartEvent() : QCustomEvent(QEvent::User) { } + }; +} + +static void sortPriorityClass(KSrvResolverWorker::PriorityClass&) +{ + // do nothing +} + +bool KSrvResolverWorker::preprocess() +{ + // check if the resolver flags mention SRV-based lookup + if ((flags() & (KResolver::NoSrv | KResolver::UseSrv)) != KResolver::UseSrv) + return false; + + QString node = nodeName(); + if (node.find('%') != -1) + node.truncate(node.find('%')); + + if (node.isEmpty() || node == QString::fromLatin1("*") || + node == QString::fromLatin1("localhost")) + return false; // empty == localhost + + encodedName = KResolver::domainToAscii(node); + if (encodedName.isNull()) + return false; + + // we only work with Internet-based families + if ((familyMask() & KResolver::InternetFamily) == 0) + return false; + + // SRV-based resolution only works if the service isn't numeric + bool ok; + serviceName().toUInt(&ok); + if (ok) + return false; // it is numeric + + // check the protocol for something we know + QCString protoname; + int sockettype = socketType(); + if (!protocolName().isEmpty()) + protoname = protocolName(); + else if (protocol() != 0) + { + QStrList names = KResolver::protocolName(protocol()); + names.setAutoDelete(true); + if (names.isEmpty()) + return false; + + protoname = "_"; + protoname += names.at(0); + } + else if (sockettype == SOCK_STREAM || sockettype == 0) + protoname = "_tcp"; + else if (sockettype == SOCK_DGRAM) + protoname = "_udp"; + else + return false; // unknown protocol and socket type + + encodedName.prepend("."); + encodedName.prepend(protoname); + encodedName.prepend("."); + encodedName.prepend(serviceName().latin1()); + encodedName.prepend("_"); + + // looks like something we could process + return true; +} + +bool KSrvResolverWorker::run() +{ + sem = new QSemaphore(1); + // zero out + sem->tryAccess(sem->available()); + + QApplication::postEvent(this, new KSrvStartEvent); + + // block + (*sem)++; + delete sem; + sem = 0L; + + if (rawResults.isEmpty()) + { + // normal lookup + KResolver *r = new KResolver(nodeName(), serviceName()); + r->setFlags(flags() | KResolver::NoSrv); + r->setFamily(familyMask()); + r->setSocketType(socketType()); + r->setProtocol(protocol(), protocolName()); + + enqueue(r); + + Entry e; + PriorityClass cl; + e.resolver = r; + cl.entries.append(e); + myResults[0] = cl; + + return true; + } + else if (rawResults.count() == 1 && rawResults.first().name == ".") + { + // no name + setError(KResolver::NoName); + finished(); + return true; + } + else + { + // now process the results + QValueList<QDns::Server>::ConstIterator it = rawResults.begin(); + while (it != rawResults.end()) + { + const QDns::Server& srv = *it; + PriorityClass& r = myResults[srv.priority]; + r.totalWeight += srv.weight; + + Entry e; + e.name = srv.name; + e.port = srv.port; + e.weight = srv.weight; + e.resolver = 0L; + r.entries.append(e); + + ++it; + } + rawResults.clear(); // free memory + + Results::Iterator mapit; + for (mapit = myResults.begin(); mapit != myResults.end(); ++mapit) + { + // sort the priority + sortPriorityClass(*mapit); + + QValueList<Entry>& entries = (*mapit).entries; + + // start the resolvers + for (QValueList<Entry>::Iterator it = entries.begin(); + it != entries.end(); ++it) + { + Entry &e = *it; + + KResolver* r = new KResolver(e.name, QString::number(e.port)); + r->setFlags(flags() | KResolver::NoSrv); + r->setFamily(familyMask()); + r->setSocketType(socketType()); + r->setProtocol(protocol(), protocolName()); + + enqueue(r); + e.resolver = r; + } + } + + return true; + } +} + +bool KSrvResolverWorker::postprocess() +{ + setError(KResolver::NoName); + if (myResults.isEmpty()) + return false; + + Results::Iterator mapit, mapend; + for (mapit = myResults.begin(), mapend = myResults.end(); + mapit != mapend; ++mapit) + { + QValueList<Entry>::Iterator it = (*mapit).entries.begin(), + end = (*mapit).entries.end(); + for ( ; it != end; ++it) + { + Entry &e = *it; + KResolverResults r = e.resolver->results(); + if (r.isEmpty() && results.isEmpty()) + setError(r.error(), r.systemError()); + else + { + setError(KResolver::NoError); + results += r; + } + } + } + + finished(); + return true; +} + +void KSrvResolverWorker::customEvent(QCustomEvent*) +{ + dns = new QDns(QString::fromLatin1(encodedName), QDns::Srv); + QObject::connect(dns, SIGNAL(resultsReady()), this, SLOT(dnsResultsReady())); +} + +void KSrvResolverWorker::dnsResultsReady() +{ + (*sem)--; + rawResults = dns->servers(); + dns->deleteLater(); + dns = 0L; +} + +namespace KNetwork +{ + namespace Internal + { + + void initSrvWorker() KDE_NO_EXPORT; + void initSrvWorker() + { + if (getenv("KDE_NO_SRV") != NULL) + return; + + KResolverWorkerFactoryBase::registerNewWorker(new KResolverWorkerFactory<KSrvResolverWorker>); + } + + } +} + +#include "ksrvresolverworker_p.moc" diff --git a/kdecore/network/ksrvresolverworker_p.h b/kdecore/network/ksrvresolverworker_p.h new file mode 100644 index 000000000..143fb0d4b --- /dev/null +++ b/kdecore/network/ksrvresolverworker_p.h @@ -0,0 +1,85 @@ +/* -*- C++ -*- + * Copyright (C) 2005 Thiago Macieira <thiago@kde.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KSRVRESOLVERWORKER_P_H +#define KSRVRESOLVERWORKER_P_H + +#include <qobject.h> +#include <qdns.h> +#include <qsemaphore.h> +#include <qvaluelist.h> +#include <qdict.h> +#include "kresolver.h" +#include "kresolverworkerbase.h" + +class QCustomEvent; + +namespace KNetwork +{ + namespace Internal + { + /** + * @internal + * This class implements SRV-based resolution + */ + class KSrvResolverWorker: public QObject, + public KNetwork::KResolverWorkerBase + { + Q_OBJECT + + public: + struct Entry + { + QString name; + Q_UINT16 port; + Q_UINT16 weight; + KNetwork::KResolver* resolver; + }; + + struct PriorityClass + { + PriorityClass() : totalWeight(0) { } + + QValueList<Entry> entries; + Q_UINT16 totalWeight; + }; + + private: + QDns *dns; + QValueList<QDns::Server> rawResults; + QCString encodedName; + QSemaphore *sem; + + typedef QMap<Q_UINT16, PriorityClass> Results; + Results myResults; + + public: + virtual bool preprocess(); + virtual bool run(); + virtual bool postprocess(); + + virtual void customEvent(QCustomEvent*); + + public slots: + void dnsResultsReady(); + }; + } +} + +#endif diff --git a/kdecore/network/kstreamsocket.cpp b/kdecore/network/kstreamsocket.cpp new file mode 100644 index 000000000..93c423804 --- /dev/null +++ b/kdecore/network/kstreamsocket.cpp @@ -0,0 +1,368 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include <config.h> + +#include <qsocketnotifier.h> +#include <qdatetime.h> +#include <qtimer.h> +#include <qguardedptr.h> + +#include "ksocketaddress.h" +#include "kresolver.h" +#include "ksocketdevice.h" +#include "kstreamsocket.h" + +using namespace KNetwork; + +class KNetwork::KStreamSocketPrivate +{ +public: + KResolverResults::ConstIterator local, peer; + QTime startTime; + QTimer timer; + + int timeout; + + inline KStreamSocketPrivate() + : timeout(0) + { } +}; + +KStreamSocket::KStreamSocket(const QString& node, const QString& service, + QObject* parent, const char *name) + : KClientSocketBase(parent, name), d(new KStreamSocketPrivate) +{ + peerResolver().setNodeName(node); + peerResolver().setServiceName(service); + peerResolver().setFamily(KResolver::KnownFamily); + localResolver().setFamily(KResolver::KnownFamily); + + setSocketOptions(socketOptions() & ~Blocking); + + QObject::connect(&d->timer, SIGNAL(timeout()), this, SLOT(timeoutSlot())); +} + +KStreamSocket::~KStreamSocket() +{ + delete d; + // KClientSocketBase's destructor closes the socket +} + +int KStreamSocket::timeout() const +{ + return d->timeout; +} + +int KStreamSocket::remainingTimeout() const +{ + if (state() != Connecting) + return timeout(); + if (timeout() <= 0) + return 0; + + return timeout() - d->startTime.elapsed(); +} + +void KStreamSocket::setTimeout(int msecs) +{ + d->timeout = msecs; + + if (state() == Connecting) + d->timer.changeInterval(msecs); +} + +bool KStreamSocket::bind(const QString& node, const QString& service) +{ + if (state() != Idle) + return false; + + if (!node.isNull()) + localResolver().setNodeName(node); + if (!service.isNull()) + localResolver().setServiceName(service); + return true; +} + +bool KStreamSocket::connect(const QString& node, const QString& service) +{ + if (state() == Connected) + return true; // already connected + + if (state() > Connected) + return false; // can't do much here + + if (!node.isNull()) + peerResolver().setNodeName(node); + if (!service.isNull()) + peerResolver().setServiceName(service); + + if (state() == Connecting && !blocking()) + { + setError(IO_ConnectError, InProgress); + emit gotError(InProgress); + return true; // we're already connecting + } + + if (state() < HostFound) + { + // connection hasn't started yet + if (!blocking()) + { + QObject::connect(this, SIGNAL(hostFound()), SLOT(hostFoundSlot())); + return lookup(); + } + + // blocking mode + if (!lookup()) + return false; // lookup failure + } + + /* + * lookup results are available here + */ + + if (timeout() > 0) + { + if (!blocking() && !d->timer.isActive()) + d->timer.start(timeout(), true); + else + { + // blocking connection with timeout + // this must be handled as a special case because it requires a + // non-blocking socket + + d->timer.stop(); // no need for a timer here + + socketDevice()->setBlocking(false); + while (true) + { + connectionEvent(); + if (state() < Connecting) + return false; // error connecting + if (state() == Connected) + return true; // connected! + + if (remainingTimeout() <= 0) + { + // we've timed out + timeoutSlot(); + return false; + } + + if (socketDevice()->error() == InProgress) + { + bool timedout; + socketDevice()->poll(remainingTimeout(), &timedout); + if (timedout) + { + timeoutSlot(); + return false; + } + } + } + } + } + + connectionEvent(); + return error() == NoError; +} + +bool KStreamSocket::connect(const KResolverEntry& entry) +{ + return KClientSocketBase::connect(entry); +} + +void KStreamSocket::hostFoundSlot() +{ + QObject::disconnect(this, SLOT(hostFoundSlot())); + if (timeout() > 0) + d->timer.start(timeout(), true); + QTimer::singleShot(0, this, SLOT(connectionEvent())); +} + +void KStreamSocket::connectionEvent() +{ + if (state() != HostFound && state() != Connecting) + return; // nothing to do + + const KResolverResults& peer = peerResults(); + if (state() == HostFound) + { + d->startTime.start(); + + setState(Connecting); + emit stateChanged(Connecting); + d->peer = peer.begin(); + d->local = localResults().begin(); // just to be on the safe side + } + + while (d->peer != peer.end()) + { + const KResolverEntry &r = *d->peer; + + if (socketDevice()->socket() != -1) + { + // we have an existing file descriptor + // this means that we've got activity in it (connection result) + if (socketDevice()->connect(r) && socketDevice()->error() == NoError) + { + // yes, it did connect! + connectionSucceeded(r); + return; + } + else if (socketDevice()->error() == InProgress) + // nope, still in progress + return; + + // no, the socket failed to connect + copyError(); + socketDevice()->close(); + ++d->peer; + continue; + } + + // try to bind + if (!bindLocallyFor(r)) + { + // could not find a matching family + ++d->peer; + continue; + } + + { + bool skip = false; + emit aboutToConnect(r, skip); + if (skip) + { + ++d->peer; + continue; + } + } + + if (socketDevice()->connect(r) || socketDevice()->error() == InProgress) + { + // socket is attempting to connect + if (socketDevice()->error() == InProgress) + { + QSocketNotifier *n = socketDevice()->readNotifier(); + QObject::connect(n, SIGNAL(activated(int)), + this, SLOT(connectionEvent())); + n->setEnabled(true); + + n = socketDevice()->writeNotifier(); + QObject::connect(n, SIGNAL(activated(int)), + this, SLOT(connectionEvent())); + n->setEnabled(true); + + return; // wait for activity + } + + // socket has connected + connectionSucceeded(r); + return; + } + + // connection failed + // try next + copyError(); + socketDevice()->close(); + ++d->peer; + } + + // that was the last item + socketDevice()->setSocketOptions(socketOptions()); + setState(Idle); + emit stateChanged(Idle); + emit gotError(error()); + return; +} + +void KStreamSocket::timeoutSlot() +{ + if (state() != Connecting) + return; + + // halt the connections + socketDevice()->close(); // this also kills the notifiers + + setError(IO_TimeOutError, Timeout); + setState(HostFound); + emit stateChanged(HostFound); + + QGuardedPtr<KStreamSocket> that = this; + emit gotError(Timeout); + if (!that.isNull()) + emit timedOut(); +} + +bool KStreamSocket::bindLocallyFor(const KResolverEntry& peer) +{ + const KResolverResults& local = localResults(); + + if (local.isEmpty()) + // user doesn't want to bind to any specific local address + return true; + + bool foundone = false; + // scan the local resolution for a matching family + for (d->local = local.begin(); d->local != local.end(); ++d->local) + if ((*d->local).family() == peer.family()) + { + // found a suitable address! + foundone = true; + + if (socketDevice()->bind(*d->local)) + return true; + } + + if (!foundone) + { + // found nothing + setError(IO_BindError, NotSupported); + emit gotError(NotSupported); + } + else + copyError(); + return false; +} + +void KStreamSocket::connectionSucceeded(const KResolverEntry& peer) +{ + QObject::disconnect(socketDevice()->readNotifier(), 0, this, SLOT(connectionEvent())); + QObject::disconnect(socketDevice()->writeNotifier(), 0, this, SLOT(connectionEvent())); + + resetError(); + setFlags(IO_Sequential | IO_Raw | IO_ReadWrite | IO_Open | IO_Async); + setState(Connected); + socketDevice()->setSocketOptions(socketOptions()); + d->timer.stop(); + emit stateChanged(Connected); + + if (!localResults().isEmpty()) + emit bound(*d->local); + emit connected(peer); +} + +#include "kstreamsocket.moc" diff --git a/kdecore/network/kstreamsocket.h b/kdecore/network/kstreamsocket.h new file mode 100644 index 000000000..d9be79657 --- /dev/null +++ b/kdecore/network/kstreamsocket.h @@ -0,0 +1,249 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago@kde.org> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KSTREAMSOCKET_H +#define KSTREAMSOCKET_H + +#include <qstring.h> + +#include "kclientsocketbase.h" + +/** A namespace to store all networking-related (socket) classes. */ +namespace KNetwork { + +class KResolverEntry; +class KResolverResults; +class KServerSocket; +class KBufferedSocket; + +class KStreamSocketPrivate; +/** @class KStreamSocket kstreamsocket.h kstreamsocket.h + * @brief Simple stream socket + * + * This class provides functionality to creating unbuffered, stream + * sockets. In the case of Internet (IP) sockets, this class creates and + * uses TCP/IP sockets. + * + * Objects of this class start, by default, on non-blocking mode. Call + * setBlocking if you wish to change that. + * + * KStreamSocket objects are thread-safe and can be used in auxiliary + * threads (i.e., not the thread in which the Qt event loop runs in). + * Note that KBufferedSocket cannot be used reliably in an auxiliary thread. + * + * Sample usage: + * \code + * QByteArray httpGet(const QString& hostname) + * { + * KStreamSocket socket(hostname, "http"); + * if (!socket.connect()) + * return QByteArray(); + * QByteArray data = socket.readAll(); + * return data; + * } + * \endcode + * + * Here's another sample, showing asynchronous operation: + * \code + * DataRetriever::DataRetriever(const QString& hostname, const QString& port) + * : socket(hostname, port) + * { + * // connect signals to our slots + * QObject::connect(&socket, SIGNAL(connected(const KResolverEntry&)), + * this, SLOT(slotSocketConnected())); + * QObject::connect(&socket, SIGNAL(gotError(int)), + * this, SLOT(slotSocketError(int))); + * QObject::connect(&socket, SIGNAL(readyRead()), + * this, SLOT(slotSocketReadyToRead())); + * QObject::connect(&socket, SIGNAL(readyWrite()), + * this, SLOT(slotSocketReadyToWrite())); + * + * // set non-blocking mode in order to work asynchronously + * socket.setBlocking(false); + * + * // turn on signal emission + * socket.enableRead(true); + * socket.enableWrite(true); + * + * // start connecting + * socket.connect(); + * } + * \endcode + * + * @see KNetwork::KBufferedSocket, KNetwork::KServerSocket + * @author Thiago Macieira <thiago@kde.org> + */ +class KDECORE_EXPORT KStreamSocket: public KClientSocketBase +{ + Q_OBJECT + +public: + /** + * Default constructor. + * + * @param node destination host + * @param service destination service to connect to + * @param parent the parent QObject object + * @param name name for this object + */ + KStreamSocket(const QString& node = QString::null, const QString& service = QString::null, + QObject* parent = 0L, const char *name = 0L); + + /** + * Destructor. This closes the socket. + */ + virtual ~KStreamSocket(); + + /** + * Retrieves the timeout value (in milliseconds). + */ + int timeout() const; + + /** + * Retrieves the remaining timeout time (in milliseconds). This value + * equals @ref timeout() if there's no connection in progress. + */ + int remainingTimeout() const; + + /** + * Sets the timeout value. Setting this value while a connection attempt + * is in progress will reset the timer. + * + * Please note that the timeout value is valid for the connection attempt + * only. No other operations are timed against this value -- including the + * name lookup associated. + * + * @param msecs the timeout value in milliseconds + */ + void setTimeout(int msecs); + + /** + * Binds this socket to the given nodename and service, + * or use the default ones if none are given. In order to bind to a service + * and allow the operating system to choose the interface, set @p node to + * QString::null. + * + * Reimplemented from KClientSocketBase. + * + * Upon successful binding, the @ref bound signal will be + * emitted. If an error is found, the @ref gotError + * signal will be emitted. + * + * @note Due to the internals of the name lookup and binding + * mechanism, some (if not most) implementations of this function + * do not actually bind the socket until the connection + * is requested (see @ref connect). They only set the values + * for future reference. + * + * This function returns true on success. + * + * @param node the nodename + * @param service the service + */ + virtual bool bind(const QString& node = QString::null, + const QString& service = QString::null); + + /** + * Reimplemented from KClientSocketBase. Connect this socket to this + * specific address. + * + * Unlike @ref bind(const QString&, const QString&) above, this function + * really does bind the socket. No lookup is performed. The @ref bound + * signal will be emitted. + */ + virtual bool bind(const KResolverEntry& entry) + { return KClientSocketBase::bind(entry); } + + /** + * Reimplemented from KClientSocketBase. + * + * Attempts to connect to the these hostname and service, + * or use the default ones if none are given. If a connection attempt + * is already in progress, check on its state and set the error status + * (NoError, meaning the connection is completed, or InProgress). + * + * If the blocking mode for this object is on, this function will only + * return when all the resolved peer addresses have been tried or when + * a connection is established. + * + * Upon successfully connecting, the @ref connected signal + * will be emitted. If an error is found, the @ref gotError + * signal will be emitted. + * + * This function also implements timeout handling. + * + * @param node the remote node to connect to + * @param service the service on the remote node to connect to + */ + virtual bool connect(const QString& node = QString::null, + const QString& service = QString::null); + + /** + * Unshadowing from KClientSocketBase. + */ + virtual bool connect(const KResolverEntry& entry); + +signals: + /** + * This signal is emitted when a connection timeout occurs. + */ + void timedOut(); + +private slots: + void hostFoundSlot(); + void connectionEvent(); + void timeoutSlot(); + +private: + /** + * @internal + * If the user requested local bind before connection, bind the socket to one + * suitable address and return true. Also sets d->local to the address used. + * + * Return false in case of error. + */ + bool bindLocallyFor(const KResolverEntry& peer); + + /** + * @internal + * Finishes the connection process by setting internal values and + * emitting the proper signals. + * + * Note: assumes d->local iterator points to the address that we bound + * to. + */ + void connectionSucceeded(const KResolverEntry& peer); + + KStreamSocket(const KStreamSocket&); + KStreamSocket& operator=(const KStreamSocket&); + + KStreamSocketPrivate *d; + + friend class KServerSocket; + friend class KBufferedSocket; +}; + +} // namespace KNetwork + +#endif diff --git a/kdecore/network/syssocket.h b/kdecore/network/syssocket.h new file mode 100644 index 000000000..e34db1bd1 --- /dev/null +++ b/kdecore/network/syssocket.h @@ -0,0 +1,93 @@ +/* -*- C++ -*- + * Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net> + * + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef KDE_SYSSOCKET_H +#define KDE_SYSSOCKET_H + +#ifdef KSOCKETBASE_H +#error syssocket.h must be included before ksocketbase.h! +#endif + +#include <sys/types.h> +#include <sys/socket.h> + +namespace { + + /* + * These function here are just wrappers for the real system calls. + * + * Unfortunately, a number of systems out there work by redefining + * symbols through the preprocessor -- symbols that we need. + * + * So we write wrappers for all the low-level system calls. + * + * Qt has a very similar implementation. I got the idea from them, but + * I copied no code. + */ + + // socket + inline int kde_socket(int af, int style, int protocol) + { + return ::socket(af, style, protocol); + } + + // bind + inline int kde_bind(int fd, const struct sockaddr* sa, socklen_t len) + { + return ::bind(fd, sa, len); + } + + // listen + inline int kde_listen(int fd, int backlog) + { + return ::listen(fd, backlog); + } + + // connect + inline int kde_connect(int fd, const struct sockaddr* sa, socklen_t len) + { + return ::connect(fd, (struct sockaddr*)sa, len); + } + + // accept + inline int kde_accept(int fd, struct sockaddr* sa, socklen_t* len) + { + return ::accept(fd, sa, len); + } + + // getpeername + inline int kde_getpeername(int fd, struct sockaddr* sa, socklen_t* len) + { + return ::getpeername(fd, sa, len); + } + + // getsockname + inline int kde_getsockname(int fd, struct sockaddr* sa, socklen_t* len) + { + return ::getsockname(fd, sa, len); + } + +} // anonymous namespace + +#endif |