diff options
Diffstat (limited to 'src/network/socket')
33 files changed, 15451 insertions, 0 deletions
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp new file mode 100644 index 0000000000..910e30aee9 --- /dev/null +++ b/src/network/socket/qabstractsocket.cpp @@ -0,0 +1,2631 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QABSTRACTSOCKET_DEBUG + +/*! + \class QAbstractSocket + + \brief The QAbstractSocket class provides the base functionality + common to all socket types. + + \reentrant + \ingroup io + \inmodule QtNetwork + + QAbstractSocket is the base class for QTcpSocket and QUdpSocket + and contains all common functionality of these two classes. If + you need a socket, you have two options: + + \list + \i Instantiate QTcpSocket or QUdpSocket. + \i Create a native socket descriptor, instantiate + QAbstractSocket, and call setSocketDescriptor() to wrap the + native socket. + \endlist + + TCP (Transmission Control Protocol) is a reliable, + stream-oriented, connection-oriented transport protocol. UDP + (User Datagram Protocol) is an unreliable, datagram-oriented, + connectionless protocol. In practice, this means that TCP is + better suited for continuous transmission of data, whereas the + more lightweight UDP can be used when reliability isn't + important. + + QAbstractSocket's API unifies most of the differences between the + two protocols. For example, although UDP is connectionless, + connectToHost() establishes a virtual connection for UDP sockets, + enabling you to use QAbstractSocket in more or less the same way + regardless of the underlying protocol. Internally, + QAbstractSocket remembers the address and port passed to + connectToHost(), and functions like read() and write() use these + values. + + At any time, QAbstractSocket has a state (returned by + state()). The initial state is UnconnectedState. After + calling connectToHost(), the socket first enters + HostLookupState. If the host is found, QAbstractSocket enters + ConnectingState and emits the hostFound() signal. When the + connection has been established, it enters ConnectedState and + emits connected(). If an error occurs at any stage, error() is + emitted. Whenever the state changes, stateChanged() is emitted. + For convenience, isValid() returns true if the socket is ready for + reading and writing, but note that the socket's state must be + ConnectedState before reading and writing can occur. + + Read or write data by calling read() or write(), or use the + convenience functions readLine() and readAll(). QAbstractSocket + also inherits getChar(), putChar(), and ungetChar() from + QIODevice, which work on single bytes. The bytesWritten() signal + is emitted when data has been written to the socket (i.e., when + the client has read the data). Note that Qt does not limit the + write buffer size. You can monitor its size by listening to this + signal. + + The readyRead() signal is emitted every time a new chunk of data + has arrived. bytesAvailable() then returns the number of bytes + that are available for reading. Typically, you would connect the + readyRead() signal to a slot and read all available data there. + If you don't read all the data at once, the remaining data will + still be available later, and any new incoming data will be + appended to QAbstractSocket's internal read buffer. To limit the + size of the read buffer, call setReadBufferSize(). + + To close the socket, call disconnectFromHost(). QAbstractSocket enters + QAbstractSocket::ClosingState. After all pending data has been written to + the socket, QAbstractSocket actually closes the socket, enters + QAbstractSocket::ClosedState, and emits disconnected(). If you want to + abort a connection immediately, discarding all pending data, call abort() + instead. If the remote host closes the connection, QAbstractSocket will + emit error(QAbstractSocket::RemoteHostClosedError), during which the socket + state will still be ConnectedState, and then the disconnected() signal + will be emitted. + + The port and address of the connected peer is fetched by calling + peerPort() and peerAddress(). peerName() returns the host name of + the peer, as passed to connectToHost(). localPort() and + localAddress() return the port and address of the local socket. + + QAbstractSocket provides a set of functions that suspend the + calling thread until certain signals are emitted. These functions + can be used to implement blocking sockets: + + \list + \o waitForConnected() blocks until a connection has been established. + + \o waitForReadyRead() blocks until new data is available for + reading. + + \o waitForBytesWritten() blocks until one payload of data has been + written to the socket. + + \o waitForDisconnected() blocks until the connection has closed. + \endlist + + We show an example: + + \snippet doc/src/snippets/network/tcpwait.cpp 0 + + If \l{QIODevice::}{waitForReadyRead()} returns false, the + connection has been closed or an error has occurred. + + Programming with a blocking socket is radically different from + programming with a non-blocking socket. A blocking socket doesn't + require an event loop and typically leads to simpler code. + However, in a GUI application, blocking sockets should only be + used in non-GUI threads, to avoid freezing the user interface. + See the \l network/fortuneclient and \l network/blockingfortuneclient + examples for an overview of both approaches. + + QAbstractSocket can be used with QTextStream and QDataStream's + stream operators (operator<<() and operator>>()). There is one + issue to be aware of, though: You must make sure that enough data + is available before attempting to read it using operator>>(). + + \sa QFtp, QHttp, QTcpServer +*/ + +/*! + \fn void QAbstractSocket::hostFound() + + This signal is emitted after connectToHost() has been called and + the host lookup has succeeded. + + \sa connected() +*/ + +/*! + \fn void QAbstractSocket::connected() + + This signal is emitted after connectToHost() has been called and + a connection has been successfully established. + + \sa connectToHost(), disconnected() +*/ + +/*! + \fn void QAbstractSocket::disconnected() + + This signal is emitted when the socket has been disconnected. + + \warning If you need to delete the sender() of this signal in a slot connected + to it, use the \l{QObject::deleteLater()}{deleteLater()} function. + + \sa connectToHost(), disconnectFromHost(), abort() +*/ + +/*! + \fn void QAbstractSocket::error(QAbstractSocket::SocketError socketError) + + This signal is emitted after an error occurred. The \a socketError + parameter describes the type of error that occurred. + + QAbstractSocket::SocketError is not a registered metatype, so for queued + connections, you will have to register it with Q_REGISTER_METATYPE. + + \sa error(), errorString() +*/ + +/*! + \fn void QAbstractSocket::stateChanged(QAbstractSocket::SocketState socketState) + + This signal is emitted whenever QAbstractSocket's state changes. + The \a socketState parameter is the new state. + + QAbstractSocket::SocketState is not a registered metatype, so for queued + connections, you will have to register it with Q_REGISTER_METATYPE. + + \sa state() +*/ + +/*! + \fn void QAbstractSocket::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) + \since 4.3 + + This signal can be emitted when a \a proxy that requires + authentication is used. The \a authenticator object can then be + filled in with the required details to allow authentication and + continue the connection. + + \note It is not possible to use a QueuedConnection to connect to + this signal, as the connection will fail if the authenticator has + not been filled in with new information when the signal returns. + + \sa QAuthenticator, QNetworkProxy +*/ + +/*! + \enum QAbstractSocket::NetworkLayerProtocol + + This enum describes the network layer protocol values used in Qt. + + \value IPv4Protocol IPv4 + \value IPv6Protocol IPv6 + \value UnknownNetworkLayerProtocol Other than IPv4 and IPv6 + + \sa QHostAddress::protocol() +*/ + +/*! + \enum QAbstractSocket::SocketType + + This enum describes the transport layer protocol. + + \value TcpSocket TCP + \value UdpSocket UDP + \value UnknownSocketType Other than TCP and UDP + + \sa QAbstractSocket::socketType() +*/ + +/*! + \enum QAbstractSocket::SocketError + + This enum describes the socket errors that can occur. + + \value ConnectionRefusedError The connection was refused by the + peer (or timed out). + \value RemoteHostClosedError The remote host closed the + connection. Note that the client socket (i.e., this socket) + will be closed after the remote close notification has + been sent. + \value HostNotFoundError The host address was not found. + \value SocketAccessError The socket operation failed because the + application lacked the required privileges. + \value SocketResourceError The local system ran out of resources + (e.g., too many sockets). + \value SocketTimeoutError The socket operation timed out. + \value DatagramTooLargeError The datagram was larger than the + operating system's limit (which can be as low as 8192 + bytes). + \value NetworkError An error occurred with the network (e.g., the + network cable was accidentally plugged out). + \value AddressInUseError The address specified to QUdpSocket::bind() is + already in use and was set to be exclusive. + \value SocketAddressNotAvailableError The address specified to + QUdpSocket::bind() does not belong to the host. + \value UnsupportedSocketOperationError The requested socket operation is + not supported by the local operating system (e.g., lack of + IPv6 support). + \value ProxyAuthenticationRequiredError The socket is using a proxy, and + the proxy requires authentication. + \value SslHandshakeFailedError The SSL/TLS handshake failed, so + the connection was closed (only used in QSslSocket) + \value UnfinishedSocketOperationError Used by QAbstractSocketEngine only, + The last operation attempted has not finished yet (still in progress in + the background). + \value ProxyConnectionRefusedError Could not contact the proxy server because + the connection to that server was denied + \value ProxyConnectionClosedError The connection to the proxy server was closed + unexpectedly (before the connection to the final peer was established) + \value ProxyConnectionTimeoutError The connection to the proxy server timed out + or the proxy server stopped responding in the authentication phase. + \value ProxyNotFoundError The proxy address set with setProxy() (or the application + proxy) was not found. + \value ProxyProtocolError The connection negotiation with the proxy server + because the response from the proxy server could not be understood. + + \value UnknownSocketError An unidentified error occurred. + \sa QAbstractSocket::error() +*/ + +/*! + \enum QAbstractSocket::SocketState + + This enum describes the different states in which a socket can be. + + \value UnconnectedState The socket is not connected. + \value HostLookupState The socket is performing a host name lookup. + \value ConnectingState The socket has started establishing a connection. + \value ConnectedState A connection is established. + \value BoundState The socket is bound to an address and port (for servers). + \value ClosingState The socket is about to close (data may still + be waiting to be written). + \value ListeningState For internal use only. + \omitvalue Idle + \omitvalue HostLookup + \omitvalue Connecting + \omitvalue Connected + \omitvalue Closing + \omitvalue Connection + + \sa QAbstractSocket::state() +*/ + +#include "qabstractsocket.h" +#include "qabstractsocket_p.h" + +#include <qabstracteventdispatcher.h> +#include <qdatetime.h> +#include <qhostaddress.h> +#include <qhostinfo.h> +#include <qmetaobject.h> +#include <qpointer.h> +#include <qtimer.h> + +#ifndef QT_NO_OPENSSL +#include <QtNetwork/qsslsocket.h> +#endif + +#include <private/qthread_p.h> + +#ifdef QABSTRACTSOCKET_DEBUG +#include <qdebug.h> +#endif + +#include <time.h> + +#define Q_CHECK_SOCKETENGINE(returnValue) do { \ + if (!d->socketEngine) { \ + return returnValue; \ + } } while (0) + +#ifndef QABSTRACTSOCKET_BUFFERSIZE +#define QABSTRACTSOCKET_BUFFERSIZE 32768 +#endif +#define QT_CONNECT_TIMEOUT 30000 +#define QT_TRANSFER_TIMEOUT 120000 + +QT_BEGIN_NAMESPACE + +#if defined QABSTRACTSOCKET_DEBUG +QT_BEGIN_INCLUDE_NAMESPACE +#include <qstring.h> +#include <ctype.h> +QT_END_INCLUDE_NAMESPACE + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxLength) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(int(uchar(c)))) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxLength) + out += "..."; + + return out; +} +#endif + +static bool isProxyError(QAbstractSocket::SocketError error) +{ + switch (error) { + case QAbstractSocket::ProxyAuthenticationRequiredError: + case QAbstractSocket::ProxyConnectionRefusedError: + case QAbstractSocket::ProxyConnectionClosedError: + case QAbstractSocket::ProxyConnectionTimeoutError: + case QAbstractSocket::ProxyNotFoundError: + case QAbstractSocket::ProxyProtocolError: + return true; + default: + return false; + } +} + +/*! \internal + + Constructs a QAbstractSocketPrivate. Initializes all members. +*/ +QAbstractSocketPrivate::QAbstractSocketPrivate() + : readSocketNotifierCalled(false), + readSocketNotifierState(false), + readSocketNotifierStateSet(false), + emittedReadyRead(false), + emittedBytesWritten(false), + abortCalled(false), + closeCalled(false), + pendingClose(false), + port(0), + localPort(0), + peerPort(0), + socketEngine(0), + cachedSocketDescriptor(-1), + readBufferMaxSize(0), + readBuffer(QABSTRACTSOCKET_BUFFERSIZE), + writeBuffer(QABSTRACTSOCKET_BUFFERSIZE), + isBuffered(false), + blockingTimeout(30000), + connectTimer(0), + connectTimeElapsed(0), + hostLookupId(-1), + state(QAbstractSocket::UnconnectedState), + socketError(QAbstractSocket::UnknownSocketError) +{ +} + +/*! \internal + + Destructs the QAbstractSocket. If the socket layer is open, it + will be reset. +*/ +QAbstractSocketPrivate::~QAbstractSocketPrivate() +{ +} + +/*! \internal + + Resets the socket layer, clears the read and write buffers and + deletes any socket notifiers. +*/ +void QAbstractSocketPrivate::resetSocketLayer() +{ +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::resetSocketLayer()"); +#endif + + if (socketEngine) { + socketEngine->close(); + socketEngine->disconnect(); + delete socketEngine; + socketEngine = 0; + cachedSocketDescriptor = -1; + } + if (connectTimer) { + connectTimer->stop(); + } +} + +/*! \internal + + Initializes the socket layer to by of type \a type, using the + network layer protocol \a protocol. Resets the socket layer first + if it's already initialized. Sets up the socket notifiers. +*/ +bool QAbstractSocketPrivate::initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol) +{ +#ifdef QT_NO_NETWORKPROXY + // this is here to avoid a duplication of the call to createSocketEngine below + static const QNetworkProxy &proxyInUse = *(QNetworkProxy *)0; +#endif + + Q_Q(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + QString typeStr; + if (q->socketType() == QAbstractSocket::TcpSocket) typeStr = "TcpSocket"; + else if (q->socketType() == QAbstractSocket::UdpSocket) typeStr = "UdpSocket"; + else typeStr = "UnknownSocketType"; + QString protocolStr; + if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = "IPv4Protocol"; + else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = "IPv6Protocol"; + else protocolStr = "UnknownNetworkLayerProtocol"; +#endif + + resetSocketLayer(); + socketEngine = QAbstractSocketEngine::createSocketEngine(q->socketType(), proxyInUse, q); + if (!socketEngine) { + socketError = QAbstractSocket::UnsupportedSocketOperationError; + q->setErrorString(QAbstractSocket::tr("Operation on socket is not supported")); + return false; + } + if (!socketEngine->initialize(q->socketType(), protocol)) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::initSocketLayer(%s, %s) failed (%s)", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData(), + socketEngine->errorString().toLatin1().constData()); +#endif + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + return false; + } + + if (threadData->eventDispatcher) + socketEngine->setReceiver(this); + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::initSocketLayer(%s, %s) success", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData()); +#endif + return true; +} + +/*! \internal + + Slot connected to the read socket notifier. This slot is called + when new data is available for reading, or when the socket has + been closed. Handles recursive calls. +*/ +bool QAbstractSocketPrivate::canReadNotification() +{ + Q_Q(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification()"); +#endif + + // Prevent recursive calls + if (readSocketNotifierCalled) { + if (!readSocketNotifierStateSet) { + readSocketNotifierStateSet = true; + readSocketNotifierState = socketEngine->isReadNotificationEnabled(); + socketEngine->setReadNotificationEnabled(false); + } + } + readSocketNotifierCalled = true; + + if (!isBuffered) + socketEngine->setReadNotificationEnabled(false); + + // If buffered, read data from the socket into the read buffer + qint64 newBytes = 0; + if (isBuffered) { + // Return if there is no space in the buffer + if (readBufferMaxSize && readBuffer.size() >= readBufferMaxSize) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() buffer is full"); +#endif + readSocketNotifierCalled = false; + return false; + } + + // If reading from the socket fails after getting a read + // notification, close the socket. + newBytes = readBuffer.size(); + if (!readFromSocket()) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() disconnecting socket"); +#endif + q->disconnectFromHost(); + readSocketNotifierCalled = false; + return false; + } + newBytes = readBuffer.size() - newBytes; + + // If read buffer is full, disable the read socket notifier. + if (readBufferMaxSize && readBuffer.size() == readBufferMaxSize) { + socketEngine->setReadNotificationEnabled(false); + } + } + + // only emit readyRead() when not recursing, and only if there is data available + bool hasData = newBytes > 0 +#ifndef QT_NO_UDPSOCKET + || (!isBuffered && socketEngine && socketEngine->hasPendingDatagrams()) +#endif + ; + + if (!emittedReadyRead && hasData) { + emittedReadyRead = true; + emit q->readyRead(); + emittedReadyRead = false; + } + + // If we were closed as a result of the readyRead() signal, + // return. + if (state == QAbstractSocket::UnconnectedState || state == QAbstractSocket::ClosingState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canReadNotification() socket is closing - returning"); +#endif + readSocketNotifierCalled = false; + return true; + } + + if (!hasData && socketEngine) + socketEngine->setReadNotificationEnabled(true); + + // reset the read socket notifier state if we reentered inside the + // readyRead() connected slot. + if (readSocketNotifierStateSet && socketEngine && + readSocketNotifierState != socketEngine->isReadNotificationEnabled()) { + socketEngine->setReadNotificationEnabled(readSocketNotifierState); + readSocketNotifierStateSet = false; + } + readSocketNotifierCalled = false; + return true; +} + +/*! \internal + + Slot connected to the write socket notifier. It's called during a + delayed connect or when the socket is ready for writing. +*/ +bool QAbstractSocketPrivate::canWriteNotification() +{ +#if defined (Q_OS_WIN) + if (socketEngine && socketEngine->isWriteNotificationEnabled()) + socketEngine->setWriteNotificationEnabled(false); +#endif + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::canWriteNotification() flushing"); +#endif + int tmp = writeBuffer.size(); + flush(); + + if (socketEngine) { +#if defined (Q_OS_WIN) + if (!writeBuffer.isEmpty()) + socketEngine->setWriteNotificationEnabled(true); +#else + if (writeBuffer.isEmpty()) + socketEngine->setWriteNotificationEnabled(false); +#endif + } + + return (writeBuffer.size() < tmp); +} + +/*! \internal + + Slot connected to a notification of connection status + change. Either we finished connecting or we failed to connect. +*/ +void QAbstractSocketPrivate::connectionNotification() +{ + // If in connecting state, check if the connection has been + // established, otherwise flush pending data. + if (state == QAbstractSocket::ConnectingState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::connectionNotification() testing connection"); +#endif + _q_testConnection(); + } +} + +/*! \internal + + Writes pending data in the write buffers to the socket. The + function writes as much as it can without blocking. + + It is usually invoked by canWriteNotification after one or more + calls to write(). + + Emits bytesWritten(). +*/ +bool QAbstractSocketPrivate::flush() +{ + Q_Q(QAbstractSocket); + if (!socketEngine || !socketEngine->isValid() || writeBuffer.isEmpty()) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::flush() nothing to do: valid ? %s, writeBuffer.isEmpty() ? %s", + socketEngine->isValid() ? "yes" : "no", writeBuffer.isEmpty() ? "yes" : "no"); +#endif + return false; + } + + int nextSize = writeBuffer.nextDataBlockSize(); + const char *ptr = writeBuffer.readPointer(); + + // Attempt to write it all in one chunk. + qint64 written = socketEngine->write(ptr, nextSize); + if (written < 0) { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + emit q->error(socketError); + // an unexpected error so close the socket. +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug() << "QAbstractSocketPrivate::flush() write error, aborting." << socketEngine->errorString(); +#endif + q->abort(); + return false; + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::flush() %lld bytes written to the network", + written); +#endif + + // Remove what we wrote so far. + writeBuffer.free(written); + if (written > 0) { + // Don't emit bytesWritten() recursively. + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(written); + emittedBytesWritten = false; + } + } + + if (writeBuffer.isEmpty() && socketEngine && socketEngine->isWriteNotificationEnabled()) + socketEngine->setWriteNotificationEnabled(false); + if (state == QAbstractSocket::ClosingState) + q->disconnectFromHost(); + + return true; +} + +#ifndef QT_NO_NETWORKPROXY +/*! \internal + + Resolve the proxy to its final value. +*/ +void QAbstractSocketPrivate::resolveProxy(const QString &hostname, quint16 port) +{ + QHostAddress parsed; + if (hostname == QLatin1String("localhost") + || hostname.startsWith(QLatin1String("localhost.")) + || (parsed.setAddress(hostname) + && (parsed == QHostAddress::LocalHost + || parsed == QHostAddress::LocalHostIPv6))) { + proxyInUse = QNetworkProxy::NoProxy; + return; + } + + QList<QNetworkProxy> proxies; + + if (proxy.type() != QNetworkProxy::DefaultProxy) { + // a non-default proxy was set with setProxy + proxies << proxy; + } else { + // try the application settings instead + QNetworkProxyQuery query(hostname, port, QString(), + socketType == QAbstractSocket::TcpSocket ? + QNetworkProxyQuery::TcpSocket : + QNetworkProxyQuery::UdpSocket); + proxies = QNetworkProxyFactory::proxyForQuery(query); + } + + // return the first that we can use + foreach (const QNetworkProxy &p, proxies) { + if (socketType == QAbstractSocket::UdpSocket && + (p.capabilities() & QNetworkProxy::UdpTunnelingCapability) == 0) + continue; + + if (socketType == QAbstractSocket::TcpSocket && + (p.capabilities() & QNetworkProxy::TunnelingCapability) == 0) + continue; + + proxyInUse = p; + return; + } + + // no proxy found + // DefaultProxy here will raise an error + proxyInUse = QNetworkProxy(); +} + +/*! + \internal + + Starts the connection to \a host, like _q_startConnecting below, + but without hostname resolution. +*/ +void QAbstractSocketPrivate::startConnectingByName(const QString &host) +{ + Q_Q(QAbstractSocket); + if (state == QAbstractSocket::ConnectingState || state == QAbstractSocket::ConnectedState) + return; + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::startConnectingByName(host == %s)", qPrintable(host)); +#endif + + // ### Let the socket engine drive this? + state = QAbstractSocket::ConnectingState; + emit q->stateChanged(state); + + connectTimeElapsed = 0; + + if (initSocketLayer(QAbstractSocket::UnknownNetworkLayerProtocol)) { + if (socketEngine->connectToHostByName(host, port) || + socketEngine->state() == QAbstractSocket::ConnectingState) { + cachedSocketDescriptor = socketEngine->socketDescriptor(); + + return; + } + + // failed to connect + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + } + + state = QAbstractSocket::UnconnectedState; + emit q->error(socketError); + emit q->stateChanged(state); +} + +#endif + +/*! \internal + + Slot connected to QHostInfo::lookupHost() in connectToHost(). This + function starts the process of connecting to any number of + candidate IP addresses for the host, if it was found. Calls + _q_connectToNextAddress(). +*/ +void QAbstractSocketPrivate::_q_startConnecting(const QHostInfo &hostInfo) +{ + Q_Q(QAbstractSocket); + if (state != QAbstractSocket::HostLookupState) + return; + + addresses = hostInfo.addresses(); + +#if defined(QABSTRACTSOCKET_DEBUG) + QString s = "{"; + for (int i = 0; i < addresses.count(); ++i) { + if (i != 0) s += ", "; + s += addresses.at(i).toString(); + } + s += "}"; + qDebug("QAbstractSocketPrivate::_q_startConnecting(hostInfo == %s)", s.toLatin1().constData()); +#endif + + // Try all addresses twice. + addresses += addresses; + + // If there are no addresses in the host list, report this to the + // user. + if (addresses.isEmpty()) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_startConnecting(), host not found"); +#endif + state = QAbstractSocket::UnconnectedState; + socketError = QAbstractSocket::HostNotFoundError; + q->setErrorString(QAbstractSocket::tr("Host not found")); + emit q->stateChanged(state); + emit q->error(QAbstractSocket::HostNotFoundError); + return; + } + + // Enter Connecting state (see also sn_write, which is called by + // the write socket notifier after connect()) + state = QAbstractSocket::ConnectingState; + emit q->stateChanged(state); + + // Report the successful host lookup + emit q->hostFound(); + + // Reset the total time spent connecting. + connectTimeElapsed = 0; + + // The addresses returned by the lookup will be tested one after + // another by _q_connectToNextAddress(). + _q_connectToNextAddress(); +} + +/*! \internal + + Called by a queued or direct connection from _q_startConnecting() or + _q_testConnection(), this function takes the first address of the + pending addresses list and tries to connect to it. If the + connection succeeds, QAbstractSocket will emit + connected(). Otherwise, error(ConnectionRefusedError) or + error(SocketTimeoutError) is emitted. +*/ +void QAbstractSocketPrivate::_q_connectToNextAddress() +{ + Q_Q(QAbstractSocket); + do { + // Check for more pending addresses + if (addresses.isEmpty()) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), all addresses failed."); +#endif + state = QAbstractSocket::UnconnectedState; + if (socketEngine) { + if ((socketEngine->error() == QAbstractSocket::UnknownSocketError +#ifdef Q_OS_AIX + // On AIX, the second connect call will result in EINVAL and not + // ECONNECTIONREFUSED; although the meaning is the same. + || socketEngine->error() == QAbstractSocket::UnsupportedSocketOperationError +#endif + ) && socketEngine->state() == QAbstractSocket::ConnectingState) { + socketError = QAbstractSocket::ConnectionRefusedError; + q->setErrorString(QAbstractSocket::tr("Connection refused")); + } else { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + } + } else { +// socketError = QAbstractSocket::ConnectionRefusedError; +// q->setErrorString(QAbstractSocket::tr("Connection refused")); + } + emit q->stateChanged(state); + emit q->error(socketError); + return; + } + + // Pick the first host address candidate + host = addresses.takeFirst(); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), connecting to %s:%i, %d left to try", + host.toString().toLatin1().constData(), port, addresses.count()); +#endif + +#if defined(QT_NO_IPV6) + if (host.protocol() == QAbstractSocket::IPv6Protocol) { + // If we have no IPv6 support, then we will not be able to + // connect. So we just pretend we didn't see this address. +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), skipping IPv6 entry"); +#endif + continue; + } +#endif + + if (!initSocketLayer(host.protocol())) { + // hope that the next address is better +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), failed to initialize sock layer"); +#endif + continue; + } + + // Tries to connect to the address. If it succeeds immediately + // (localhost address on BSD or any UDP connect), emit + // connected() and return. + if (socketEngine->connectToHost(host, port)) { + //_q_testConnection(); + fetchConnectionParameters(); + return; + } + + // cache the socket descriptor even if we're not fully connected yet + cachedSocketDescriptor = socketEngine->socketDescriptor(); + + // Check that we're in delayed connection state. If not, try + // the next address + if (socketEngine->state() != QAbstractSocket::ConnectingState) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), connection failed (%s)", + socketEngine->errorString().toLatin1().constData()); +#endif + continue; + } + + // Start the connect timer. + if (threadData->eventDispatcher) { + if (!connectTimer) { + connectTimer = new QTimer(q); + QObject::connect(connectTimer, SIGNAL(timeout()), + q, SLOT(_q_abortConnectionAttempt()), + Qt::DirectConnection); + } + connectTimer->start(QT_CONNECT_TIMEOUT); + } + + // Wait for a write notification that will eventually call + // _q_testConnection(). + socketEngine->setWriteNotificationEnabled(true); + break; + } while (state != QAbstractSocket::ConnectedState); +} + +/*! \internal + + Tests if a connection has been established. If it has, connected() + is emitted. Otherwise, _q_connectToNextAddress() is invoked. +*/ +void QAbstractSocketPrivate::_q_testConnection() +{ + if (socketEngine) { + if (threadData->eventDispatcher) { + if (connectTimer) + connectTimer->stop(); + } + + if (socketEngine->state() == QAbstractSocket::ConnectedState) { + // Fetch the parameters if our connection is completed; + // otherwise, fall out and try the next address. + fetchConnectionParameters(); + if (pendingClose) { + q_func()->disconnectFromHost(); + pendingClose = false; + } + return; + } + + // don't retry the other addresses if we had a proxy error + if (isProxyError(socketEngine->error())) + addresses.clear(); + } + + if (threadData->eventDispatcher) { + if (connectTimer) + connectTimer->stop(); + } + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_testConnection() connection failed," + " checking for alternative addresses"); +#endif + _q_connectToNextAddress(); +} + +/*! \internal + + This function is called after a certain number of seconds has + passed while waiting for a connection. It simply tests the + connection, and continues to the next address if the connection + failed. +*/ +void QAbstractSocketPrivate::_q_abortConnectionAttempt() +{ + Q_Q(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::_q_abortConnectionAttempt() (timed out)"); +#endif + if (socketEngine) + socketEngine->setWriteNotificationEnabled(false); + connectTimer->stop(); + + if (addresses.isEmpty()) { + state = QAbstractSocket::UnconnectedState; + socketError = QAbstractSocket::SocketTimeoutError; + q->setErrorString(QAbstractSocket::tr("Connection timed out")); + emit q->stateChanged(state); + emit q->error(socketError); + } else { + _q_connectToNextAddress(); + } +} + +/*! \internal + + Reads data from the socket layer into the read buffer. Returns + true on success; otherwise false. +*/ +bool QAbstractSocketPrivate::readFromSocket() +{ + Q_Q(QAbstractSocket); + // Find how many bytes we can read from the socket layer. + qint64 bytesToRead = socketEngine->bytesAvailable(); +#ifdef Q_OS_LINUX + if (bytesToRead > 0) // ### See setSocketDescriptor() + bytesToRead += addToBytesAvailable; +#endif + if (bytesToRead == 0) { + // Under heavy load, certain conditions can trigger read notifications + // for socket notifiers on which there is no activity. If we continue + // to read 0 bytes from the socket, we will trigger behavior similar + // to that which signals a remote close. When we hit this condition, + // we try to read 4k of data from the socket, which will give us either + // an EAGAIN/EWOULDBLOCK if the connection is alive (i.e., the remote + // host has _not_ disappeared). + bytesToRead = 4096; + } + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) + bytesToRead = readBufferMaxSize - readBuffer.size(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() about to read %d bytes", + int(bytesToRead)); +#endif + + // Read from the socket, store data in the read buffer. + char *ptr = readBuffer.reserve(bytesToRead); + qint64 readBytes = socketEngine->read(ptr, bytesToRead); + if (readBytes == -2) { + // No bytes currently available for reading. + readBuffer.chop(bytesToRead); + return true; + } + readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes))); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() got %d bytes, buffer size = %d", + int(readBytes), readBuffer.size()); +#endif + + if (!socketEngine->isValid()) { + socketError = socketEngine->error(); + q->setErrorString(socketEngine->errorString()); + emit q->error(socketError); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::readFromSocket() read failed: %s", + q->errorString().toLatin1().constData()); +#endif + resetSocketLayer(); + return false; + } + + return true; +} + +/*! \internal + + Sets up the the internal state after the connection has succeeded. +*/ +void QAbstractSocketPrivate::fetchConnectionParameters() +{ + Q_Q(QAbstractSocket); + + peerName = hostName; + if (socketEngine) { + socketEngine->setReadNotificationEnabled(true); + socketEngine->setWriteNotificationEnabled(true); + localPort = socketEngine->localPort(); + peerPort = socketEngine->peerPort(); + localAddress = socketEngine->localAddress(); + peerAddress = socketEngine->peerAddress(); + cachedSocketDescriptor = socketEngine->socketDescriptor(); + } + + state = QAbstractSocket::ConnectedState; + emit q->stateChanged(state); + emit q->connected(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocketPrivate::fetchConnectionParameters() connection to %s:%i established", + host.toString().toLatin1().constData(), port); +#endif +} + +/*! \internal + + Constructs a new abstract socket of type \a socketType. The \a + parent argument is passed to QObject's constructor. +*/ +QAbstractSocket::QAbstractSocket(SocketType socketType, + QAbstractSocketPrivate &dd, QObject *parent) + : QIODevice(dd, parent) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::QAbstractSocket(%sSocket, QAbstractSocketPrivate == %p, parent == %p)", + socketType == TcpSocket ? "Tcp" : socketType == UdpSocket + ? "Udp" : "Unknown", &dd, parent); +#endif + d->socketType = socketType; +} + +/*! + Creates a new abstract socket of type \a socketType. The \a + parent argument is passed to QObject's constructor. + + \sa socketType(), QTcpSocket, QUdpSocket +*/ +QAbstractSocket::QAbstractSocket(SocketType socketType, QObject *parent) + : QIODevice(*new QAbstractSocketPrivate, parent) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::QAbstractSocket(%p)", parent); +#endif + d->socketType = socketType; +} + +/*! + Destroys the socket. +*/ +QAbstractSocket::~QAbstractSocket() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::~QAbstractSocket()"); +#endif + if (d->state != UnconnectedState) + abort(); +} + +/*! + Returns true if the socket is valid and ready for use; otherwise + returns false. + + \bold{Note:} The socket's state must be ConnectedState before reading and + writing can occur. + + \sa state() +*/ +bool QAbstractSocket::isValid() const +{ + return d_func()->socketEngine ? d_func()->socketEngine->isValid() : isOpen(); +} + +/*! + Attempts to make a connection to \a hostName on the given \a port. + + The socket is opened in the given \a openMode and first enters + HostLookupState, then performs a host name lookup of \a hostName. + If the lookup succeeds, hostFound() is emitted and QAbstractSocket + enters ConnectingState. It then attempts to connect to the address + or addresses returned by the lookup. Finally, if a connection is + established, QAbstractSocket enters ConnectedState and + emits connected(). + + At any point, the socket can emit error() to signal that an error + occurred. + + \a hostName may be an IP address in string form (e.g., + "43.195.83.32"), or it may be a host name (e.g., + "qtsoftware.com"). QAbstractSocket will do a lookup only if + required. \a port is in native byte order. + + \sa state(), peerName(), peerAddress(), peerPort(), waitForConnected() +*/ +void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, + OpenMode openMode) +{ + QMetaObject::invokeMethod(this, "connectToHostImplementation", + Qt::DirectConnection, + Q_ARG(QString, hostName), + Q_ARG(quint16, port), + Q_ARG(OpenMode, openMode)); +} + +/*! + \since 4.1 + + Contains the implementation of connectToHost(). + + Attempts to make a connection to \a hostName on the given \a + port. The socket is opened in the given \a openMode. +*/ +void QAbstractSocket::connectToHostImplementation(const QString &hostName, quint16 port, + OpenMode openMode) +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost(\"%s\", %i, %i)...", qPrintable(hostName), port, + (int) openMode); +#endif + + if (d->state == ConnectedState || d->state == ConnectingState) { + qWarning("QAbstractSocket::connectToHost() called when already connecting/connected to \"%s\"", qPrintable(hostName)); + return; + } + + d->hostName = hostName; + d->port = port; + d->state = UnconnectedState; + d->readBuffer.clear(); + d->writeBuffer.clear(); + d->abortCalled = false; + d->closeCalled = false; + d->pendingClose = false; + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + d->peerName = hostName; +#ifdef Q_OS_LINUX + // ### See setSocketDescriptor(). + d->addToBytesAvailable = 0; +#endif + if (d->hostLookupId != -1) { + QHostInfo::abortHostLookup(d->hostLookupId); + d->hostLookupId = -1; + } + +#ifndef QT_NO_NETWORKPROXY + // Get the proxy information + d->resolveProxy(hostName, port); + if (d->proxyInUse.type() == QNetworkProxy::DefaultProxy) { + // failed to setup the proxy + d->socketError = QAbstractSocket::UnsupportedSocketOperationError; + setErrorString(QAbstractSocket::tr("Operation on socket is not supported")); + emit error(d->socketError); + return; + } +#endif + + if (!d_func()->isBuffered) + openMode |= QAbstractSocket::Unbuffered; + QIODevice::open(openMode); + d->state = HostLookupState; + emit stateChanged(d->state); + + QHostAddress temp; + if (temp.setAddress(hostName)) { + QHostInfo info; + info.setAddresses(QList<QHostAddress>() << temp); + d->_q_startConnecting(info); +#ifndef QT_NO_NETWORKPROXY + } else if (d->proxyInUse.capabilities() & QNetworkProxy::HostNameLookupCapability) { + // the proxy supports connection by name, so use it + d->startConnectingByName(hostName); + return; +#endif + } else { + if (d->threadData->eventDispatcher) + d->hostLookupId = QHostInfo::lookupHost(hostName, this, SLOT(_q_startConnecting(QHostInfo))); + } + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost(\"%s\", %i) == %s%s", hostName.toLatin1().constData(), port, + (d->state == ConnectedState) ? "true" : "false", + (d->state == ConnectingState || d->state == HostLookupState) + ? " (connection in progress)" : ""); +#endif +} + +/*! \overload + + Attempts to make a connection to \a address on port \a port. +*/ +void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, + OpenMode openMode) +{ +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::connectToHost([%s], %i, %i)...", + address.toString().toLatin1().constData(), port, (int) openMode); +#endif + connectToHost(address.toString(), port, openMode); +} + +/*! + Returns the number of bytes that are waiting to be written. The + bytes are written when control goes back to the event loop or + when flush() is called. + + \sa bytesAvailable(), flush() +*/ +qint64 QAbstractSocket::bytesToWrite() const +{ + Q_D(const QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::bytesToWrite() == %i", d->writeBuffer.size()); +#endif + return (qint64)d->writeBuffer.size(); +} + +/*! + Returns the number of incoming bytes that are waiting to be read. + + \sa bytesToWrite(), read() +*/ +qint64 QAbstractSocket::bytesAvailable() const +{ + Q_D(const QAbstractSocket); + qint64 available = QIODevice::bytesAvailable(); + if (d->isBuffered) + available += (qint64) d->readBuffer.size(); + else if (d->socketEngine && d->socketEngine->isValid()) + available += d->socketEngine->bytesAvailable(); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::bytesAvailable() == %llu", available); +#endif + return available; +} + +/*! + Returns the host port number (in native byte order) of the local + socket if available; otherwise returns 0. + + \sa localAddress(), peerPort(), setLocalPort() +*/ +quint16 QAbstractSocket::localPort() const +{ + Q_D(const QAbstractSocket); + return d->localPort; +} + +/*! + Returns the host address of the local socket if available; + otherwise returns QHostAddress::Null. + + This is normally the main IP address of the host, but can be + QHostAddress::LocalHost (127.0.0.1) for connections to the + local host. + + \sa localPort(), peerAddress(), setLocalAddress() +*/ +QHostAddress QAbstractSocket::localAddress() const +{ + Q_D(const QAbstractSocket); + return d->localAddress; +} + +/*! + Returns the port of the connected peer if the socket is in + ConnectedState; otherwise returns 0. + + \sa peerAddress(), localPort(), setPeerPort() +*/ +quint16 QAbstractSocket::peerPort() const +{ + Q_D(const QAbstractSocket); + return d->peerPort; +} + +/*! + Returns the address of the connected peer if the socket is in + ConnectedState; otherwise returns QHostAddress::Null. + + \sa peerName(), peerPort(), localAddress(), setPeerAddress() +*/ +QHostAddress QAbstractSocket::peerAddress() const +{ + Q_D(const QAbstractSocket); + return d->peerAddress; +} + +/*! + Returns the name of the peer as specified by connectToHost(), or + an empty QString if connectToHost() has not been called. + + \sa peerAddress(), peerPort(), setPeerName() +*/ +QString QAbstractSocket::peerName() const +{ + Q_D(const QAbstractSocket); + return d->peerName.isEmpty() ? d->hostName : d->peerName; +} + +/*! + Returns true if a line of data can be read from the socket; + otherwise returns false. + + \sa readLine() +*/ +bool QAbstractSocket::canReadLine() const +{ + bool hasLine = d_func()->readBuffer.canReadLine(); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::canReadLine() == %s, buffer size = %d, size = %d", hasLine ? "true" : "false", + d_func()->readBuffer.size(), d_func()->buffer.size()); +#endif + return hasLine || QIODevice::canReadLine(); +} + +/*! + Returns the native socket descriptor of the QAbstractSocket object + if this is available; otherwise returns -1. + + If the socket is using QNetworkProxy, the returned descriptor + may not be usable with native socket functions. + + The socket descriptor is not available when QAbstractSocket is in + UnconnectedState. + + \sa setSocketDescriptor() +*/ +int QAbstractSocket::socketDescriptor() const +{ + Q_D(const QAbstractSocket); + return d->cachedSocketDescriptor; +} + +/*! + Initializes QAbstractSocket with the native socket descriptor \a + socketDescriptor. Returns true if \a socketDescriptor is accepted + as a valid socket descriptor; otherwise returns false. + The socket is opened in the mode specified by \a openMode, and + enters the socket state specified by \a socketState. + + \bold{Note:} It is not possible to initialize two abstract sockets + with the same native socket descriptor. + + \sa socketDescriptor() +*/ +bool QAbstractSocket::setSocketDescriptor(int socketDescriptor, SocketState socketState, + OpenMode openMode) +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->setSocketDescriptor(socketDescriptor, socketState, openMode); +#endif + + d->resetSocketLayer(); + d->socketEngine = QAbstractSocketEngine::createSocketEngine(socketDescriptor, this); + if (!d->socketEngine) { + d->socketError = UnsupportedSocketOperationError; + setErrorString(tr("Operation on socket is not supported")); + return false; + } + bool result = d->socketEngine->initialize(socketDescriptor, socketState); + if (!result) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + return false; + } + + if (d->threadData->eventDispatcher) + d->socketEngine->setReceiver(d); + + QIODevice::open(openMode); + + if (d->state != socketState) { + d->state = socketState; + emit stateChanged(d->state); + } + + d->pendingClose = false; + d->socketEngine->setReadNotificationEnabled(true); + d->localPort = d->socketEngine->localPort(); + d->peerPort = d->socketEngine->peerPort(); + d->localAddress = d->socketEngine->localAddress(); + d->peerAddress = d->socketEngine->peerAddress(); + d->cachedSocketDescriptor = socketDescriptor; + +#ifdef Q_OS_LINUX + // ### This is a workaround for certain broken Linux kernels, when using + // QTcpSocket with a Unix domain socket. It was introduced around 2.6.9, + // and fixed at some point after that. + // http://archive.linux-usenet.com/index-t-73300.html + // We can provide a better workaround for this: readFromSocket() can loop + // while reading, but this must happen without triggering an implicit + // close because of reading after the socket has closed. + d->addToBytesAvailable = 4096; +#endif + + return true; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +/*! + Waits until the socket is connected, up to \a msecs + milliseconds. If the connection has been established, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be established: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 0 + + If msecs is -1, this function will not time out. + + Note: This function may wait slightly longer than \a msecs, + depending on the time it takes to complete the host lookup. + + \sa connectToHost(), connected() +*/ +bool QAbstractSocket::waitForConnected(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i)", msecs); +#endif + + if (state() == ConnectedState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) already connected", msecs); +#endif + return true; + } + +#ifndef QT_NO_OPENSSL + // Manual polymorphism; this function is not virtual, but has an overload + // in QSslSocket. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->waitForConnected(msecs); +#endif + + bool wasPendingClose = d->pendingClose; + d->pendingClose = false; + QTime stopWatch; + stopWatch.start(); + + if (d->state == HostLookupState) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) doing host name lookup", msecs); +#endif + QHostInfo::abortHostLookup(d->hostLookupId); + d->hostLookupId = -1; + d->_q_startConnecting(QHostInfo::fromName(d->hostName)); + } + if (state() == UnconnectedState) + return false; + + bool timedOut = true; +#if defined (QABSTRACTSOCKET_DEBUG) + int attempt = 1; +#endif + while (state() == ConnectingState && (msecs == -1 || stopWatch.elapsed() < msecs)) { + int timeout = qt_timeout_value(msecs, stopWatch.elapsed()); + if (msecs != -1 && timeout > QT_CONNECT_TIMEOUT) + timeout = QT_CONNECT_TIMEOUT; +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) waiting %.2f secs for connection attempt #%i", + msecs, timeout / 1000.0, attempt++); +#endif + timedOut = false; + + if (d->socketEngine && d->socketEngine->waitForWrite(timeout, &timedOut) && !timedOut) { + d->_q_testConnection(); + } else { + d->_q_connectToNextAddress(); + } + } + + if ((timedOut && state() != ConnectedState) || state() == ConnectingState) { + d->socketError = SocketTimeoutError; + d->state = UnconnectedState; + emit stateChanged(d->state); + d->resetSocketLayer(); + setErrorString(tr("Socket operation timed out")); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForConnected(%i) == %s", msecs, + state() == ConnectedState ? "true" : "false"); +#endif + if (state() != ConnectedState) + return false; + if (wasPendingClose) + disconnectFromHost(); + return true; +} + +/*! + This function blocks until data is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds; the default timeout is + 30000 milliseconds. + + The function returns true if the readyRead() signal is emitted and + there is data available for reading; otherwise it returns false + (if an error occurred or the operation timed out). + + \sa waitForBytesWritten() +*/ +bool QAbstractSocket::waitForReadyRead(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i)", msecs); +#endif + + // require calling connectToHost() before waitForReadyRead() + if (state() == UnconnectedState) { + /* If all you have is a QIODevice pointer to an abstractsocket, you cannot check + this, so you cannot avoid this warning. */ +// qWarning("QAbstractSocket::waitForReadyRead() is not allowed in UnconnectedState"); + return false; + } + + QTime stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + } + + Q_ASSERT(d->socketEngine); + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, true, !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) { + if (d->canReadNotification()) + return true; + } + + if (readyToWrite) + d->canWriteNotification(); + + if (state() != ConnectedState) + return false; + } + return false; +} + +/*! \reimp + */ +bool QAbstractSocket::waitForBytesWritten(int msecs) +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten(%i)", msecs); +#endif + + // require calling connectToHost() before waitForBytesWritten() + if (state() == UnconnectedState) { + qWarning("QAbstractSocket::waitForBytesWritten() is not allowed in UnconnectedState"); + return false; + } + + if (d->writeBuffer.isEmpty()) + return false; + + QTime stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + } + + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, true, !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten calls canReadNotification"); +#endif + if(!d->canReadNotification()) + return false; + } + + + if (readyToWrite) { + if (d->canWriteNotification()) { +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForBytesWritten returns true"); +#endif + return true; + } + } + + if (state() != ConnectedState) + return false; + } + return false; +} + +/*! + Waits until the socket has disconnected, up to \a msecs + milliseconds. If the connection has been disconnected, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be closed: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 1 + + If msecs is -1, this function will not time out. + + \sa disconnectFromHost(), close() +*/ +bool QAbstractSocket::waitForDisconnected(int msecs) +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + // Manual polymorphism; this function is not virtual, but has an overload + // in QSslSocket. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->waitForDisconnected(msecs); +#endif + + // require calling connectToHost() before waitForDisconnected() + if (state() == UnconnectedState) { + qWarning("QAbstractSocket::waitForDisconnected() is not allowed in UnconnectedState"); + return false; + } + + QTime stopWatch; + stopWatch.start(); + + // handle a socket in connecting state + if (state() == HostLookupState || state() == ConnectingState) { + if (!waitForConnected(msecs)) + return false; + if (state() == UnconnectedState) + return true; + } + + forever { + bool readyToRead = false; + bool readyToWrite = false; + if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, state() == ConnectedState, + !d->writeBuffer.isEmpty(), + qt_timeout_value(msecs, stopWatch.elapsed()))) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::waitForReadyRead(%i) failed (%i, %s)", + msecs, d->socketError, errorString().toLatin1().constData()); +#endif + emit error(d->socketError); + if (d->socketError != SocketTimeoutError) + close(); + return false; + } + + if (readyToRead) + d->canReadNotification(); + if (readyToWrite) + d->canWriteNotification(); + + if (state() == UnconnectedState) + return true; + } + return false; +} + +/*! + Aborts the current connection and resets the socket. Unlike + disconnectFromHost(), this function immediately closes the socket, discarding + any pending data in the write buffer. + + \sa disconnectFromHost(), close() +*/ +void QAbstractSocket::abort() +{ + Q_D(QAbstractSocket); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::abort()"); +#endif + if (d->state == UnconnectedState) + return; +#ifndef QT_NO_OPENSSL + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) { + socket->abort(); + return; + } +#endif + if (d->connectTimer) { + d->connectTimer->stop(); + delete d->connectTimer; + d->connectTimer = 0; + } + + d->writeBuffer.clear(); + d->abortCalled = true; + close(); +} + +/*! \reimp +*/ +bool QAbstractSocket::isSequential() const +{ + return true; +} + +/*! \reimp + + Returns true if no more data is currently + available for reading; otherwise returns false. + + This function is most commonly used when reading data from the + socket in a loop. For example: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 2 + + \sa bytesAvailable(), readyRead() + */ +bool QAbstractSocket::atEnd() const +{ + return QIODevice::atEnd() && (!isOpen() || d_func()->readBuffer.isEmpty()); +} + +/*! + This function writes as much as possible from the internal write buffer to + the underlying network socket, without blocking. If any data was written, + this function returns true; otherwise false is returned. + + Call this function if you need QAbstractSocket to start sending buffered + data immediately. The number of bytes successfully written depends on the + operating system. In most cases, you do not need to call this function, + because QAbstractSocket will start sending data automatically once control + goes back to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \sa write(), waitForBytesWritten() +*/ +// Note! docs copied to QSslSocket::flush() +bool QAbstractSocket::flush() +{ + Q_D(QAbstractSocket); +#ifndef QT_NO_OPENSSL + // Manual polymorphism; flush() isn't virtual, but QSslSocket overloads + // it. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) + return socket->flush(); +#endif + Q_CHECK_SOCKETENGINE(false); + return d->flush(); +} + +/*! \reimp +*/ +qint64 QAbstractSocket::readData(char *data, qint64 maxSize) +{ + Q_D(QAbstractSocket); + if (d->socketEngine && !d->socketEngine->isReadNotificationEnabled() && d->socketEngine->isValid()) + d->socketEngine->setReadNotificationEnabled(true); + + if (!d->isBuffered) { + if (!d->socketEngine) + return -1; // no socket engine is probably EOF + qint64 readBytes = d->socketEngine->read(data, maxSize); + if (readBytes < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + } + if (!d->socketEngine->isReadNotificationEnabled()) + d->socketEngine->setReadNotificationEnabled(true); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld", + data, qt_prettyDebug(data, 32, readBytes).data(), maxSize, + readBytes); +#endif + return readBytes; + } + + if (d->readBuffer.isEmpty()) + // if we're still connected, return 0 indicating there may be more data in the future + // if we're not connected, return -1 indicating EOF + return d->state == QAbstractSocket::ConnectedState ? qint64(0) : qint64(-1); + + // If readFromSocket() read data, copy it to its destination. + if (maxSize == 1) { + *data = d->readBuffer.getChar(); +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p '%c (0x%.2x)', 1) == 1", + data, isprint(int(uchar(*data))) ? *data : '?', *data); +#endif + return 1; + } + + qint64 bytesToRead = qMin(qint64(d->readBuffer.size()), maxSize); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = d->readBuffer.readPointer(); + int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar), + d->readBuffer.nextDataBlockSize()); + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + d->readBuffer.free(bytesToReadFromThisBlock); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::readData(%p \"%s\", %lli) == %lld", + data, qt_prettyDebug(data, qMin<qint64>(32, readSoFar), readSoFar).data(), + maxSize, readSoFar); +#endif + return readSoFar; +} + +/*! \reimp +*/ +qint64 QAbstractSocket::readLineData(char *data, qint64 maxlen) +{ + return QIODevice::readLineData(data, maxlen); +} + +/*! \reimp +*/ +qint64 QAbstractSocket::writeData(const char *data, qint64 size) +{ + Q_D(QAbstractSocket); + if (d->state == QAbstractSocket::UnconnectedState) { + d->socketError = QAbstractSocket::UnknownSocketError; + setErrorString(tr("Socket is not connected")); + return -1; + } + + if (!d->isBuffered) { + qint64 written = d->socketEngine->write(data, size); + if (written < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + } else if (!d->writeBuffer.isEmpty()) { + d->socketEngine->setWriteNotificationEnabled(true); + } + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data, + qt_prettyDebug(data, qMin((int)size, 32), size).data(), + size, written); +#endif + if (written >= 0) + emit bytesWritten(written); + return written; + } + + char *ptr = d->writeBuffer.reserve(size); + if (size == 1) + *ptr = *data; + else + memcpy(ptr, data, size); + + qint64 written = size; + + if (d->socketEngine && !d->writeBuffer.isEmpty()) + d->socketEngine->setWriteNotificationEnabled(true); + +#if defined (QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::writeData(%p \"%s\", %lli) == %lli", data, + qt_prettyDebug(data, qMin((int)size, 32), size).data(), + size, written); +#endif + return written; +} + +/*! + \since 4.1 + + Sets the port on the local side of a connection to \a port. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the localPort() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + Note that this function does not bind the local port of the socket + prior to a connection (e.g., QUdpSocket::bind()). + + \sa localAddress(), setLocalAddress(), setPeerPort() +*/ +void QAbstractSocket::setLocalPort(quint16 port) +{ + Q_D(QAbstractSocket); + d->localPort = port; +} + +/*! + \since 4.1 + + Sets the address on the local side of a connection to + \a address. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the localAddress() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + Note that this function does not bind the local address of the socket + prior to a connection (e.g., QUdpSocket::bind()). + + \sa localAddress(), setLocalPort(), setPeerAddress() +*/ +void QAbstractSocket::setLocalAddress(const QHostAddress &address) +{ + Q_D(QAbstractSocket); + d->localAddress = address; +} + +/*! + \since 4.1 + + Sets the port of the remote side of the connection to + \a port. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerPort() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerPort(), setPeerAddress(), setLocalPort() +*/ +void QAbstractSocket::setPeerPort(quint16 port) +{ + Q_D(QAbstractSocket); + d->peerPort = port; +} + +/*! + \since 4.1 + + Sets the address of the remote side of the connection + to \a address. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerAddress() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerAddress(), setPeerPort(), setLocalAddress() +*/ +void QAbstractSocket::setPeerAddress(const QHostAddress &address) +{ + Q_D(QAbstractSocket); + d->peerAddress = address; +} + +/*! + \since 4.1 + + Sets the host name of the remote peer to \a name. + + You can call this function in a subclass of QAbstractSocket to + change the return value of the peerName() function after a + connection has been established. This feature is commonly used by + proxy connections for virtual connection settings. + + \sa peerName() +*/ +void QAbstractSocket::setPeerName(const QString &name) +{ + Q_D(QAbstractSocket); + d->peerName = name; +} + +/*! + Disconnects the socket's connection with the host. + + \sa abort() +*/ +void QAbstractSocket::close() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::close()"); +#endif + QIODevice::close(); + if (d->state != UnconnectedState) { + d->closeCalled = true; + disconnectFromHost(); + } + + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + d->peerName.clear(); + d->cachedSocketDescriptor = -1; +} + +/*! + Attempts to close the socket. If there is pending data waiting to + be written, QAbstractSocket will enter ClosingState and wait + until all data has been written. Eventually, it will enter + UnconnectedState and emit the disconnected() signal. + + \sa connectToHost() +*/ +void QAbstractSocket::disconnectFromHost() +{ + QMetaObject::invokeMethod(this, "disconnectFromHostImplementation", + Qt::DirectConnection); +} + +/*! + \since 4.1 + + Contains the implementation of disconnectFromHost(). +*/ +void QAbstractSocket::disconnectFromHostImplementation() +{ + Q_D(QAbstractSocket); +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost()"); +#endif + + if (d->state == UnconnectedState) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() was called on an unconnected socket"); +#endif + return; + } + + if (!d->abortCalled && (d->state == ConnectingState || d->state == HostLookupState)) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() but we're still connecting"); +#endif + d->pendingClose = true; + return; + } + +#ifdef QT3_SUPPORT + emit connectionClosed(); // compat signal +#endif + + // Disable and delete read notification + if (d->socketEngine) + d->socketEngine->setReadNotificationEnabled(false); + + if (d->abortCalled) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() aborting immediately"); +#endif + } else { + // Perhaps emit closing() + if (d->state != ClosingState) { + d->state = ClosingState; +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() emits stateChanged()(ClosingState)"); +#endif + emit stateChanged(d->state); + } else { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() return from delayed close"); +#endif + } + + // Wait for pending data to be written. + if (d->socketEngine && d->socketEngine->isValid() && d->writeBuffer.size() > 0) { + d->socketEngine->setWriteNotificationEnabled(true); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() delaying disconnect"); +#endif + return; + } else { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() disconnecting immediately"); +#endif + } + } + + SocketState previousState = d->state; + d->resetSocketLayer(); + d->state = UnconnectedState; + emit stateChanged(d->state); + emit readChannelFinished(); // we got an EOF + +#ifdef QT3_SUPPORT + emit delayedCloseFinished(); // compat signal +#endif + // only emit disconnected if we were connected before + if (previousState == ConnectedState || ClosingState) + emit disconnected(); + + d->localPort = 0; + d->peerPort = 0; + d->localAddress.clear(); + d->peerAddress.clear(); + +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() disconnected!"); +#endif + + if (d->closeCalled) { +#if defined(QABSTRACTSOCKET_DEBUG) + qDebug("QAbstractSocket::disconnectFromHost() closed!"); +#endif + d->readBuffer.clear(); + d->writeBuffer.clear(); + QIODevice::close(); + } +} + +/*! + Returns the size of the internal read buffer. This limits the + amount of data that the client can receive before you call read() + or readAll(). + + A read buffer size of 0 (the default) means that the buffer has + no size limit, ensuring that no data is lost. + + \sa setReadBufferSize(), read() +*/ +qint64 QAbstractSocket::readBufferSize() const +{ + return d_func()->readBufferMaxSize; +} + +/*! + Sets the size of QAbstractSocket's internal read buffer to be \a + size bytes. + + If the buffer size is limited to a certain size, QAbstractSocket + won't buffer more than this size of data. Exceptionally, a buffer + size of 0 means that the read buffer is unlimited and all + incoming data is buffered. This is the default. + + This option is useful if you only read the data at certain points + in time (e.g., in a real-time streaming application) or if you + want to protect your socket against receiving too much data, + which may eventually cause your application to run out of memory. + + Only QTcpSocket uses QAbstractSocket's internal buffer; QUdpSocket + does not use any buffering at all, but rather relies on the + implicit buffering provided by the operating system. + Because of this, calling this function on QUdpSocket has no + effect. + + \sa readBufferSize(), read() +*/ +void QAbstractSocket::setReadBufferSize(qint64 size) +{ + Q_D(QAbstractSocket); + +#ifndef QT_NO_OPENSSL + // Manual polymorphism; setReadBufferSize() isn't virtual, but QSslSocket overloads + // it. + if (QSslSocket *socket = qobject_cast<QSslSocket *>(this)) { + socket->setReadBufferSize(size); + return; + } +#endif + + if (d->readBufferMaxSize == size) + return; + d->readBufferMaxSize = size; + if (!d->readSocketNotifierCalled && d->socketEngine) { + // ensure that the read notification is enabled if we've now got + // room in the read buffer + // but only if we're not inside canReadNotification -- that will take care on its own + if (size == 0 || d->readBuffer.size() < size) + d->socketEngine->setReadNotificationEnabled(true); + } +} + +/*! + Returns the state of the socket. + + \sa error() +*/ +QAbstractSocket::SocketState QAbstractSocket::state() const +{ + return d_func()->state; +} + +/*! + Sets the state of the socket to \a state. + + \sa state() +*/ +void QAbstractSocket::setSocketState(SocketState state) +{ + d_func()->state = state; +} + +/*! + Returns the socket type (TCP, UDP, or other). + + \sa QTcpSocket, QUdpSocket +*/ +QAbstractSocket::SocketType QAbstractSocket::socketType() const +{ + return d_func()->socketType; +} + +/*! + Returns the type of error that last occurred. + + \sa state(), errorString() +*/ +QAbstractSocket::SocketError QAbstractSocket::error() const +{ + return d_func()->socketError; +} + +/*! + Sets the type of error that last occurred to \a socketError. + + \sa setSocketState(), setErrorString() +*/ +void QAbstractSocket::setSocketError(SocketError socketError) +{ + d_func()->socketError = socketError; +} + +#ifndef QT_NO_NETWORKPROXY +/*! + \since 4.1 + + Sets the explicit network proxy for this socket to \a networkProxy. + + To disable the use of a proxy for this socket, use the + QNetworkProxy::NoProxy proxy type: + + \snippet doc/src/snippets/code/src_network_socket_qabstractsocket.cpp 3 + + The default value for the proxy is QNetworkProxy::DefaultProxy, + which means the socket will use the application settings: if a + proxy is set with QNetworkProxy::setApplicationProxy, it will use + that; otherwise, if a factory is set with + QNetworkProxyFactory::setApplicationProxyFactory, it will query + that factory with type QNetworkProxyQuery::TcpSocket. + + \sa proxy(), QNetworkProxy, QNetworkProxyFactory::queryProxy() +*/ +void QAbstractSocket::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QAbstractSocket); + d->proxy = networkProxy; +} + +/*! + \since 4.1 + + Returns the network proxy for this socket. + By default QNetworkProxy::DefaultProxy is used, which means this + socket will query the default proxy settings for the application. + + \sa setProxy(), QNetworkProxy, QNetworkProxyFactory +*/ +QNetworkProxy QAbstractSocket::proxy() const +{ + Q_D(const QAbstractSocket); + return d->proxy; +} +#endif // QT_NO_NETWORKPROXY + +#ifdef QT3_SUPPORT +/*! \enum QAbstractSocket::Error + \compat + + Use QAbstractSocket::SocketError instead. + + \value ErrConnectionRefused Use QAbstractSocket::ConnectionRefusedError instead. + \value ErrHostNotFound Use QAbstractSocket::HostNotFoundError instead. + \value ErrSocketRead Use QAbstractSocket::UnknownSocketError instead. +*/ + +/*! + \typedef QAbstractSocket::State + \compat + + Use QAbstractSocket::SocketState instead. + + \table + \header \o Qt 3 enum value \o Qt 4 enum value + \row \o \c Idle \o \l UnconnectedState + \row \o \c HostLookup \o \l HostLookupState + \row \o \c Connecting \o \l ConnectingState + \row \o \c Connected \o \l ConnectedState + \row \o \c Closing \o \l ClosingState + \row \o \c Connection \o \l ConnectedState + \endtable +*/ + +/*! + \fn int QAbstractSocket::socket() const + + Use socketDescriptor() instead. +*/ + +/*! + \fn void QAbstractSocket::setSocket(int socket) + + Use setSocketDescriptor() instead. +*/ + +/*! + \fn Q_ULONG QAbstractSocket::waitForMore(int msecs, bool *timeout = 0) const + + Use waitForReadyRead() instead. + + \oldcode + bool timeout; + Q_ULONG numBytes = socket->waitForMore(30000, &timeout); + \newcode + qint64 numBytes = 0; + if (socket->waitForReadyRead(msecs)) + numBytes = socket->bytesAvailable(); + bool timeout = (error() == QAbstractSocket::SocketTimeoutError); + \endcode + + \sa waitForReadyRead(), bytesAvailable(), error(), SocketTimeoutError +*/ + +/*! + \fn void QAbstractSocket::connectionClosed() + + Use disconnected() instead. +*/ + +/*! + \fn void QAbstractSocket::delayedCloseFinished() + + Use disconnected() instead. +*/ +#endif // QT3_SUPPORT + +#ifndef QT_NO_DEBUG_STREAM +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, QAbstractSocket::SocketError error) +{ + switch (error) { + case QAbstractSocket::ConnectionRefusedError: + debug << "QAbstractSocket::ConnectionRefusedError"; + break; + case QAbstractSocket::RemoteHostClosedError: + debug << "QAbstractSocket::RemoteHostClosedError"; + break; + case QAbstractSocket::HostNotFoundError: + debug << "QAbstractSocket::HostNotFoundError"; + break; + case QAbstractSocket::SocketAccessError: + debug << "QAbstractSocket::SocketAccessError"; + break; + case QAbstractSocket::SocketResourceError: + debug << "QAbstractSocket::SocketResourceError"; + break; + case QAbstractSocket::SocketTimeoutError: + debug << "QAbstractSocket::SocketTimeoutError"; + break; + case QAbstractSocket::DatagramTooLargeError: + debug << "QAbstractSocket::DatagramTooLargeError"; + break; + case QAbstractSocket::NetworkError: + debug << "QAbstractSocket::NetworkError"; + break; + case QAbstractSocket::AddressInUseError: + debug << "QAbstractSocket::AddressInUseError"; + break; + case QAbstractSocket::SocketAddressNotAvailableError: + debug << "QAbstractSocket::SocketAddressNotAvailableError"; + break; + case QAbstractSocket::UnsupportedSocketOperationError: + debug << "QAbstractSocket::UnsupportedSocketOperationError"; + break; + case QAbstractSocket::UnfinishedSocketOperationError: + debug << "QAbstractSocket::UnfinishedSocketOperationError"; + break; + case QAbstractSocket::ProxyAuthenticationRequiredError: + debug << "QAbstractSocket::ProxyAuthenticationRequiredError"; + break; + case QAbstractSocket::UnknownSocketError: + debug << "QAbstractSocket::UnknownSocketError"; + break; + case QAbstractSocket::ProxyConnectionRefusedError: + debug << "QAbstractSocket::ProxyConnectionRefusedError"; + break; + case QAbstractSocket::ProxyConnectionClosedError: + debug << "QAbstractSocket::ProxyConnectionClosedError"; + break; + case QAbstractSocket::ProxyConnectionTimeoutError: + debug << "QAbstractSocket::ProxyConnectionTimeoutError"; + break; + case QAbstractSocket::ProxyNotFoundError: + debug << "QAbstractSocket::ProxyNotFoundError"; + break; + case QAbstractSocket::ProxyProtocolError: + debug << "QAbstractSocket::ProxyProtocolError"; + break; + default: + debug << "QAbstractSocket::SocketError(" << int(error) << ")"; + break; + } + return debug; +} + +Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, QAbstractSocket::SocketState state) +{ + switch (state) { + case QAbstractSocket::UnconnectedState: + debug << "QAbstractSocket::UnconnectedState"; + break; + case QAbstractSocket::HostLookupState: + debug << "QAbstractSocket::HostLookupState"; + break; + case QAbstractSocket::ConnectingState: + debug << "QAbstractSocket::ConnectingState"; + break; + case QAbstractSocket::ConnectedState: + debug << "QAbstractSocket::ConnectedState"; + break; + case QAbstractSocket::BoundState: + debug << "QAbstractSocket::BoundState"; + break; + case QAbstractSocket::ListeningState: + debug << "QAbstractSocket::ListeningState"; + break; + case QAbstractSocket::ClosingState: + debug << "QAbstractSocket::ClosingState"; + break; + default: + debug << "QAbstractSocket::SocketState(" << int(state) << ")"; + break; + } + return debug; +} +#endif + +QT_END_NAMESPACE + +#include "moc_qabstractsocket.cpp" diff --git a/src/network/socket/qabstractsocket.h b/src/network/socket/qabstractsocket.h new file mode 100644 index 0000000000..1b86e92ce2 --- /dev/null +++ b/src/network/socket/qabstractsocket.h @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSOCKET_H +#define QABSTRACTSOCKET_H + +#include <QtCore/qiodevice.h> +#include <QtCore/qobject.h> +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> +#endif + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QHostAddress; +#ifndef QT_NO_NETWORKPROXY +class QNetworkProxy; +#endif +class QAbstractSocketPrivate; +class QAuthenticator; + +class Q_NETWORK_EXPORT QAbstractSocket : public QIODevice +{ + Q_OBJECT +public: + enum SocketType { + TcpSocket, + UdpSocket, + UnknownSocketType = -1 + }; + enum NetworkLayerProtocol { + IPv4Protocol, + IPv6Protocol, + UnknownNetworkLayerProtocol = -1 + }; + enum SocketError { + ConnectionRefusedError, + RemoteHostClosedError, + HostNotFoundError, + SocketAccessError, + SocketResourceError, + SocketTimeoutError, /* 5 */ + DatagramTooLargeError, + NetworkError, + AddressInUseError, + SocketAddressNotAvailableError, + UnsupportedSocketOperationError, /* 10 */ + UnfinishedSocketOperationError, + ProxyAuthenticationRequiredError, + SslHandshakeFailedError, + ProxyConnectionRefusedError, + ProxyConnectionClosedError, /* 15 */ + ProxyConnectionTimeoutError, + ProxyNotFoundError, + ProxyProtocolError, + + UnknownSocketError = -1 + }; + enum SocketState { + UnconnectedState, + HostLookupState, + ConnectingState, + ConnectedState, + BoundState, + ListeningState, + ClosingState +#ifdef QT3_SUPPORT + , + Idle = UnconnectedState, + HostLookup = HostLookupState, + Connecting = ConnectingState, + Connected = ConnectedState, + Closing = ClosingState, + Connection = ConnectedState +#endif + }; + + QAbstractSocket(SocketType socketType, QObject *parent); + virtual ~QAbstractSocket(); + + // ### Qt 5: Make connectToHost() and disconnectFromHost() virtual. + void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite); + void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite); + void disconnectFromHost(); + + bool isValid() const; + + qint64 bytesAvailable() const; + qint64 bytesToWrite() const; + + bool canReadLine() const; + + quint16 localPort() const; + QHostAddress localAddress() const; + quint16 peerPort() const; + QHostAddress peerAddress() const; + QString peerName() const; + + // ### Qt 5: Make setReadBufferSize() virtual + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + void abort(); + + // ### Qt 5: Make socketDescriptor() and setSocketDescriptor() virtual. + int socketDescriptor() const; + bool setSocketDescriptor(int socketDescriptor, SocketState state = ConnectedState, + OpenMode openMode = ReadWrite); + + SocketType socketType() const; + SocketState state() const; + SocketError error() const; + + // from QIODevice + void close(); + bool isSequential() const; + bool atEnd() const; + bool flush(); + + // for synchronous access + // ### Qt 5: Make waitForConnected() and waitForDisconnected() virtual. + bool waitForConnected(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + bool waitForBytesWritten(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + +Q_SIGNALS: + void hostFound(); + void connected(); + void disconnected(); + void stateChanged(QAbstractSocket::SocketState); + void error(QAbstractSocket::SocketError); +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); +#endif + +protected Q_SLOTS: + void connectToHostImplementation(const QString &hostName, quint16 port, OpenMode mode = ReadWrite); + void disconnectFromHostImplementation(); + +protected: + qint64 readData(char *data, qint64 maxlen); + qint64 readLineData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + + void setSocketState(SocketState state); + void setSocketError(SocketError socketError); + void setLocalPort(quint16 port); + void setLocalAddress(const QHostAddress &address); + void setPeerPort(quint16 port); + void setPeerAddress(const QHostAddress &address); + void setPeerName(const QString &name); + + QAbstractSocket(SocketType socketType, QAbstractSocketPrivate &dd, QObject *parent = 0); + +private: + Q_DECLARE_PRIVATE(QAbstractSocket) + Q_DISABLE_COPY(QAbstractSocket) + + Q_PRIVATE_SLOT(d_func(), void _q_connectToNextAddress()) + Q_PRIVATE_SLOT(d_func(), void _q_startConnecting(const QHostInfo &)) + Q_PRIVATE_SLOT(d_func(), void _q_abortConnectionAttempt()) + Q_PRIVATE_SLOT(d_func(), void _q_testConnection()) + +#ifdef QT3_SUPPORT +public: + enum Error { + ErrConnectionRefused = ConnectionRefusedError, + ErrHostNotFound = HostNotFoundError, + ErrSocketRead = UnknownSocketError + }; + inline QT3_SUPPORT int socket() const { return socketDescriptor(); } + inline QT3_SUPPORT void setSocket(int socket) { setSocketDescriptor(socket); } + inline QT3_SUPPORT qulonglong waitForMore(int msecs, bool *timeout = 0) const + { + QAbstractSocket *that = const_cast<QAbstractSocket *>(this); + if (that->waitForReadyRead(msecs)) + return qulonglong(bytesAvailable()); + if (error() == SocketTimeoutError && timeout) + *timeout = true; + return 0; + } + typedef SocketState State; +Q_SIGNALS: + QT_MOC_COMPAT void connectionClosed(); // same as disconnected() + QT_MOC_COMPAT void delayedCloseFinished(); // same as disconnected() + + +#endif +}; + +#ifndef QT_NO_DEBUG_STREAM +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketError); +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QAbstractSocket::SocketState); +#endif + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QABSTRACTSOCKET_H diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h new file mode 100644 index 0000000000..4cb7dcb932 --- /dev/null +++ b/src/network/socket/qabstractsocket_p.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSOCKET_P_H +#define QABSTRACTSOCKET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtNetwork/qabstractsocket.h" +#include "QtCore/qbytearray.h" +#include "QtCore/qlist.h" +#include "QtCore/qtimer.h" +#include "private/qringbuffer_p.h" +#include "private/qiodevice_p.h" +#include "private/qnativesocketengine_p.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +class QHostInfo; + +class QAbstractSocketPrivate : public QIODevicePrivate, public QAbstractSocketEngineReceiver +{ + Q_DECLARE_PUBLIC(QAbstractSocket) +public: + QAbstractSocketPrivate(); + virtual ~QAbstractSocketPrivate(); + + // from QAbstractSocketEngineReceiver + inline void readNotification() { canReadNotification(); } + inline void writeNotification() { canWriteNotification(); } + inline void exceptionNotification() {} + void connectionNotification(); +#ifndef QT_NO_NETWORKPROXY + inline void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) { + Q_Q(QAbstractSocket); + q->proxyAuthenticationRequired(proxy, authenticator); + } +#endif + + bool canReadNotification(); + bool canWriteNotification(); + + // slots + void _q_connectToNextAddress(); + void _q_startConnecting(const QHostInfo &hostInfo); + void _q_testConnection(); + void _q_abortConnectionAttempt(); + + bool readSocketNotifierCalled; + bool readSocketNotifierState; + bool readSocketNotifierStateSet; + + bool emittedReadyRead; + bool emittedBytesWritten; + + bool abortCalled; + bool closeCalled; + bool pendingClose; + + QString hostName; + quint16 port; + QHostAddress host; + QList<QHostAddress> addresses; + + quint16 localPort; + quint16 peerPort; + QHostAddress localAddress; + QHostAddress peerAddress; + QString peerName; + + QAbstractSocketEngine *socketEngine; + int cachedSocketDescriptor; + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + QNetworkProxy proxyInUse; + void resolveProxy(const QString &hostName, quint16 port); +#else + inline void resolveProxy(const QString &, quint16) { } +#endif + inline void resolveProxy(quint16 port) { resolveProxy(QString(), port); } + + void resetSocketLayer(); + bool flush(); + + bool initSocketLayer(QAbstractSocket::NetworkLayerProtocol protocol); + void startConnectingByName(const QString &host); + void fetchConnectionParameters(); + void setupSocketNotifiers(); + bool readFromSocket(); + +#ifdef Q_OS_LINUX + qint64 addToBytesAvailable; +#endif + qint64 readBufferMaxSize; + QRingBuffer readBuffer; + QRingBuffer writeBuffer; + + bool isBuffered; + int blockingTimeout; + + QTimer *connectTimer; + int connectTimeElapsed; + + int hostLookupId; + + QAbstractSocket::SocketType socketType; + QAbstractSocket::SocketState state; + + QAbstractSocket::SocketError socketError; +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTSOCKET_P_H diff --git a/src/network/socket/qabstractsocketengine.cpp b/src/network/socket/qabstractsocketengine.cpp new file mode 100644 index 0000000000..620cf8ba84 --- /dev/null +++ b/src/network/socket/qabstractsocketengine.cpp @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qabstractsocketengine_p.h" +#include "qnativesocketengine_p.h" +#include "qmutex.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +class QSocketEngineHandlerList : public QList<QSocketEngineHandler*> +{ +public: + QMutex mutex; +}; + +Q_GLOBAL_STATIC(QSocketEngineHandlerList, socketHandlers) + +QSocketEngineHandler::QSocketEngineHandler() +{ + if (!socketHandlers()) + return; + QMutexLocker locker(&socketHandlers()->mutex); + socketHandlers()->prepend(this); +} + +QSocketEngineHandler::~QSocketEngineHandler() +{ + if (!socketHandlers()) + return; + QMutexLocker locker(&socketHandlers()->mutex); + socketHandlers()->removeAll(this); +} + +QAbstractSocketEnginePrivate::QAbstractSocketEnginePrivate() + : socketError(QAbstractSocket::UnknownSocketError) + , hasSetSocketError(false) + , socketErrorString(QLatin1String(QT_TRANSLATE_NOOP(QSocketLayer, "Unknown error"))) + , socketState(QAbstractSocket::UnconnectedState) + , socketType(QAbstractSocket::UnknownSocketType) + , socketProtocol(QAbstractSocket::UnknownNetworkLayerProtocol) + , localPort(0) + , peerPort(0) + , receiver(0) +{ +} + +QAbstractSocketEngine::QAbstractSocketEngine(QObject *parent) + : QObject(*new QAbstractSocketEnginePrivate(), parent) +{ +} + +QAbstractSocketEngine::QAbstractSocketEngine(QAbstractSocketEnginePrivate &dd, QObject* parent) + : QObject(dd, parent) +{ +} + +QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &proxy, QObject *parent) +{ +#ifndef QT_NO_NETWORKPROXY + // proxy type must have been resolved by now + if (proxy.type() == QNetworkProxy::DefaultProxy) + return 0; +#endif + + QMutexLocker locker(&socketHandlers()->mutex); + for (int i = 0; i < socketHandlers()->size(); i++) { + if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketType, proxy, parent)) + return ret; + } + +#ifndef QT_NO_NETWORKPROXY + // only NoProxy can have reached here + if (proxy.type() != QNetworkProxy::NoProxy) + return 0; +#endif + + return new QNativeSocketEngine(parent); +} + +QAbstractSocketEngine *QAbstractSocketEngine::createSocketEngine(int socketDescripter, QObject *parent) +{ + QMutexLocker locker(&socketHandlers()->mutex); + for (int i = 0; i < socketHandlers()->size(); i++) { + if (QAbstractSocketEngine *ret = socketHandlers()->at(i)->createSocketEngine(socketDescripter, parent)) + return ret; + } + return new QNativeSocketEngine(parent); +} + +QAbstractSocket::SocketError QAbstractSocketEngine::error() const +{ + return d_func()->socketError; +} + +QString QAbstractSocketEngine::errorString() const +{ + return d_func()->socketErrorString; +} + +void QAbstractSocketEngine::setError(QAbstractSocket::SocketError error, const QString &errorString) const +{ + Q_D(const QAbstractSocketEngine); + d->socketError = error; + d->socketErrorString = errorString; +} + +void QAbstractSocketEngine::setReceiver(QAbstractSocketEngineReceiver *receiver) +{ + d_func()->receiver = receiver; +} + +void QAbstractSocketEngine::readNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->readNotification(); +} + +void QAbstractSocketEngine::writeNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->writeNotification(); +} + +void QAbstractSocketEngine::exceptionNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->exceptionNotification(); +} + +void QAbstractSocketEngine::connectionNotification() +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->connectionNotification(); +} + +#ifndef QT_NO_NETWORKPROXY +void QAbstractSocketEngine::proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator) +{ + if (QAbstractSocketEngineReceiver *receiver = d_func()->receiver) + receiver->proxyAuthenticationRequired(proxy, authenticator); +} +#endif + + +QAbstractSocket::SocketState QAbstractSocketEngine::state() const +{ + return d_func()->socketState; +} + +void QAbstractSocketEngine::setState(QAbstractSocket::SocketState state) +{ + d_func()->socketState = state; +} + +QAbstractSocket::SocketType QAbstractSocketEngine::socketType() const +{ + return d_func()->socketType; +} + +void QAbstractSocketEngine::setSocketType(QAbstractSocket::SocketType socketType) +{ + d_func()->socketType = socketType; +} + +QAbstractSocket::NetworkLayerProtocol QAbstractSocketEngine::protocol() const +{ + return d_func()->socketProtocol; +} + +void QAbstractSocketEngine::setProtocol(QAbstractSocket::NetworkLayerProtocol protocol) +{ + d_func()->socketProtocol = protocol; +} + +QHostAddress QAbstractSocketEngine::localAddress() const +{ + return d_func()->localAddress; +} + +void QAbstractSocketEngine::setLocalAddress(const QHostAddress &address) +{ + d_func()->localAddress = address; +} + +quint16 QAbstractSocketEngine::localPort() const +{ + return d_func()->localPort; +} + +void QAbstractSocketEngine::setLocalPort(quint16 port) +{ + d_func()->localPort = port; +} + +QHostAddress QAbstractSocketEngine::peerAddress() const +{ + return d_func()->peerAddress; +} + +void QAbstractSocketEngine::setPeerAddress(const QHostAddress &address) +{ + d_func()->peerAddress = address; +} + +quint16 QAbstractSocketEngine::peerPort() const +{ + return d_func()->peerPort; +} + +void QAbstractSocketEngine::setPeerPort(quint16 port) +{ + d_func()->peerPort = port; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qabstractsocketengine_p.h b/src/network/socket/qabstractsocketengine_p.h new file mode 100644 index 0000000000..6bfa456d1f --- /dev/null +++ b/src/network/socket/qabstractsocketengine_p.h @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QABSTRACTSOCKETENGINE_P_H +#define QABSTRACTSOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtNetwork/qhostaddress.h" +#include "QtNetwork/qabstractsocket.h" +#include "private/qobject_p.h" + +QT_BEGIN_NAMESPACE + +class QAuthenticator; +class QAbstractSocketEnginePrivate; +class QNetworkProxy; + +class QAbstractSocketEngineReceiver { +public: + virtual ~QAbstractSocketEngineReceiver(){} + virtual void readNotification()= 0; + virtual void writeNotification()= 0; + virtual void exceptionNotification()= 0; + virtual void connectionNotification()= 0; +#ifndef QT_NO_NETWORKPROXY + virtual void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator)= 0; +#endif +}; + +class Q_AUTOTEST_EXPORT QAbstractSocketEngine : public QObject +{ + Q_OBJECT +public: + + static QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, const QNetworkProxy &, QObject *parent); + static QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); + + QAbstractSocketEngine(QObject *parent = 0); + + enum SocketOption { + NonBlockingSocketOption, + BroadcastSocketOption, + ReceiveBufferSocketOption, + SendBufferSocketOption, + AddressReusable, + BindExclusively, + ReceiveOutOfBandData + }; + + virtual bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol) = 0; + + virtual bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState) = 0; + + virtual int socketDescriptor() const = 0; + + virtual bool isValid() const = 0; + + virtual bool connectToHost(const QHostAddress &address, quint16 port) = 0; + virtual bool connectToHostByName(const QString &name, quint16 port) = 0; + virtual bool bind(const QHostAddress &address, quint16 port) = 0; + virtual bool listen() = 0; + virtual int accept() = 0; + virtual void close() = 0; + + virtual qint64 bytesAvailable() const = 0; + + virtual qint64 read(char *data, qint64 maxlen) = 0; + virtual qint64 write(const char *data, qint64 len) = 0; + +#ifndef QT_NO_UDPSOCKET + virtual qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0) = 0; + virtual qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port) = 0; + virtual bool hasPendingDatagrams() const = 0; + virtual qint64 pendingDatagramSize() const = 0; +#endif + + virtual int option(SocketOption option) const = 0; + virtual bool setOption(SocketOption option, int value) = 0; + + virtual bool waitForRead(int msecs = 30000, bool *timedOut = 0) = 0; + virtual bool waitForWrite(int msecs = 30000, bool *timedOut = 0) = 0; + virtual bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0) = 0; + + QAbstractSocket::SocketError error() const; + QString errorString() const; + QAbstractSocket::SocketState state() const; + QAbstractSocket::SocketType socketType() const; + QAbstractSocket::NetworkLayerProtocol protocol() const; + + QHostAddress localAddress() const; + quint16 localPort() const; + QHostAddress peerAddress() const; + quint16 peerPort() const; + + virtual bool isReadNotificationEnabled() const = 0; + virtual void setReadNotificationEnabled(bool enable) = 0; + virtual bool isWriteNotificationEnabled() const = 0; + virtual void setWriteNotificationEnabled(bool enable) = 0; + virtual bool isExceptionNotificationEnabled() const = 0; + virtual void setExceptionNotificationEnabled(bool enable) = 0; + +public Q_SLOTS: + void readNotification(); + void writeNotification(); + void exceptionNotification(); + void connectionNotification(); +#ifndef QT_NO_NETWORKPROXY + void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); +#endif + +public: + void setReceiver(QAbstractSocketEngineReceiver *receiver); +protected: + QAbstractSocketEngine(QAbstractSocketEnginePrivate &dd, QObject* parent = 0); + + void setError(QAbstractSocket::SocketError error, const QString &errorString) const; + void setState(QAbstractSocket::SocketState state); + void setSocketType(QAbstractSocket::SocketType socketType); + void setProtocol(QAbstractSocket::NetworkLayerProtocol protocol); + void setLocalAddress(const QHostAddress &address); + void setLocalPort(quint16 port); + void setPeerAddress(const QHostAddress &address); + void setPeerPort(quint16 port); + +private: + Q_DECLARE_PRIVATE(QAbstractSocketEngine) + Q_DISABLE_COPY(QAbstractSocketEngine) +}; + +class QAbstractSocketEnginePrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QAbstractSocketEngine) +public: + QAbstractSocketEnginePrivate(); + + mutable QAbstractSocket::SocketError socketError; + mutable bool hasSetSocketError; + mutable QString socketErrorString; + QAbstractSocket::SocketState socketState; + QAbstractSocket::SocketType socketType; + QAbstractSocket::NetworkLayerProtocol socketProtocol; + QHostAddress localAddress; + quint16 localPort; + QHostAddress peerAddress; + quint16 peerPort; + QAbstractSocketEngineReceiver *receiver; +}; + + +class Q_AUTOTEST_EXPORT QSocketEngineHandler +{ +protected: + QSocketEngineHandler(); + virtual ~QSocketEngineHandler(); + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent) = 0; + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent) = 0; + +private: + friend class QAbstractSocketEngine; +}; + +QT_END_NAMESPACE + +#endif // QABSTRACTSOCKETENGINE_P_H diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp new file mode 100644 index 0000000000..540c443e2f --- /dev/null +++ b/src/network/socket/qhttpsocketengine.cpp @@ -0,0 +1,773 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qhttpsocketengine_p.h" +#include "qtcpsocket.h" +#include "qhostaddress.h" +#include "qdatetime.h" +#include "qurl.h" +#include "qhttp.h" + +#if !defined(QT_NO_NETWORKPROXY) && !defined(QT_NO_HTTP) +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +#define DEBUG + +QHttpSocketEngine::QHttpSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QHttpSocketEnginePrivate, parent) +{ +} + +QHttpSocketEngine::~QHttpSocketEngine() +{ +} + +bool QHttpSocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QHttpSocketEngine); + if (type != QAbstractSocket::TcpSocket) + return false; + + setProtocol(protocol); + setSocketType(type); + d->socket = new QTcpSocket(this); + + // Explicitly disable proxying on the proxy socket itself to avoid + // unwanted recursion. + d->socket->setProxy(QNetworkProxy::NoProxy); + + // Intercept all the signals. + connect(d->socket, SIGNAL(connected()), + this, SLOT(slotSocketConnected()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(disconnected()), + this, SLOT(slotSocketDisconnected()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(readyRead()), + this, SLOT(slotSocketReadNotification()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(bytesWritten(qint64)), + this, SLOT(slotSocketBytesWritten()), + Qt::DirectConnection); + connect(d->socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(slotSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + connect(d->socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(slotSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + return true; +} + +bool QHttpSocketEngine::initialize(int, QAbstractSocket::SocketState) +{ + return false; +} + +void QHttpSocketEngine::setProxy(const QNetworkProxy &proxy) +{ + Q_D(QHttpSocketEngine); + d->proxy = proxy; + QString user = proxy.user(); + if (!user.isEmpty()) + d->authenticator.setUser(user); + QString password = proxy.password(); + if (!password.isEmpty()) + d->authenticator.setPassword(password); +} + +int QHttpSocketEngine::socketDescriptor() const +{ + Q_D(const QHttpSocketEngine); + return d->socket ? d->socket->socketDescriptor() : 0; +} + +bool QHttpSocketEngine::isValid() const +{ + Q_D(const QHttpSocketEngine); + return d->socket; +} + +bool QHttpSocketEngine::connectInternal() +{ + Q_D(QHttpSocketEngine); + + // If the handshake is done, enter ConnectedState state and return true. + if (d->state == Connected) { + qWarning("QHttpSocketEngine::connectToHost: called when already connected"); + setState(QAbstractSocket::ConnectedState); + return true; + } + + if (d->state == ConnectSent && d->socketState != QAbstractSocket::ConnectedState) + setState(QAbstractSocket::UnconnectedState); + + // Handshake isn't done. If unconnected, start connecting. + if (d->state == None && d->socket->state() == QAbstractSocket::UnconnectedState) { + setState(QAbstractSocket::ConnectingState); + d->socket->connectToHost(d->proxy.hostName(), d->proxy.port()); + } + + // If connected (might happen right away, at least for localhost services + // on some BSD systems), there might already be bytes available. + if (bytesAvailable()) + slotSocketReadNotification(); + + return d->socketState == QAbstractSocket::ConnectedState; +} + +bool QHttpSocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QHttpSocketEngine); + + setPeerAddress(address); + setPeerPort(port); + d->peerName.clear(); + + return connectInternal(); +} + +bool QHttpSocketEngine::connectToHostByName(const QString &hostname, quint16 port) +{ + Q_D(QHttpSocketEngine); + + setPeerAddress(QHostAddress()); + setPeerPort(port); + d->peerName = hostname; + + return connectInternal(); +} + +bool QHttpSocketEngine::bind(const QHostAddress &, quint16) +{ + return false; +} + +bool QHttpSocketEngine::listen() +{ + return false; +} + +int QHttpSocketEngine::accept() +{ + return 0; +} + +void QHttpSocketEngine::close() +{ + Q_D(QHttpSocketEngine); + if (d->socket) { + d->socket->close(); + delete d->socket; + d->socket = 0; + } +} + +qint64 QHttpSocketEngine::bytesAvailable() const +{ + Q_D(const QHttpSocketEngine); + return d->readBuffer.size() + (d->socket ? d->socket->bytesAvailable() : 0); +} + +qint64 QHttpSocketEngine::read(char *data, qint64 maxlen) +{ + Q_D(QHttpSocketEngine); + qint64 bytesRead = 0; + + if (!d->readBuffer.isEmpty()) { + // Read as much from the buffer as we can. + bytesRead = qMin((qint64)d->readBuffer.size(), maxlen); + memcpy(data, d->readBuffer.constData(), bytesRead); + data += bytesRead; + maxlen -= bytesRead; + d->readBuffer = d->readBuffer.mid(bytesRead); + } + + qint64 bytesReadFromSocket = d->socket->read(data, maxlen); + + if (d->socket->state() == QAbstractSocket::UnconnectedState + && d->socket->bytesAvailable() == 0) { + emitReadNotification(); + } + + if (bytesReadFromSocket > 0) { + // Add to what we read so far. + bytesRead += bytesReadFromSocket; + } else if (bytesRead == 0 && bytesReadFromSocket == -1) { + // If nothing has been read so far, and the direct socket read + // failed, return the socket's error. Otherwise, fall through and + // return as much as we read so far. + close(); + setError(QAbstractSocket::RemoteHostClosedError, + QLatin1String("Remote host closed")); + setState(QAbstractSocket::UnconnectedState); + return -1; + } + return bytesRead; +} + +qint64 QHttpSocketEngine::write(const char *data, qint64 len) +{ + Q_D(QHttpSocketEngine); + return d->socket->write(data, len); +} + +#ifndef QT_NO_UDPSOCKET +qint64 QHttpSocketEngine::readDatagram(char *, qint64, QHostAddress *, + quint16 *) +{ + return 0; +} + +qint64 QHttpSocketEngine::writeDatagram(const char *, qint64, const QHostAddress &, + quint16) +{ + return 0; +} + +bool QHttpSocketEngine::hasPendingDatagrams() const +{ + return false; +} + +qint64 QHttpSocketEngine::pendingDatagramSize() const +{ + return 0; +} +#endif // QT_NO_UDPSOCKET + +int QHttpSocketEngine::option(SocketOption) const +{ + return -1; +} + +bool QHttpSocketEngine::setOption(SocketOption, int) +{ + return false; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +bool QHttpSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QHttpSocketEngine); + + if (!d->socket || d->socket->state() == QAbstractSocket::UnconnectedState) + return false; + + QTime stopWatch; + stopWatch.start(); + + // Wait for more data if nothing is available. + if (!d->socket->bytesAvailable()) { + if (!d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (d->socket->state() == QAbstractSocket::UnconnectedState) + return true; + setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } + + // If we're not connected yet, wait until we are, or until an error + // occurs. + while (d->state != Connected && d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + // Loop while the protocol handshake is taking place. + } + + // Report any error that may occur. + if (d->state != Connected) { + setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + return true; +} + +bool QHttpSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(const QHttpSocketEngine); + + // If we're connected, just forward the call. + if (d->state == Connected) { + if (d->socket->bytesToWrite()) { + if (!d->socket->waitForBytesWritten(msecs)) { + if (d->socket->error() == QAbstractSocket::SocketTimeoutError && timedOut) + *timedOut = true; + return false; + } + } + return true; + } + + QTime stopWatch; + stopWatch.start(); + + // If we're not connected yet, wait until we are, and until bytes have + // been received (i.e., the socket has connected, we have sent the + // greeting, and then received the response). + while (d->state != Connected && d->socket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + // Loop while the protocol handshake is taking place. + } + + // Report any error that may occur. + if (d->state != Connected) { +// setError(d->socket->error(), d->socket->errorString()); + if (timedOut && d->socket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + } + + return true; +} + +bool QHttpSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_UNUSED(checkRead); + + if (!checkWrite) { + // Not interested in writing? Then we wait for read notifications. + bool canRead = waitForRead(msecs, timedOut); + if (readyToRead) + *readyToRead = canRead; + return canRead; + } + + // Interested in writing? Then we wait for write notifications. + bool canWrite = waitForWrite(msecs, timedOut); + if (readyToWrite) + *readyToWrite = canWrite; + return canWrite; +} + +bool QHttpSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->readNotificationEnabled; +} + +void QHttpSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + if (d->readNotificationEnabled == enable) + return; + + d->readNotificationEnabled = enable; + if (enable) { + // Enabling read notification can trigger a notification. + if (bytesAvailable()) + slotSocketReadNotification(); + } +} + +bool QHttpSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->writeNotificationEnabled; +} + +void QHttpSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + d->writeNotificationEnabled = enable; + if (enable && d->state == Connected && d->socket->state() == QAbstractSocket::ConnectedState) + QMetaObject::invokeMethod(this, "writeNotification", Qt::QueuedConnection); +} + +bool QHttpSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QHttpSocketEngine); + return d->exceptNotificationEnabled; +} + +void QHttpSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QHttpSocketEngine); + d->exceptNotificationEnabled = enable; +} + +void QHttpSocketEngine::slotSocketConnected() +{ + Q_D(QHttpSocketEngine); + + // Send the greeting. + const char method[] = "CONNECT "; + QByteArray peerAddress = d->peerName.isEmpty() ? + d->peerAddress.toString().toLatin1() : + QUrl::toAce(d->peerName); + QByteArray path = peerAddress + ':' + QByteArray::number(d->peerPort); + QByteArray data = method; + data += path; + data += " HTTP/1.1\r\n"; + data += "Proxy-Connection: keep-alive\r\n" + "Host: " + peerAddress + "\r\n"; + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator); + //qDebug() << "slotSocketConnected: priv=" << priv << (priv ? (int)priv->method : -1); + if (priv && priv->method != QAuthenticatorPrivate::None) { + data += "Proxy-Authorization: " + priv->calculateResponse(method, path); + data += "\r\n"; + } + data += "\r\n"; +// qDebug() << ">>>>>>>> sending request" << this; +// qDebug() << data; +// qDebug() << ">>>>>>>"; + d->socket->write(data); + d->state = ConnectSent; +} + +void QHttpSocketEngine::slotSocketDisconnected() +{ +} + +void QHttpSocketEngine::slotSocketReadNotification() +{ + Q_D(QHttpSocketEngine); + if (d->state != Connected && d->socket->bytesAvailable() == 0) + return; + + if (d->state == Connected) { + // Forward as a read notification. + if (d->readNotificationEnabled) + emitReadNotification(); + return; + } + + readResponseContent: + if (d->state == ReadResponseContent) { + char dummybuffer[4096]; + while (d->pendingResponseData) { + int read = d->socket->read(dummybuffer, qMin(sizeof(dummybuffer), (size_t)d->pendingResponseData)); + if (read >= 0) + dummybuffer[read] = 0; + + if (read == 0) + return; + if (read == -1) { + d->socket->disconnectFromHost(); + emitWriteNotification(); + return; + } + d->pendingResponseData -= read; + } + if (d->pendingResponseData > 0) + return; + d->state = SendAuthentication; + slotSocketConnected(); + return; + } + + // Still in handshake mode. Wait until we've got a full response. + bool done = false; + do { + d->readBuffer += d->socket->readLine(); + } while (!(done = d->readBuffer.endsWith("\r\n\r\n")) && d->socket->canReadLine()); + + if (!done) { + // Wait for more. + return; + } + + if (!d->readBuffer.startsWith("HTTP/1.")) { + // protocol error, this isn't HTTP + d->readBuffer.clear(); + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + setError(QAbstractSocket::ProxyProtocolError, tr("Did not receive HTTP response from proxy")); + emitConnectionNotification(); + return; + } + + QHttpResponseHeader responseHeader(QString::fromLatin1(d->readBuffer)); + d->readBuffer.clear(); + + int statusCode = responseHeader.statusCode(); + if (statusCode == 200) { + d->state = Connected; + setLocalAddress(d->socket->localAddress()); + setLocalPort(d->socket->localPort()); + setState(QAbstractSocket::ConnectedState); + } else if (statusCode == 407) { + if (d->authenticator.isNull()) + d->authenticator.detach(); + QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator); + + priv->parseHttpResponse(responseHeader, true); + + if (priv->phase == QAuthenticatorPrivate::Invalid) { + // problem parsing the reply + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + setError(QAbstractSocket::ProxyProtocolError, tr("Error parsing authentication request from proxy")); + emitConnectionNotification(); + return; + } + + bool willClose; + QString proxyConnectionHeader = responseHeader.value(QLatin1String("Proxy-Connection")); + proxyConnectionHeader = proxyConnectionHeader.toLower(); + if (proxyConnectionHeader == QLatin1String("close")) { + willClose = true; + } else if (proxyConnectionHeader == QLatin1String("keep-alive")) { + willClose = false; + } else { + // no Proxy-Connection header, so use the default + // HTTP 1.1's default behaviour is to keep persistent connections + // HTTP 1.0 or earlier, so we expect the server to close + willClose = (responseHeader.majorVersion() * 0x100 + responseHeader.minorVersion()) <= 0x0100; + } + + if (willClose) { + // the server will disconnect, so let's avoid receiving an error + // especially since the signal below may trigger a new event loop + d->socket->disconnectFromHost(); + d->socket->readAll(); + } + + if (priv->phase == QAuthenticatorPrivate::Done) + emit proxyAuthenticationRequired(d->proxy, &d->authenticator); + + // priv->phase will get reset to QAuthenticatorPrivate::Start if the authenticator got modified in the signal above. + if (priv->phase == QAuthenticatorPrivate::Done) { + setError(QAbstractSocket::ProxyAuthenticationRequiredError, tr("Authentication required")); + d->socket->disconnectFromHost(); + } else { + // close the connection if it isn't already and reconnect using the chosen authentication method + d->state = SendAuthentication; + if (willClose) { + d->socket->connectToHost(d->proxy.hostName(), d->proxy.port()); + } else { + bool ok; + int contentLength = responseHeader.value(QLatin1String("Content-Length")).toInt(&ok); + if (ok && contentLength > 0) { + d->state = ReadResponseContent; + d->pendingResponseData = contentLength; + goto readResponseContent; + } else { + d->state = SendAuthentication; + slotSocketConnected(); + } + } + return; + } + } else { + d->socket->close(); + setState(QAbstractSocket::UnconnectedState); + if (statusCode == 403 || statusCode == 405) { + // 403 Forbidden + // 405 Method Not Allowed + setError(QAbstractSocket::SocketAccessError, tr("Proxy denied connection")); + } else if (statusCode == 404) { + // 404 Not Found: host lookup error + setError(QAbstractSocket::HostNotFoundError, QAbstractSocket::tr("Host not found")); + } else if (statusCode == 503) { + // 503 Service Unavailable: Connection Refused + setError(QAbstractSocket::ConnectionRefusedError, QAbstractSocket::tr("Connection refused")); + } else { + // Some other reply + //qWarning("UNEXPECTED RESPONSE: [%s]", responseHeader.toString().toLatin1().data()); + setError(QAbstractSocket::ProxyProtocolError, tr("Error communicating with HTTP proxy")); + } + } + + // The handshake is done; notify that we're connected (or failed to connect) + emitConnectionNotification(); +} + +void QHttpSocketEngine::slotSocketBytesWritten() +{ + Q_D(QHttpSocketEngine); + if (d->state == Connected && d->writeNotificationEnabled) + emitWriteNotification(); +} + +void QHttpSocketEngine::slotSocketError(QAbstractSocket::SocketError error) +{ + Q_D(QHttpSocketEngine); + d->readBuffer.clear(); + + if (d->state != Connected) { + // we are in proxy handshaking stages + if (error == QAbstractSocket::HostNotFoundError) + setError(QAbstractSocket::ProxyNotFoundError, tr("Proxy server not found")); + else if (error == QAbstractSocket::ConnectionRefusedError) + setError(QAbstractSocket::ProxyConnectionRefusedError, tr("Proxy connection refused")); + else if (error == QAbstractSocket::SocketTimeoutError) + setError(QAbstractSocket::ProxyConnectionTimeoutError, tr("Proxy server connection timed out")); + else if (error == QAbstractSocket::RemoteHostClosedError) + setError(QAbstractSocket::ProxyConnectionClosedError, tr("Proxy connection closed prematurely")); + else + setError(error, d->socket->errorString()); + emitConnectionNotification(); + return; + } + + // We're connected + if (error == QAbstractSocket::SocketTimeoutError) + return; // ignore this error + + d->state = None; + setError(error, d->socket->errorString()); + if (error == QAbstractSocket::RemoteHostClosedError) { + emitReadNotification(); + } else { + qDebug() << "QHttpSocketEngine::slotSocketError: got weird error =" << error; + } +} + +void QHttpSocketEngine::slotSocketStateChanged(QAbstractSocket::SocketState state) +{ + Q_UNUSED(state); +} + +void QHttpSocketEngine::emitPendingReadNotification() +{ + Q_D(QHttpSocketEngine); + d->readNotificationPending = false; + if (d->readNotificationEnabled) + emit readNotification(); +} + +void QHttpSocketEngine::emitPendingWriteNotification() +{ + Q_D(QHttpSocketEngine); + d->writeNotificationPending = false; + if (d->writeNotificationEnabled) + emit writeNotification(); +} + +void QHttpSocketEngine::emitPendingConnectionNotification() +{ + Q_D(QHttpSocketEngine); + d->connectionNotificationPending = false; + emit connectionNotification(); +} + +void QHttpSocketEngine::emitReadNotification() +{ + Q_D(QHttpSocketEngine); + d->readNotificationActivated = true; + if (d->readNotificationEnabled && !d->readNotificationPending) { + d->readNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingReadNotification", Qt::QueuedConnection); + } +} + +void QHttpSocketEngine::emitWriteNotification() +{ + Q_D(QHttpSocketEngine); + d->writeNotificationActivated = true; + if (d->writeNotificationEnabled && !d->writeNotificationPending) { + d->writeNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingWriteNotification", Qt::QueuedConnection); + } +} + +void QHttpSocketEngine::emitConnectionNotification() +{ + Q_D(QHttpSocketEngine); + if (!d->connectionNotificationPending) { + d->connectionNotificationPending = true; + QMetaObject::invokeMethod(this, "emitPendingConnectionNotification", Qt::QueuedConnection); + } +} + +QHttpSocketEnginePrivate::QHttpSocketEnginePrivate() + : readNotificationEnabled(false) + , writeNotificationEnabled(false) + , exceptNotificationEnabled(false) + , readNotificationActivated(false) + , writeNotificationActivated(false) + , readNotificationPending(false) + , writeNotificationPending(false) + , connectionNotificationPending(false) +{ + socket = 0; + state = QHttpSocketEngine::None; +} + +QHttpSocketEnginePrivate::~QHttpSocketEnginePrivate() +{ +} + +QAbstractSocketEngine *QHttpSocketEngineHandler::createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &proxy, + QObject *parent) +{ + if (socketType != QAbstractSocket::TcpSocket) + return 0; + + // proxy type must have been resolved by now + if (proxy.type() != QNetworkProxy::HttpProxy) + return 0; + + // we only accept active sockets + if (!qobject_cast<QAbstractSocket *>(parent)) + return 0; + + QHttpSocketEngine *engine = new QHttpSocketEngine(parent); + engine->setProxy(proxy); + return engine; +} + +QAbstractSocketEngine *QHttpSocketEngineHandler::createSocketEngine(int, QObject *) +{ + return 0; +} + +#endif + +QT_END_NAMESPACE diff --git a/src/network/socket/qhttpsocketengine_p.h b/src/network/socket/qhttpsocketengine_p.h new file mode 100644 index 0000000000..d0c53d000a --- /dev/null +++ b/src/network/socket/qhttpsocketengine_p.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHTTPSOCKETENGINE_P_H +#define QHTTPSOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "private/qabstractsocketengine_p.h" +#include "qabstractsocket.h" +#include "qnetworkproxy.h" +#include "private/qauthenticator_p.h" + +QT_BEGIN_NAMESPACE + +#if !defined(QT_NO_NETWORKPROXY) && !defined(QT_NO_HTTP) + +class QTcpSocket; +class QHttpSocketEnginePrivate; + +class Q_AUTOTEST_EXPORT QHttpSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + enum HttpState { + None, + ConnectSent, + Connected, + SendAuthentication, + ReadResponseContent + }; + QHttpSocketEngine(QObject *parent = 0); + ~QHttpSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + void setProxy(const QNetworkProxy &networkProxy); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectInternal(); + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + +#ifndef QT_NO_UDPSOCKET + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; +#endif // QT_NO_UDPSOCKET + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +public slots: + void slotSocketConnected(); + void slotSocketDisconnected(); + void slotSocketReadNotification(); + void slotSocketBytesWritten(); + void slotSocketError(QAbstractSocket::SocketError error); + void slotSocketStateChanged(QAbstractSocket::SocketState state); + +private slots: + void emitPendingReadNotification(); + void emitPendingWriteNotification(); + void emitPendingConnectionNotification(); + +private: + void emitReadNotification(); + void emitWriteNotification(); + void emitConnectionNotification(); + + Q_DECLARE_PRIVATE(QHttpSocketEngine) + Q_DISABLE_COPY(QHttpSocketEngine) + +}; + + +class QHttpSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QHttpSocketEngine) +public: + QHttpSocketEnginePrivate(); + ~QHttpSocketEnginePrivate(); + + QNetworkProxy proxy; + QString peerName; + QTcpSocket *socket; + QByteArray readBuffer; + QHttpSocketEngine::HttpState state; + QAuthenticator authenticator; + bool readNotificationEnabled; + bool writeNotificationEnabled; + bool exceptNotificationEnabled; + bool readNotificationActivated; + bool writeNotificationActivated; + bool readNotificationPending; + bool writeNotificationPending; + bool connectionNotificationPending; + uint pendingResponseData; +}; + +class Q_AUTOTEST_EXPORT QHttpSocketEngineHandler : public QSocketEngineHandler +{ +public: + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent); + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); +}; +#endif + +QT_END_NAMESPACE + +#endif // QHTTPSOCKETENGINE_H diff --git a/src/network/socket/qlocalserver.cpp b/src/network/socket/qlocalserver.cpp new file mode 100644 index 0000000000..d6b1507d3d --- /dev/null +++ b/src/network/socket/qlocalserver.cpp @@ -0,0 +1,395 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_LOCALSERVER + +/*! + \class QLocalServer + \since 4.4 + + \brief The QLocalServer class provides a local socket based server. + + This class makes it possible to accept incoming local socket + connections. + + Call listen() to have the server start listening + for incoming connections on a specified key. The + newConnection() signal is then emitted each time a client + connects to the server. + + Call nextPendingConnection() to accept the pending connection + as a connected QLocalSocket. The function returns a pointer to a + QLocalSocket that can be used for communicating with the client. + + If an error occurs, serverError() returns the type of error, and + errorString() can be called to get a human readable description + of what happened. + + When listening for connections, the name which the server is + listening on is available through serverName(). + + Calling close() makes QLocalServer stop listening for incoming connections. + + Although QLocalServer is designed for use with an event loop, it's possible + to use it without one. In that case, you must use waitForNewConnection(), + which blocks until either a connection is available or a timeout expires. + + Note that this feature is not supported on Windows 9x. + + \sa QLocalSocket, QTcpServer +*/ + +/*! + Create a new local socket server with the given \a parent. + + \sa listen() + */ +QLocalServer::QLocalServer(QObject *parent) + : QObject(*new QLocalServerPrivate, parent) +{ + Q_D(QLocalServer); + d->init(); +} + +/*! + Destroys the QLocalServer object. If the server is listening for + connections, it is automatically closed. + + Any client QLocalSockets that are still connected must either + disconnect or be reparented before the server is deleted. + + \sa close() + */ +QLocalServer::~QLocalServer() +{ + if (isListening()) + close(); +} + +/*! + Stop listening for incoming connections. Existing connections are not + effected, but any new connections will be refused. + + \sa isListening(), listen() + */ +void QLocalServer::close() +{ + Q_D(QLocalServer); + if (!isListening()) + return; + qDeleteAll(d->pendingConnections); + d->pendingConnections.clear(); + d->closeServer(); + d->serverName = QString(); + d->fullServerName = QString(); + d->errorString = QString(); + d->error = QAbstractSocket::UnknownSocketError; +} + +/*! + Returns the human-readable message appropriate to the current error + reported by serverError(). If no suitable string is available, an empty + string is returned. + + \sa serverError() + */ +QString QLocalServer::errorString() const +{ + Q_D(const QLocalServer); + return d->errorString; +} + +/*! + Returns true if the server has a pending connection; otherwise + returns false. + + \sa nextPendingConnection(), setMaxPendingConnections() + */ +bool QLocalServer::hasPendingConnections() const +{ + Q_D(const QLocalServer); + return !(d->pendingConnections.isEmpty()); +} + +/*! + This virtual function is called by QLocalServer when a new connection + is available. \a socketDescriptor is the native socket descriptor for + the accepted connection. + + The base implementation creates a QLocalSocket, sets the socket descriptor + and then stores the QLocalSocket in an internal list of pending + connections. Finally newConnection() is emitted. + + Reimplement this function to alter the server's behavior + when a connection is available. + + \sa newConnection(), nextPendingConnection(), + QLocalSocket::setSocketDescriptor() + */ +void QLocalServer::incomingConnection(quintptr socketDescriptor) +{ + Q_D(QLocalServer); + QLocalSocket *socket = new QLocalSocket(this); + socket->setSocketDescriptor(socketDescriptor); + d->pendingConnections.enqueue(socket); + emit newConnection(); +} + +/*! + Returns true if the server is listening for incoming connections + otherwise false. + + \sa listen(), close() + */ +bool QLocalServer::isListening() const +{ + Q_D(const QLocalServer); + return !(d->serverName.isEmpty()); +} + +/*! + Tells the server to listen for incoming connections on \a name. + If the server is currently listening then it will return false. + Return true on success otherwise false. + + \a name can be a single name and QLocalServer will determine + the correct platform specific path. serverName() will return + the name that is passed into listen. + + Usually you would just pass in a name like "foo", but on Unix this + could also be a path such as "/tmp/foo" and on Windows this could + be a pipe path such as "\\\\.\\pipe\\foo" + + Note: + On Unix if the server crashes without closing listen will fail + with AddressInUseError. To create a new server the file should be removed. + On Windows two local servers can listen to the same pipe at the same + time, but any connections will go to one of the server. + + \sa serverName(), isListening(), close() + */ +bool QLocalServer::listen(const QString &name) +{ + Q_D(QLocalServer); + if (isListening()) { + qWarning("QLocalServer::listen() called when already listening"); + return false; + } + + if (name.isEmpty()) { + d->error = QAbstractSocket::HostNotFoundError; + QString function = QLatin1String("QLocalServer::listen"); + d->errorString = tr("%1: Name error").arg(function); + return false; + } + + if (!d->listen(name)) { + d->serverName = QString(); + d->fullServerName = QString(); + return false; + } + + d->serverName = name; + return true; +} + +/*! + Returns the maximum number of pending accepted connections. + The default is 30. + + \sa setMaxPendingConnections(), hasPendingConnections() + */ +int QLocalServer::maxPendingConnections() const +{ + Q_D(const QLocalServer); + return d->maxPendingConnections; +} + +/*! + \fn void QLocalServer::newConnection() + + This signal is emitted every time a new connection is available. + + \sa hasPendingConnections(), nextPendingConnection() +*/ + +/*! + Returns the next pending connection as a connected QLocalSocket object. + + The socket is created as a child of the server, which means that it is + automatically deleted when the QLocalServer object is destroyed. It is + still a good idea to delete the object explicitly when you are done with + it, to avoid wasting memory. + + 0 is returned if this function is called when there are no pending + connections. + + \sa hasPendingConnections(), newConnection(), incomingConnection() + */ +QLocalSocket *QLocalServer::nextPendingConnection() +{ + Q_D(QLocalServer); + if (d->pendingConnections.isEmpty()) + return 0; + QLocalSocket *nextSocket = d->pendingConnections.dequeue(); +#ifndef Q_OS_WIN + d->socketNotifier->setEnabled(d->pendingConnections.size() + <= d->maxPendingConnections); +#endif + return nextSocket; +} + +/*! + \since 4.5 + + Removes any server instance that might cause a call to listen() to fail + and returns true if successful; otherwise returns false. + This function is meant to recover from a crash, when the previous server + instance has not been cleaned up. + + On Windows, this function does nothing; on Unix, it removes the socket file + given by \a name. + + \warning Be careful to avoid removing sockets of running instances. +*/ +bool QLocalServer::removeServer(const QString &name) +{ + return QLocalServerPrivate::removeServer(name); +} + +/*! + Returns the server name if the server is listening for connections; + otherwise returns QString() + + \sa listen(), fullServerName() + */ +QString QLocalServer::serverName() const +{ + Q_D(const QLocalServer); + return d->serverName; +} + +/*! + Returns the full path that the server is listening on. + + Note: This is platform specific + + \sa listen(), serverName() + */ +QString QLocalServer::fullServerName() const +{ + Q_D(const QLocalServer); + return d->fullServerName; +} + +/*! + Returns the type of error that occurred last or NoError. + + \sa errorString() + */ +QAbstractSocket::SocketError QLocalServer::serverError() const +{ + Q_D(const QLocalServer); + return d->error; +} + +/*! + Sets the maximum number of pending accepted connections to + \a numConnections. QLocalServer will accept no more than + \a numConnections incoming connections before nextPendingConnection() + is called. + + Note: Even though QLocalServer will stop accepting new connections + after it has reached its maximum number of pending connections, + the operating system may still keep them in queue which will result + in clients signaling that it is connected. + + \sa maxPendingConnections(), hasPendingConnections() + */ +void QLocalServer::setMaxPendingConnections(int numConnections) +{ + Q_D(QLocalServer); + d->maxPendingConnections = numConnections; +} + +/*! + Waits for at most \a msec milliseconds or until an incoming connection + is available. Returns true if a connection is available; otherwise + returns false. If the operation timed out and \a timedOut is not 0, + *timedOut will be set to true. + + This is a blocking function call. Its use is ill-advised in a + single-threaded GUI application, since the whole application will stop + responding until the function returns. waitForNewConnection() is mostly + useful when there is no event loop available. + + The non-blocking alternative is to connect to the newConnection() signal. + + If msec is -1, this function will not time out. + + \sa hasPendingConnections(), nextPendingConnection() + */ +bool QLocalServer::waitForNewConnection(int msec, bool *timedOut) +{ + Q_D(QLocalServer); + if (timedOut) + *timedOut = false; + + if (!isListening()) + return false; + + d->waitForNewConnection(msec, timedOut); + + return !d->pendingConnections.isEmpty(); +} + +#endif + +QT_END_NAMESPACE + +#include "moc_qlocalserver.cpp" + diff --git a/src/network/socket/qlocalserver.h b/src/network/socket/qlocalserver.h new file mode 100644 index 0000000000..8e8babdab8 --- /dev/null +++ b/src/network/socket/qlocalserver.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALSERVER_H +#define QLOCALSERVER_H + +#include <QtNetwork/qabstractsocket.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_LOCALSERVER + +class QLocalSocket; +class QLocalServerPrivate; + +class Q_NETWORK_EXPORT QLocalServer : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QLocalServer) + +Q_SIGNALS: + void newConnection(); + +public: + QLocalServer(QObject *parent = 0); + ~QLocalServer(); + + void close(); + QString errorString() const; + virtual bool hasPendingConnections() const; + bool isListening() const; + bool listen(const QString &name); + int maxPendingConnections() const; + virtual QLocalSocket *nextPendingConnection(); + QString serverName() const; + QString fullServerName() const; + static bool removeServer(const QString &name); + QAbstractSocket::SocketError serverError() const; + void setMaxPendingConnections(int numConnections); + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + +protected: + virtual void incomingConnection(quintptr socketDescriptor); + +private: + Q_DISABLE_COPY(QLocalServer) +#if defined(QT_LOCALSOCKET_TCP) + Q_PRIVATE_SLOT(d_func(), void _q_onNewConnection()) +#elif defined(Q_OS_WIN) + Q_PRIVATE_SLOT(d_func(), void _q_openSocket(HANDLE handle)) + Q_PRIVATE_SLOT(d_func(), void _q_stoppedListening()) + Q_PRIVATE_SLOT(d_func(), void _q_setError(QAbstractSocket::SocketError error, const QString &errorString)) +#else + Q_PRIVATE_SLOT(d_func(), void _q_socketActivated()) +#endif +}; + +#endif // QT_NO_LOCALSERVER + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLOCALSERVER_H + diff --git a/src/network/socket/qlocalserver_p.h b/src/network/socket/qlocalserver_p.h new file mode 100644 index 0000000000..8e96401347 --- /dev/null +++ b/src/network/socket/qlocalserver_p.h @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALSERVER_P_H +#define QLOCALSERVER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLocalServer class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_LOCALSERVER + +#include "qlocalserver.h" +#include "private/qobject_p.h" +#include <qqueue.h> + +#if defined(QT_LOCALSOCKET_TCP) +# include <qtcpserver.h> +#elif defined(Q_OS_WIN) +# include <qt_windows.h> +# include <qthread.h> +#else +# include <private/qnativesocketengine_p.h> +# include <qsocketnotifier.h> +#endif + +QT_BEGIN_NAMESPACE + +#if defined(Q_OS_WIN) && !defined(QT_LOCALSOCKET_TCP) + +/*! + \internal + QLocalServerThread exists because Windows does not have a + way to provide notifications when there is a new connections to + the server. + */ +class QLocalServerThread : public QThread +{ + Q_OBJECT + +Q_SIGNALS: + void connected(HANDLE newSocket); + void error(QAbstractSocket::SocketError error, const QString &errorString); + +public: + QLocalServerThread(QObject *parent = 0); + ~QLocalServerThread(); + void closeServer(); + +public: + QString setName(const QString &name); + void run(); + void stop(); + bool makeHandle(); + + HANDLE gotConnectionEvent; + QQueue<HANDLE> pendingHandles; + int maxPendingConnections; +private: + HANDLE stopEvent; + QString fullServerName; +}; + +#endif + +class QLocalServerPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QLocalServer) + +public: + QLocalServerPrivate() : +#if defined(Q_OS_WIN) && !defined(QT_LOCALSOCKET_TCP) + inWaitingFunction(false), +#elif !defined(QT_LOCALSOCKET_TCP) + listenSocket(-1), socketNotifier(0), +#endif + maxPendingConnections(30), error(QAbstractSocket::UnknownSocketError) + { + } + + void init(); + bool listen(const QString &name); + static bool removeServer(const QString &name); + void closeServer(); + void waitForNewConnection(int msec, bool *timedOut); + +#if defined(QT_LOCALSOCKET_TCP) + void _q_onNewConnection(); + + QTcpServer tcpServer; + QMap<quintptr, QTcpSocket*> socketMap; +#elif defined(Q_OS_WIN) + void _q_openSocket(HANDLE socket); + void _q_stoppedListening(); + void _q_setError(QAbstractSocket::SocketError error, const QString &errorString); + + QLocalServerThread waitForConnection; + bool inWaitingFunction; +#else + void setError(const QString &function); + void _q_socketActivated(); + + int listenSocket; + QSocketNotifier *socketNotifier; +#endif + + QString serverName; + QString fullServerName; + int maxPendingConnections; + QQueue<QLocalSocket*> pendingConnections; + QString errorString; + QAbstractSocket::SocketError error; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSERVER + +#endif // QLOCALSERVER_P_H + diff --git a/src/network/socket/qlocalserver_tcp.cpp b/src/network/socket/qlocalserver_tcp.cpp new file mode 100644 index 0000000000..f85777b830 --- /dev/null +++ b/src/network/socket/qlocalserver_tcp.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#include <qhostaddress.h> +#include <qsettings.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +void QLocalServerPrivate::init() +{ + Q_Q(QLocalServer); + q->connect(&tcpServer, SIGNAL(newConnection()), SLOT(_q_onNewConnection())); +} + +bool QLocalServerPrivate::listen(const QString &requestedServerName) +{ + if (!tcpServer.listen(QHostAddress::LocalHost)) + return false; + + const QLatin1String prefix("QLocalServer/"); + if (requestedServerName.startsWith(prefix)) + fullServerName = requestedServerName; + else + fullServerName = prefix + requestedServerName; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (settings.contains(fullServerName)) { + qWarning("QLocalServer::listen: server name is already in use."); + tcpServer.close(); + return false; + } + + settings.setValue(fullServerName, tcpServer.serverPort()); + return true; +} + +void QLocalServerPrivate::closeServer() +{ + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (fullServerName == QLatin1String("QLocalServer")) + settings.setValue(fullServerName, QVariant()); + else + settings.remove(fullServerName); + tcpServer.close(); +} + +void QLocalServerPrivate::waitForNewConnection(int msec, bool *timedOut) +{ + if (pendingConnections.isEmpty()) + tcpServer.waitForNewConnection(msec, timedOut); + else + *timedOut = false; +} + +void QLocalServerPrivate::_q_onNewConnection() +{ + Q_Q(QLocalServer); + QTcpSocket* tcpSocket = tcpServer.nextPendingConnection(); + if (!tcpSocket) { + qWarning("QLocalServer: no pending connection"); + return; + } + + tcpSocket->setParent(q); + const quintptr socketDescriptor = tcpSocket->socketDescriptor(); + q->incomingConnection(socketDescriptor); +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + const QLatin1String prefix("QLocalServer/"); + QString serverName; + if (name.startsWith(prefix)) + serverName = name; + else + serverName = prefix + name; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + if (settings.contains(serverName)) + settings.remove(serverName); + + return true; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalserver_unix.cpp b/src/network/socket/qlocalserver_unix.cpp new file mode 100644 index 0000000000..065a9de5b0 --- /dev/null +++ b/src/network/socket/qlocalserver_unix.cpp @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#ifndef QT_NO_LOCALSERVER + +#include <sys/socket.h> +#include <sys/un.h> + +#include <qdebug.h> +#include <qdir.h> +#include <qdatetime.h> + +QT_BEGIN_NAMESPACE + +void QLocalServerPrivate::init() +{ +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + QString fileName; + if (name.startsWith(QLatin1Char('/'))) { + fileName = name; + } else { + fileName = QDir::cleanPath(QDir::tempPath()); + fileName += QLatin1Char('/') + name; + } + if (QFile::exists(fileName)) + return QFile::remove(fileName); + else + return true; +} + +bool QLocalServerPrivate::listen(const QString &requestedServerName) +{ + Q_Q(QLocalServer); + + // determine the full server path + if (requestedServerName.startsWith(QLatin1Char('/'))) { + fullServerName = requestedServerName; + } else { + fullServerName = QDir::cleanPath(QDir::tempPath()); + fullServerName += QLatin1Char('/') + requestedServerName; + } + serverName = requestedServerName; + + // create the unix socket + listenSocket = qSocket(PF_UNIX, SOCK_STREAM, 0); + if (-1 == listenSocket) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + return false; + } + + // Construct the unix address + struct ::sockaddr_un addr; + addr.sun_family = PF_UNIX; + if (sizeof(addr.sun_path) < (uint)fullServerName.toLatin1().size() + 1) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + return false; + } + ::memcpy(addr.sun_path, fullServerName.toLatin1().data(), + fullServerName.toLatin1().size() + 1); + + // bind + if(-1 == qBind(listenSocket, (sockaddr *)&addr, sizeof(sockaddr_un))) { + setError(QLatin1String("QLocalServer::listen")); + // if address is in use already, just close the socket, but do not delete the file + if(errno == EADDRINUSE) + QT_CLOSE(listenSocket); + // otherwise, close the socket and delete the file + else + closeServer(); + listenSocket = -1; + return false; + } + + // listen for connections + if (-1 == qListen(listenSocket, 50)) { + setError(QLatin1String("QLocalServer::listen")); + closeServer(); + listenSocket = -1; + if (error != QAbstractSocket::AddressInUseError) + QFile::remove(fullServerName); + return false; + } + Q_ASSERT(!socketNotifier); + socketNotifier = new QSocketNotifier(listenSocket, + QSocketNotifier::Read, q); + q->connect(socketNotifier, SIGNAL(activated(int)), + q, SLOT(_q_socketActivated())); + socketNotifier->setEnabled(maxPendingConnections > 0); + return true; +} + +/*! + \internal + + \sa QLocalServer::closeServer() + */ +void QLocalServerPrivate::closeServer() +{ + if (-1 != listenSocket) + QT_CLOSE(listenSocket); + listenSocket = -1; + + if (socketNotifier) + socketNotifier->deleteLater(); + socketNotifier = 0; + + if (!fullServerName.isEmpty()) + QFile::remove(fullServerName); +} + +/*! + \internal + + We have received a notification that we can read on the listen socket. + Accept the new socket. + */ +void QLocalServerPrivate::_q_socketActivated() +{ + Q_Q(QLocalServer); + if (-1 == listenSocket) + return; + + ::sockaddr_un addr; + QT_SOCKLEN_T length = sizeof(sockaddr_un); + int connectedSocket = qAccept(listenSocket, (sockaddr *)&addr, &length); + if(-1 == connectedSocket) { + setError(QLatin1String("QLocalSocket::activated")); + closeServer(); + } else { + socketNotifier->setEnabled(pendingConnections.size() + <= maxPendingConnections); + q->incomingConnection(connectedSocket); + } +} + +void QLocalServerPrivate::waitForNewConnection(int msec, bool *timedOut) +{ + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(listenSocket, &readfds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + // timeout can not be 0 or else select will return an error. + if (0 == msec) + timeout.tv_usec = 1000; + + int result = -1; + // on Linux timeout will be updated by select, but _not_ on other systems. + QTime timer; + timer.start(); + while (pendingConnections.isEmpty() && (-1 == msec || timer.elapsed() < msec)) { + result = ::select(listenSocket + 1, &readfds, 0, 0, &timeout); + if (-1 == result && errno != EINTR) { + setError(QLatin1String("QLocalServer::waitForNewConnection")); + closeServer(); + break; + } + if (result > 0) + _q_socketActivated(); + } + if (timedOut) + *timedOut = (result == 0); +} + +void QLocalServerPrivate::setError(const QString &function) +{ + if (EAGAIN == errno) + return; + + switch (errno) { + case EACCES: + errorString = QLocalServer::tr("%1: Permission denied").arg(function); + error = QAbstractSocket::SocketAccessError; + break; + case ELOOP: + case ENOENT: + case ENAMETOOLONG: + case EROFS: + case ENOTDIR: + errorString = QLocalServer::tr("%1: Name error").arg(function); + error = QAbstractSocket::HostNotFoundError; + break; + case EADDRINUSE: + errorString = QLocalServer::tr("%1: Address in use").arg(function); + error = QAbstractSocket::AddressInUseError; + break; + + default: + errorString = QLocalServer::tr("%1: Unknown error %2") + .arg(function).arg(errno); + error = QAbstractSocket::UnknownSocketError; +#if defined QLOCALSERVER_DEBUG + qWarning() << errorString << "fullServerName:" << fullServerName; +#endif + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSERVER diff --git a/src/network/socket/qlocalserver_win.cpp b/src/network/socket/qlocalserver_win.cpp new file mode 100644 index 0000000000..880cd7ea21 --- /dev/null +++ b/src/network/socket/qlocalserver_win.cpp @@ -0,0 +1,252 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalserver.h" +#include "qlocalserver_p.h" +#include "qlocalsocket.h" + +#include <qdebug.h> +#include <qdatetime.h> +#include <qcoreapplication.h> +#include <QMetaType> + +// The buffer size need to be 0 otherwise data could be +// lost if the socket that has written data closes the connection +// before it is read. Pipewriter is used for write buffering. +#define BUFSIZE 0 + +QT_BEGIN_NAMESPACE + +QLocalServerThread::QLocalServerThread(QObject *parent) : QThread(parent), + maxPendingConnections(1) +{ + stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + gotConnectionEvent = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +QLocalServerThread::~QLocalServerThread() +{ + stop(); + closeServer(); + CloseHandle(stopEvent); + CloseHandle(gotConnectionEvent); +} + +void QLocalServerThread::stop() +{ + if (isRunning()) { + SetEvent(stopEvent); + wait(); + ResetEvent(stopEvent); + } +} + +void QLocalServerThread::closeServer() +{ + while (!pendingHandles.isEmpty()) + CloseHandle(pendingHandles.dequeue()); +} + +QString QLocalServerThread::setName(const QString &name) +{ + QString pipePath = QLatin1String("\\\\.\\pipe\\"); + if (name.startsWith(pipePath)) + fullServerName = name; + else + fullServerName = pipePath + name; + for (int i = pendingHandles.count(); i < maxPendingConnections; ++i) + if (!makeHandle()) + break; + return fullServerName; +} + +bool QLocalServerThread::makeHandle() +{ + if (pendingHandles.count() >= maxPendingConnections) + return false; + + HANDLE handle = INVALID_HANDLE_VALUE; + QT_WA({ + handle = CreateNamedPipeW( + (TCHAR*)fullServerName.utf16(), // pipe name + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // read/write access + PIPE_TYPE_MESSAGE | // message type pipe + PIPE_READMODE_MESSAGE | // message-read mode + PIPE_WAIT, // blocking mode + PIPE_UNLIMITED_INSTANCES, // max. instances + BUFSIZE, // output buffer size + BUFSIZE, // input buffer size + 3000, // client time-out + NULL); + }, { + handle = CreateNamedPipeA( + fullServerName.toLocal8Bit().constData(), // pipe name + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // read/write access + PIPE_TYPE_MESSAGE | // message type pipe + PIPE_READMODE_MESSAGE | // message-read mode + PIPE_WAIT, // blocking mode + PIPE_UNLIMITED_INSTANCES, // max. instances + BUFSIZE, // output buffer size + BUFSIZE, // input buffer size + 3000, // client time-out + NULL); + }); + + if (INVALID_HANDLE_VALUE == handle) { + return false; + } + pendingHandles.enqueue(handle); + return true; +} + +void QLocalServerThread::run() +{ + OVERLAPPED op; + HANDLE handleArray[2]; + memset(&op, 0, sizeof(op)); + handleArray[0] = op.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + handleArray[1] = stopEvent; + HANDLE handle = INVALID_HANDLE_VALUE; + + forever { + if (INVALID_HANDLE_VALUE == handle) { + makeHandle(); + if (!pendingHandles.isEmpty()) + handle = pendingHandles.dequeue(); + } + if (INVALID_HANDLE_VALUE == handle) { + int windowsError = GetLastError(); + QString function = QLatin1String("QLocalServer::run"); + QString errorString = QLocalServer::tr("%1: Unknown error %2").arg(function).arg(windowsError); + emit error(QAbstractSocket::UnknownSocketError, errorString); + CloseHandle(handleArray[0]); + SetEvent(gotConnectionEvent); + return; + } + + BOOL isConnected = ConnectNamedPipe(handle, &op) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); + if (!isConnected) { + switch (WaitForMultipleObjects(2, handleArray, FALSE, INFINITE)) + { + case WAIT_OBJECT_0 + 1: + CloseHandle(handle); + CloseHandle(handleArray[0]); + return; + } + } + emit connected(handle); + handle = INVALID_HANDLE_VALUE; + ResetEvent(handleArray[0]); + SetEvent(gotConnectionEvent); + } +} + +void QLocalServerPrivate::init() +{ + Q_Q(QLocalServer); + qRegisterMetaType<HANDLE>("HANDLE"); + q->connect(&waitForConnection, SIGNAL(connected(HANDLE)), + q, SLOT(_q_openSocket(HANDLE)), Qt::QueuedConnection); + q->connect(&waitForConnection, SIGNAL(finished()), + q, SLOT(_q_stoppedListening()), Qt::QueuedConnection); + q->connect(&waitForConnection, SIGNAL(terminated()), + q, SLOT(_q_stoppedListening()), Qt::QueuedConnection); + q->connect(&waitForConnection, SIGNAL(error(QAbstractSocket::SocketError, const QString &)), + q, SLOT(_q_setError(QAbstractSocket::SocketError, const QString &))); +} + +bool QLocalServerPrivate::removeServer(const QString &name) +{ + Q_UNUSED(name); + return true; +} + +bool QLocalServerPrivate::listen(const QString &name) +{ + fullServerName = waitForConnection.setName(name); + serverName = name; + waitForConnection.start(); + return true; +} + +void QLocalServerPrivate::_q_setError(QAbstractSocket::SocketError e, const QString &eString) +{ + error = e; + errorString = eString; +} + +void QLocalServerPrivate::_q_stoppedListening() +{ + Q_Q(QLocalServer); + if (!inWaitingFunction) + q->close(); +} + +void QLocalServerPrivate::_q_openSocket(HANDLE handle) +{ + Q_Q(QLocalServer); + q->incomingConnection((int)handle); +} + +void QLocalServerPrivate::closeServer() +{ + waitForConnection.stop(); + waitForConnection.closeServer(); +} + +void QLocalServerPrivate::waitForNewConnection(int msecs, bool *timedOut) +{ + Q_Q(QLocalServer); + if (!pendingConnections.isEmpty() || !q->isListening()) + return; + + DWORD result = WaitForSingleObject(waitForConnection.gotConnectionEvent, + (msecs == -1) ? INFINITE : msecs); + if (result == WAIT_TIMEOUT) { + if (timedOut) + *timedOut = true; + } else { + ResetEvent(waitForConnection.gotConnectionEvent); + QCoreApplication::instance()->processEvents(); + } +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalsocket.cpp b/src/network/socket/qlocalsocket.cpp new file mode 100644 index 0000000000..327bfc6341 --- /dev/null +++ b/src/network/socket/qlocalsocket.cpp @@ -0,0 +1,504 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#ifndef QT_NO_LOCALSOCKET + +QT_BEGIN_NAMESPACE + +/*! + \class QLocalSocket + \since 4.4 + + \brief The QLocalSocket class provides a local socket. + + On Windows this is a named pipe and on Unix this is a local domain socket. + + If an error occurs, socketError() returns the type of error, and + errorString() can be called to get a human readable description + of what happened. + + Although QLocalSocket is designed for use with an event loop, it's possible + to use it without one. In that case, you must use waitForConnected(), + waitForReadyRead(), waitForBytesWritten(), and waitForDisconnected() + which blocks until the operation is complete or the timeout expires. + + Note that this feature is not supported on Window 9x. + + \sa QLocalServer +*/ + +/*! + \fn void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) + + Attempts to make a connection to \a name. + + The socket is opened in the given \a openMode and first enters ConnectingState. + It then attempts to connect to the address or addresses returned by the lookup. + Finally, if a connection is established, QLocalSocket enters ConnectedState + and emits connected(). + + At any point, the socket can emit error() to signal that an error occurred. + + See also state(), serverName(), and waitForConnected(). +*/ + +/*! + \fn void QLocalSocket::connected() + + This signal is emitted after connectToServer() has been called and + a connection has been successfully established. + + \sa connectToServer(), disconnected() +*/ + +/*! + \fn bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) + + Initializes QLocalSocket with the native socket descriptor + \a socketDescriptor. Returns true if socketDescriptor is accepted + as a valid socket descriptor; otherwise returns false. The socket is + opened in the mode specified by \a openMode, and enters the socket state + specified by \a socketState. + + Note: It is not possible to initialize two local sockets with the same + native socket descriptor. + + \sa socketDescriptor(), state(), openMode() +*/ + +/*! + \fn quintptr QLocalSocket::socketDescriptor() const + + Returns the native socket descriptor of the QLocalSocket object if + this is available; otherwise returns -1. + + The socket descriptor is not available when QLocalSocket + is in UnconnectedState. + + \sa setSocketDescriptor() +*/ + +/*! + \fn qint64 QLocalSocket::readData(char *data, qint64 c) + \reimp +*/ + +/*! + \fn qint64 QLocalSocket::writeData(const char *data, qint64 c) + \reimp +*/ + +/*! + \fn void QLocalSocket::abort() + + Aborts the current connection and resets the socket. + Unlike disconnectFromServer(), this function immediately closes the socket, + clearing any pending data in the write buffer. + + \sa disconnectFromServer(), close() +*/ + +/*! + \fn qint64 QLocalSocket::bytesAvailable() const + \reimp +*/ + +/*! + \fn qint64 QLocalSocket::bytesToWrite() const + \reimp +*/ + +/*! + \fn bool QLocalSocket::canReadLine() const + \reimp +*/ + +/*! + \fn void QLocalSocket::close() + \reimp +*/ + +/*! + \fn bool QLocalSocket::waitForBytesWritten(int msecs) + \reimp +*/ + +/*! + \fn bool QLocalSocket::flush() + + This function writes as much as possible from the internal write buffer + to the socket, without blocking. If any data was written, this function + returns true; otherwise false is returned. + + Call this function if you need QLocalSocket to start sending buffered data + immediately. The number of bytes successfully written depends on the + operating system. In most cases, you do not need to call this function, + because QLocalSocket will start sending data automatically once control + goes back to the event loop. In the absence of an event loop, call + waitForBytesWritten() instead. + + \sa write(), waitForBytesWritten() +*/ + +/*! + \fn void QLocalSocket::disconnectFromServer() + + Attempts to close the socket. If there is pending data waiting to be + written, QLocalSocket will enter ClosingState and wait until all data + has been written. Eventually, it will enter UnconnectedState and emit + the disconnectedFromServer() signal. + + \sa connectToServer() +*/ + +/*! + \fn QLocalSocket::LocalSocketError QLocalSocket::error() const + + Returns the type of error that last occurred. + + \sa state(), errorString() +*/ + +/*! + \fn bool QLocalSocket::isValid() const + + Returns true if the socket is valid and ready for use; otherwise + returns false. + + Note: The socket's state must be ConnectedState before reading + and writing can occur. + + \sa state() +*/ + +/*! + \fn qint64 QLocalSocket::readBufferSize() const + + Returns the size of the internal read buffer. This limits the amount of + data that the client can receive before you call read() or readAll(). + A read buffer size of 0 (the default) means that the buffer has no size + limit, ensuring that no data is lost. + + \sa setReadBufferSize(), read() +*/ + +/*! + \fn void QLocalSocket::setReadBufferSize(qint64 size) + + Sets the size of QLocalSocket's internal read buffer to be \a size bytes. + + If the buffer size is limited to a certain size, QLocalSocket won't + buffer more than this size of data. Exceptionally, a buffer size of 0 + means that the read buffer is unlimited and all incoming data is buffered. + This is the default. + + This option is useful if you only read the data at certain points in + time (e.g., in a real-time streaming application) or if you want to + protect your socket against receiving too much data, which may eventually + cause your application to run out of memory. + + \sa readBufferSize(), read() +*/ + +/*! + \fn bool QLocalSocket::waitForConnected(int msec) + + Waits until the socket is connected, up to \a msec milliseconds. If the + connection has been established, this function returns true; otherwise + it returns false. In the case where it returns false, you can call + error() to determine the cause of the error. + + The following example waits up to one second for a connection + to be established: + + \snippet doc/src/snippets/code/src_network_socket_qlocalsocket_unix.cpp 0 + + If msecs is -1, this function will not time out. + + \sa connectToServer(), connected() +*/ + +/*! + \fn bool QLocalSocket::waitForDisconnected(int msecs) + + Waits until the socket has disconnected, up to \a msecs + milliseconds. If the connection has been disconnected, this + function returns true; otherwise it returns false. In the case + where it returns false, you can call error() to determine + the cause of the error. + + The following example waits up to one second for a connection + to be closed: + + \snippet doc/src/snippets/code/src_network_socket_qlocalsocket_unix.cpp 1 + + If msecs is -1, this function will not time out. + + \sa disconnectFromServer(), close() +*/ + +/*! + \fn bool QLocalSocket::waitForReadyRead(int msecs) + + This function blocks until data is available for reading and the + \l{QIODevice::}{readyRead()} signal has been emitted. The function + will timeout after \a msecs milliseconds; the default timeout is + 30000 milliseconds. + + The function returns true if data is available for reading; + otherwise it returns false (if an error occurred or the + operation timed out). + + \sa waitForBytesWritten() +*/ + +/*! + \fn void QLocalSocket::disconnected() + + This signal is emitted when the socket has been disconnected. + + \sa connectToServer(), disconnectFromServer(), abort(), connected() +*/ + +/*! + \fn void QLocalSocket::error(QLocalSocket::LocalSocketError socketError) + + This signal is emitted after an error occurred. The \a socketError + parameter describes the type of error that occurred. + + QLocalSocket::LocalSocketError is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE. + + \sa error(), errorString() +*/ + +/*! + \fn void QLocalSocket::stateChanged(QLocalSocket::LocalSocketState socketState) + + This signal is emitted whenever QLocalSocket's state changes. + The \a socketState parameter is the new state. + + QLocalSocket::SocketState is not a registered metatype, so for queued + connections, you will have to register it with Q_DECLARE_METATYPE. + + \sa state() +*/ + +/*! + Creates a new local socket. The \a parent argument is passed to + QObject's constructor. + */ +QLocalSocket::QLocalSocket(QObject * parent) + : QIODevice(*new QLocalSocketPrivate, parent) +{ + Q_D(QLocalSocket); + d->init(); +} + +/*! + Destroys the socket, closing the connection if necessary. + */ +QLocalSocket::~QLocalSocket() +{ + close(); +#ifndef Q_OS_WIN + Q_D(QLocalSocket); + d->unixSocket.setParent(0); +#endif +} + +/*! + Returns the name of the peer as specified by connectToServer(), or an + empty QString if connectToServer() has not been called or it failed. + + \sa connectToServer(), fullServerName() + + */ +QString QLocalSocket::serverName() const +{ + Q_D(const QLocalSocket); + return d->serverName; +} + +/*! + Returns the server path that the socket is connected to. + + Note: This is platform specific + + \sa connectToServer(), serverName() + */ +QString QLocalSocket::fullServerName() const +{ + Q_D(const QLocalSocket); + return d->fullServerName; +} + +/*! + Returns the state of the socket. + + \sa error() + */ +QLocalSocket::LocalSocketState QLocalSocket::state() const +{ + Q_D(const QLocalSocket); + return d->state; +} + +/*! \reimp +*/ +bool QLocalSocket::isSequential() const +{ + return true; +} + +/*! + \enum QLocalSocket::LocalSocketError + + The LocalServerError enumeration represents the errors that can occur. + The most recent error can be retrieved through a call to + \l QLocalSocket::error(). + + \value ConnectionRefusedError The connection was refused by + the peer (or timed out). + \value PeerClosedError The remote socket closed the connection. + Note that the client socket (i.e., this socket) will be closed + after the remote close notification has been sent. + \value ServerNotFoundError The local socket name was not found. + \value SocketAccessError The socket operation failed because the + application lacked the required privileges. + \value SocketResourceError The local system ran out of resources + (e.g., too many sockets). + \value SocketTimeoutError The socket operation timed out. + \value DatagramTooLargeError The datagram was larger than the operating + system's limit (which can be as low as 8192 bytes). + \value ConnectionError An error occurred with the connection. + \value UnsupportedSocketOperationError The requested socket operation + is not supported by the local operating system. + \value UnknownSocketError An unidentified error occurred. + */ + +/*! + \enum QLocalSocket::LocalSocketState + + This enum describes the different states in which a socket can be. + + \sa QLocalSocket::state() + + \value UnconnectedState The socket is not connected. + \value ConnectingState The socket has started establishing a connection. + \value ConnectedState A connection is established. + \value ClosingState The socket is about to close + (data may still be waiting to be written). + */ + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, QLocalSocket::LocalSocketError error) +{ + switch (error) { + case QLocalSocket::ConnectionRefusedError: + debug << "QLocalSocket::ConnectionRefusedError"; + break; + case QLocalSocket::PeerClosedError: + debug << "QLocalSocket::PeerClosedError"; + break; + case QLocalSocket::ServerNotFoundError: + debug << "QLocalSocket::ServerNotFoundError"; + break; + case QLocalSocket::SocketAccessError: + debug << "QLocalSocket::SocketAccessError"; + break; + case QLocalSocket::SocketResourceError: + debug << "QLocalSocket::SocketResourceError"; + break; + case QLocalSocket::SocketTimeoutError: + debug << "QLocalSocket::SocketTimeoutError"; + break; + case QLocalSocket::DatagramTooLargeError: + debug << "QLocalSocket::DatagramTooLargeError"; + break; + case QLocalSocket::ConnectionError: + debug << "QLocalSocket::ConnectionError"; + break; + case QLocalSocket::UnsupportedSocketOperationError: + debug << "QLocalSocket::UnsupportedSocketOperationError"; + break; + case QLocalSocket::UnknownSocketError: + debug << "QLocalSocket::UnknownSocketError"; + break; + default: + debug << "QLocalSocket::SocketError(" << int(error) << ")"; + break; + } + return debug; +} + +QDebug operator<<(QDebug debug, QLocalSocket::LocalSocketState state) +{ + switch (state) { + case QLocalSocket::UnconnectedState: + debug << "QLocalSocket::UnconnectedState"; + break; + case QLocalSocket::ConnectingState: + debug << "QLocalSocket::ConnectingState"; + break; + case QLocalSocket::ConnectedState: + debug << "QLocalSocket::ConnectedState"; + break; + case QLocalSocket::ClosingState: + debug << "QLocalSocket::ClosingState"; + break; + default: + debug << "QLocalSocket::SocketState(" << int(state) << ")"; + break; + } + return debug; +} +#endif + +QT_END_NAMESPACE + +#endif + +#include "moc_qlocalsocket.cpp" diff --git a/src/network/socket/qlocalsocket.h b/src/network/socket/qlocalsocket.h new file mode 100644 index 0000000000..b8ad5c7294 --- /dev/null +++ b/src/network/socket/qlocalsocket.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALSOCKET_H +#define QLOCALSOCKET_H + +#include <QtCore/qiodevice.h> +#include <QtNetwork/qabstractsocket.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_LOCALSOCKET + +class QLocalSocketPrivate; + +class Q_NETWORK_EXPORT QLocalSocket : public QIODevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QLocalSocket) + +public: + enum LocalSocketError + { + ConnectionRefusedError = QAbstractSocket::ConnectionRefusedError, + PeerClosedError = QAbstractSocket::RemoteHostClosedError, + ServerNotFoundError = QAbstractSocket::HostNotFoundError, + SocketAccessError = QAbstractSocket::SocketAccessError, + SocketResourceError = QAbstractSocket::SocketResourceError, + SocketTimeoutError = QAbstractSocket::SocketTimeoutError, + DatagramTooLargeError = QAbstractSocket::DatagramTooLargeError, + ConnectionError = QAbstractSocket::NetworkError, + UnsupportedSocketOperationError = QAbstractSocket::UnsupportedSocketOperationError, + UnknownSocketError = QAbstractSocket::UnknownSocketError + }; + + enum LocalSocketState + { + UnconnectedState = QAbstractSocket::UnconnectedState, + ConnectingState = QAbstractSocket::ConnectingState, + ConnectedState = QAbstractSocket::ConnectedState, + ClosingState = QAbstractSocket::ClosingState + }; + + QLocalSocket(QObject *parent = 0); + ~QLocalSocket(); + + void connectToServer(const QString &name, OpenMode openMode = ReadWrite); + void disconnectFromServer(); + + QString serverName() const; + QString fullServerName() const; + + void abort(); + virtual bool isSequential() const; + virtual qint64 bytesAvailable() const; + virtual qint64 bytesToWrite() const; + virtual bool canReadLine() const; + virtual void close(); + LocalSocketError error() const; + bool flush(); + bool isValid() const; + qint64 readBufferSize() const; + void setReadBufferSize(qint64 size); + + bool setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState = ConnectedState, + OpenMode openMode = ReadWrite); + quintptr socketDescriptor() const; + + LocalSocketState state() const; + bool waitForBytesWritten(int msecs = 30000); + bool waitForConnected(int msecs = 30000); + bool waitForDisconnected(int msecs = 30000); + bool waitForReadyRead(int msecs = 30000); + +Q_SIGNALS: + void connected(); + void disconnected(); + void error(QLocalSocket::LocalSocketError socketError); + void stateChanged(QLocalSocket::LocalSocketState socketState); + +protected: + virtual qint64 readData(char*, qint64); + virtual qint64 writeData(const char*, qint64); + +private: + Q_DISABLE_COPY(QLocalSocket) +#if defined(QT_LOCALSOCKET_TCP) + Q_PRIVATE_SLOT(d_func(), void _q_stateChanged(QAbstractSocket::SocketState)) + Q_PRIVATE_SLOT(d_func(), void _q_error(QAbstractSocket::SocketError)) +#elif defined(Q_OS_WIN) + Q_PRIVATE_SLOT(d_func(), void _q_notified()) + Q_PRIVATE_SLOT(d_func(), void _q_canWrite()) + Q_PRIVATE_SLOT(d_func(), void _q_pipeClosed()) +#else + Q_PRIVATE_SLOT(d_func(), void _q_stateChanged(QAbstractSocket::SocketState)) + Q_PRIVATE_SLOT(d_func(), void _q_error(QAbstractSocket::SocketError)) + Q_PRIVATE_SLOT(d_func(), void _q_connectToSocket()) + Q_PRIVATE_SLOT(d_func(), void _q_abortConnectionAttempt()) +#endif +}; + +#ifndef QT_NO_DEBUG_STREAM +#include <QtCore/qdebug.h> +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QLocalSocket::LocalSocketError); +Q_NETWORK_EXPORT QDebug operator<<(QDebug, QLocalSocket::LocalSocketState); +#endif + +#endif // QT_NO_LOCALSOCKET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QLOCALSOCKET_H diff --git a/src/network/socket/qlocalsocket_p.h b/src/network/socket/qlocalsocket_p.h new file mode 100644 index 0000000000..dd48d0ab6b --- /dev/null +++ b/src/network/socket/qlocalsocket_p.h @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCALSOCKET_P_H +#define QLOCALSOCKET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLocalSocket class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QT_NO_LOCALSOCKET + +#include "qlocalsocket.h" +#include "private/qiodevice_p.h" + +#include <qtimer.h> + +#if defined(QT_LOCALSOCKET_TCP) +# include "qtcpsocket.h" +#elif defined(Q_OS_WIN) +# include "private/qwindowspipewriter_p.h" +# include "private/qringbuffer_p.h" +#else +# include "private/qnativesocketengine_p.h" +# include <qtcpsocket.h> +# include <qsocketnotifier.h> +# include <errno.h> +#endif + +QT_BEGIN_NAMESPACE + +#if !defined(Q_OS_WIN) && !defined(QT_LOCALSOCKET_TCP) +static inline int qSocket(int af, int socketype, int proto) +{ + int ret; + while((ret = qt_socket_socket(af, socketype, proto)) == -1 && errno == EINTR){} + return ret; +} + +static inline int qBind(int fd, const sockaddr *sa, int len) +{ + int ret; + while((ret = QT_SOCKET_BIND(fd, (sockaddr*)sa, len)) == -1 && errno == EINTR){} + return ret; +} + +static inline int qConnect(int fd, const sockaddr *sa, int len) +{ + int ret; + while((ret = QT_SOCKET_CONNECT(fd, (sockaddr*)sa, len)) == -1 && errno == EINTR){} + return ret; +} + +static inline int qListen(int fd, int backlog) +{ + int ret; + while((ret = qt_socket_listen(fd, backlog)) == -1 && errno == EINTR){} + return ret; +} + +static inline int qAccept(int fd, struct sockaddr *addr, QT_SOCKLEN_T *addrlen) +{ + int ret; + while((ret = qt_socket_accept(fd, addr, addrlen)) == -1 && errno == EINTR){} + return ret; +} +#endif //#if !defined(Q_OS_WIN) && !defined(QT_LOCALSOCKET_TCP) + +#if !defined(Q_OS_WIN) || defined(QT_LOCALSOCKET_TCP) +class QLocalUnixSocket : public QTcpSocket +{ + +public: + QLocalUnixSocket() : QTcpSocket() + { + }; + + inline void setSocketState(QAbstractSocket::SocketState state) + { + QTcpSocket::setSocketState(state); + }; + + inline void setErrorString(const QString &string) + { + QTcpSocket::setErrorString(string); + } + + inline void setSocketError(QAbstractSocket::SocketError error) + { + QTcpSocket::setSocketError(error); + } + + inline qint64 readData(char *data, qint64 maxSize) + { + return QTcpSocket::readData(data, maxSize); + } + + inline qint64 writeData(const char *data, qint64 maxSize) + { + return QTcpSocket::writeData(data, maxSize); + } +}; +#endif //#if !defined(Q_OS_WIN) || defined(QT_LOCALSOCKET_TCP) + +class QLocalSocketPrivate : public QIODevicePrivate +{ + Q_DECLARE_PUBLIC(QLocalSocket) + +public: + QLocalSocketPrivate(); + void init(); + +#if defined(QT_LOCALSOCKET_TCP) + QLocalUnixSocket* tcpSocket; + bool ownsTcpSocket; + void setSocket(QLocalUnixSocket*); + QString generateErrorString(QLocalSocket::LocalSocketError, const QString &function) const; + void errorOccurred(QLocalSocket::LocalSocketError, const QString &function); + void _q_stateChanged(QAbstractSocket::SocketState newState); + void _q_error(QAbstractSocket::SocketError newError); +#elif defined(Q_OS_WIN) + ~QLocalSocketPrivate() { + CloseHandle(overlapped.hEvent); + } + + void setErrorString(const QString &function); + void _q_notified(); + void _q_canWrite(); + void _q_pipeClosed(); + qint64 readData(char *data, qint64 maxSize); + qint64 bytesAvailable(); + bool readFromSocket(); + HANDLE handle; + OVERLAPPED overlapped; + QWindowsPipeWriter *pipeWriter; + qint64 readBufferMaxSize; + QRingBuffer readBuffer; + QTimer dataNotifier; + QLocalSocket::LocalSocketError error; + bool readyReadEmitted; + bool pipeClosed; +#else + QLocalUnixSocket unixSocket; + QString generateErrorString(QLocalSocket::LocalSocketError, const QString &function) const; + void errorOccurred(QLocalSocket::LocalSocketError, const QString &function); + void _q_stateChanged(QAbstractSocket::SocketState newState); + void _q_error(QAbstractSocket::SocketError newError); + void _q_connectToSocket(); + void _q_abortConnectionAttempt(); + QSocketNotifier *delayConnect; + QTimer *connectTimer; + int connectingSocket; + QString connectingName; + QIODevice::OpenMode connectingOpenMode; +#endif + + QString serverName; + QString fullServerName; + QLocalSocket::LocalSocketState state; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_LOCALSOCKET + +#endif // QLOCALSOCKET_P_H + diff --git a/src/network/socket/qlocalsocket_tcp.cpp b/src/network/socket/qlocalsocket_tcp.cpp new file mode 100644 index 0000000000..a4c6aa9aff --- /dev/null +++ b/src/network/socket/qlocalsocket_tcp.cpp @@ -0,0 +1,437 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" +#include "qlocalserver.h" + +#include <qhostaddress.h> +#include <qsettings.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + tcpSocket(0), + ownsTcpSocket(true), + state(QLocalSocket::UnconnectedState) +{ +} + +void QLocalSocketPrivate::init() +{ + setSocket(new QLocalUnixSocket); +} + +void QLocalSocketPrivate::setSocket(QLocalUnixSocket* socket) +{ + if (ownsTcpSocket) + delete tcpSocket; + ownsTcpSocket = false; + tcpSocket = socket; + + Q_Q(QLocalSocket); + // QIODevice signals + q->connect(tcpSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + q->connect(tcpSocket, SIGNAL(bytesWritten(qint64)), + q, SIGNAL(bytesWritten(qint64))); + q->connect(tcpSocket, SIGNAL(readyRead()), q, SIGNAL(readyRead())); + // QAbstractSocket signals + q->connect(tcpSocket, SIGNAL(connected()), q, SIGNAL(connected())); + q->connect(tcpSocket, SIGNAL(disconnected()), q, SIGNAL(disconnected())); + q->connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_stateChanged(QAbstractSocket::SocketState))); + q->connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_error(QAbstractSocket::SocketError))); + q->connect(tcpSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + tcpSocket->setParent(q); +} + +void QLocalSocketPrivate::_q_error(QAbstractSocket::SocketError socketError) +{ + Q_Q(QLocalSocket); + QString function = QLatin1String("QLocalSocket"); + QLocalSocket::LocalSocketError error = (QLocalSocket::LocalSocketError)socketError; + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); +} + +void QLocalSocketPrivate::_q_stateChanged(QAbstractSocket::SocketState newState) +{ + Q_Q(QLocalSocket); + QLocalSocket::LocalSocketState currentState = state; + switch(newState) { + case QAbstractSocket::UnconnectedState: + state = QLocalSocket::UnconnectedState; + serverName = QString(); + fullServerName = QString(); + break; + case QAbstractSocket::ConnectingState: + state = QLocalSocket::ConnectingState; + break; + case QAbstractSocket::ConnectedState: + state = QLocalSocket::ConnectedState; + break; + case QAbstractSocket::ClosingState: + state = QLocalSocket::ClosingState; + break; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket::Unhandled socket state change:" << newState; +#endif + return; + } + if (currentState != state) + emit q->stateChanged(state); +} + +QString QLocalSocketPrivate::generateErrorString(QLocalSocket::LocalSocketError error, const QString &function) const +{ + QString errorString; + switch (error) { + case QLocalSocket::ConnectionRefusedError: + errorString = QLocalSocket::tr("%1: Connection refused").arg(function); + break; + case QLocalSocket::PeerClosedError: + errorString = QLocalSocket::tr("%1: Remote closed").arg(function); + break; + case QLocalSocket::ServerNotFoundError: + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + break; + case QLocalSocket::SocketAccessError: + errorString = QLocalSocket::tr("%1: Socket access error").arg(function); + break; + case QLocalSocket::SocketResourceError: + errorString = QLocalSocket::tr("%1: Socket resource error").arg(function); + break; + case QLocalSocket::SocketTimeoutError: + errorString = QLocalSocket::tr("%1: Socket operation timed out").arg(function); + break; + case QLocalSocket::DatagramTooLargeError: + errorString = QLocalSocket::tr("%1: Datagram too large").arg(function); + break; + case QLocalSocket::ConnectionError: + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + break; + case QLocalSocket::UnsupportedSocketOperationError: + errorString = QLocalSocket::tr("%1: The socket operation is not supported").arg(function); + break; + case QLocalSocket::UnknownSocketError: + default: + errorString = QLocalSocket::tr("%1: Unknown error").arg(function); + } + return errorString; +} + +void QLocalSocketPrivate::errorOccurred(QLocalSocket::LocalSocketError error, const QString &function) +{ + Q_Q(QLocalSocket); + switch (error) { + case QLocalSocket::ConnectionRefusedError: + tcpSocket->setSocketError(QAbstractSocket::ConnectionRefusedError); + break; + case QLocalSocket::PeerClosedError: + tcpSocket->setSocketError(QAbstractSocket::RemoteHostClosedError); + break; + case QLocalSocket::ServerNotFoundError: + tcpSocket->setSocketError(QAbstractSocket::HostNotFoundError); + break; + case QLocalSocket::SocketAccessError: + tcpSocket->setSocketError(QAbstractSocket::SocketAccessError); + break; + case QLocalSocket::SocketResourceError: + tcpSocket->setSocketError(QAbstractSocket::SocketResourceError); + break; + case QLocalSocket::SocketTimeoutError: + tcpSocket->setSocketError(QAbstractSocket::SocketTimeoutError); + break; + case QLocalSocket::DatagramTooLargeError: + tcpSocket->setSocketError(QAbstractSocket::DatagramTooLargeError); + break; + case QLocalSocket::ConnectionError: + tcpSocket->setSocketError(QAbstractSocket::NetworkError); + break; + case QLocalSocket::UnsupportedSocketOperationError: + tcpSocket->setSocketError(QAbstractSocket::UnsupportedSocketOperationError); + break; + case QLocalSocket::UnknownSocketError: + default: + tcpSocket->setSocketError(QAbstractSocket::UnknownSocketError); + } + + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); + + // errors cause a disconnect + tcpSocket->setSocketState(QAbstractSocket::UnconnectedState); + bool stateChanged = (state != QLocalSocket::UnconnectedState); + state = QLocalSocket::UnconnectedState; + q->close(); + if (stateChanged) + q->emit stateChanged(state); +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState + || state() == ConnectingState) + return; + + d->errorString = QString(); + d->state = ConnectingState; + emit stateChanged(d->state); + + if (name.isEmpty()) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + + d->serverName = name; + const QLatin1String prefix("QLocalServer/"); + if (name.startsWith(prefix)) + d->fullServerName = name; + else + d->fullServerName = prefix + name; + + QSettings settings(QLatin1String("Trolltech"), QLatin1String("Qt")); + bool ok; + const quint16 port = settings.value(d->fullServerName).toUInt(&ok); + if (!ok) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + d->tcpSocket->connectToHost(QHostAddress::LocalHost, port, openMode); + QIODevice::open(openMode); +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + QAbstractSocket::SocketState newSocketState = QAbstractSocket::UnconnectedState; + switch (socketState) { + case ConnectingState: + newSocketState = QAbstractSocket::ConnectingState; + break; + case ConnectedState: + newSocketState = QAbstractSocket::ConnectedState; + break; + case ClosingState: + newSocketState = QAbstractSocket::ClosingState; + break; + case UnconnectedState: + newSocketState = QAbstractSocket::UnconnectedState; + break; + } + QIODevice::open(openMode); + d->state = socketState; + + // Is our parent a localServer? Then it wants us to use its remote socket. + QLocalServer* localServer = qobject_cast<QLocalServer*>( parent() ); + if (localServer) { + foreach (QObject* child, localServer->children()) { + QTcpSocket* childTcpSocket = qobject_cast<QTcpSocket*>(child); + if (childTcpSocket && childTcpSocket->socketDescriptor() == socketDescriptor) { + d->setSocket( static_cast<QLocalUnixSocket*>(childTcpSocket) ); + return true; + } + } + } + + // We couldn't find the socket in the children list of our server. + // So it might be that the user wants to set a socket descriptor. + return d->tcpSocket->setSocketDescriptor(socketDescriptor, + newSocketState, openMode); +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->socketDescriptor(); +} + +qint64 QLocalSocket::readData(char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->tcpSocket->readData(data, c); +} + +qint64 QLocalSocket::writeData(const char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->tcpSocket->writeData(data, c); +} + +void QLocalSocket::abort() +{ + Q_D(QLocalSocket); + d->tcpSocket->abort(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + return QIODevice::bytesAvailable() + d->tcpSocket->bytesAvailable(); +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->bytesToWrite(); +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + return QIODevice::canReadLine() || d->tcpSocket->canReadLine(); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + d->tcpSocket->close(); + d->serverName = QString(); + d->fullServerName = QString(); + QIODevice::close(); +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(QLocalSocket); + return d->tcpSocket->waitForBytesWritten(msecs); +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + return d->tcpSocket->flush(); +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + d->tcpSocket->disconnectFromHost(); +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + switch (d->tcpSocket->error()) { + case QAbstractSocket::ConnectionRefusedError: + return QLocalSocket::ConnectionRefusedError; + case QAbstractSocket::RemoteHostClosedError: + return QLocalSocket::PeerClosedError; + case QAbstractSocket::HostNotFoundError: + return QLocalSocket::ServerNotFoundError; + case QAbstractSocket::SocketAccessError: + return QLocalSocket::SocketAccessError; + case QAbstractSocket::SocketResourceError: + return QLocalSocket::SocketResourceError; + case QAbstractSocket::SocketTimeoutError: + return QLocalSocket::SocketTimeoutError; + case QAbstractSocket::DatagramTooLargeError: + return QLocalSocket::DatagramTooLargeError; + case QAbstractSocket::NetworkError: + return QLocalSocket::ConnectionError; + case QAbstractSocket::UnsupportedSocketOperationError: + return QLocalSocket::UnsupportedSocketOperationError; + case QAbstractSocket::UnknownSocketError: + return QLocalSocket::UnknownSocketError; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << d->tcpSocket->error(); +#endif + break; + } + return UnknownSocketError; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->isValid(); +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->tcpSocket->readBufferSize(); +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->tcpSocket->setReadBufferSize(size); +} + +bool QLocalSocket::waitForConnected(int msec) +{ + Q_D(QLocalSocket); + if (state() != ConnectingState) + return (state() == ConnectedState); + + return d->tcpSocket->waitForConnected(msec); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) { + qWarning() << "QLocalSocket::waitForDisconnected() is not allowed in UnconnectedState"; + return false; + } + return (d->tcpSocket->waitForDisconnected(msecs)); +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + if (state() == QLocalSocket::UnconnectedState) + return false; + return (d->tcpSocket->waitForReadyRead(msecs)); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qlocalsocket_unix.cpp b/src/network/socket/qlocalsocket_unix.cpp new file mode 100644 index 0000000000..a375e9ba10 --- /dev/null +++ b/src/network/socket/qlocalsocket_unix.cpp @@ -0,0 +1,566 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#ifndef QT_NO_LOCALSOCKET + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <qdatetime.h> +#include <qdir.h> +#include <qdebug.h> + +#define QT_CONNECT_TIMEOUT 30000 + +QT_BEGIN_NAMESPACE + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + delayConnect(0), + connectTimer(0), + connectingSocket(-1), + connectingOpenMode(0), + state(QLocalSocket::UnconnectedState) +{ +} + +void QLocalSocketPrivate::init() +{ + Q_Q(QLocalSocket); + // QIODevice signals + q->connect(&unixSocket, SIGNAL(aboutToClose()), q, SIGNAL(aboutToClose())); + q->connect(&unixSocket, SIGNAL(bytesWritten(qint64)), + q, SIGNAL(bytesWritten(qint64))); + q->connect(&unixSocket, SIGNAL(readyRead()), q, SIGNAL(readyRead())); + // QAbstractSocket signals + q->connect(&unixSocket, SIGNAL(connected()), q, SIGNAL(connected())); + q->connect(&unixSocket, SIGNAL(disconnected()), q, SIGNAL(disconnected())); + q->connect(&unixSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_stateChanged(QAbstractSocket::SocketState))); + q->connect(&unixSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_error(QAbstractSocket::SocketError))); + q->connect(&unixSocket, SIGNAL(readChannelFinished()), q, SIGNAL(readChannelFinished())); + unixSocket.setParent(q); +} + +void QLocalSocketPrivate::_q_error(QAbstractSocket::SocketError socketError) +{ + Q_Q(QLocalSocket); + QString function = QLatin1String("QLocalSocket"); + QLocalSocket::LocalSocketError error = (QLocalSocket::LocalSocketError)socketError; + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); +} + +void QLocalSocketPrivate::_q_stateChanged(QAbstractSocket::SocketState newState) +{ + Q_Q(QLocalSocket); + QLocalSocket::LocalSocketState currentState = state; + switch(newState) { + case QAbstractSocket::UnconnectedState: + state = QLocalSocket::UnconnectedState; + serverName = QString(); + fullServerName = QString(); + break; + case QAbstractSocket::ConnectingState: + state = QLocalSocket::ConnectingState; + break; + case QAbstractSocket::ConnectedState: + state = QLocalSocket::ConnectedState; + break; + case QAbstractSocket::ClosingState: + state = QLocalSocket::ClosingState; + break; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket::Unhandled socket state change:" << newState; +#endif + return; + } + if (currentState != state) + emit q->stateChanged(state); +} + +QString QLocalSocketPrivate::generateErrorString(QLocalSocket::LocalSocketError error, const QString &function) const +{ + QString errorString; + switch (error) { + case QLocalSocket::ConnectionRefusedError: + errorString = QLocalSocket::tr("%1: Connection refused").arg(function); + break; + case QLocalSocket::PeerClosedError: + errorString = QLocalSocket::tr("%1: Remote closed").arg(function); + break; + case QLocalSocket::ServerNotFoundError: + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + break; + case QLocalSocket::SocketAccessError: + errorString = QLocalSocket::tr("%1: Socket access error").arg(function); + break; + case QLocalSocket::SocketResourceError: + errorString = QLocalSocket::tr("%1: Socket resource error").arg(function); + break; + case QLocalSocket::SocketTimeoutError: + errorString = QLocalSocket::tr("%1: Socket operation timed out").arg(function); + break; + case QLocalSocket::DatagramTooLargeError: + errorString = QLocalSocket::tr("%1: Datagram too large").arg(function); + break; + case QLocalSocket::ConnectionError: + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + break; + case QLocalSocket::UnsupportedSocketOperationError: + errorString = QLocalSocket::tr("%1: The socket operation is not supported").arg(function); + break; + case QLocalSocket::UnknownSocketError: + default: + errorString = QLocalSocket::tr("%1: Unknown error %2").arg(function).arg(errno); + } + return errorString; +} + +void QLocalSocketPrivate::errorOccurred(QLocalSocket::LocalSocketError error, const QString &function) +{ + Q_Q(QLocalSocket); + switch (error) { + case QLocalSocket::ConnectionRefusedError: + unixSocket.setSocketError(QAbstractSocket::ConnectionRefusedError); + break; + case QLocalSocket::PeerClosedError: + unixSocket.setSocketError(QAbstractSocket::RemoteHostClosedError); + break; + case QLocalSocket::ServerNotFoundError: + unixSocket.setSocketError(QAbstractSocket::HostNotFoundError); + break; + case QLocalSocket::SocketAccessError: + unixSocket.setSocketError(QAbstractSocket::SocketAccessError); + break; + case QLocalSocket::SocketResourceError: + unixSocket.setSocketError(QAbstractSocket::SocketResourceError); + break; + case QLocalSocket::SocketTimeoutError: + unixSocket.setSocketError(QAbstractSocket::SocketTimeoutError); + break; + case QLocalSocket::DatagramTooLargeError: + unixSocket.setSocketError(QAbstractSocket::DatagramTooLargeError); + break; + case QLocalSocket::ConnectionError: + unixSocket.setSocketError(QAbstractSocket::NetworkError); + break; + case QLocalSocket::UnsupportedSocketOperationError: + unixSocket.setSocketError(QAbstractSocket::UnsupportedSocketOperationError); + break; + case QLocalSocket::UnknownSocketError: + default: + unixSocket.setSocketError(QAbstractSocket::UnknownSocketError); + } + + QString errorString = generateErrorString(error, function); + q->setErrorString(errorString); + emit q->error(error); + + // errors cause a disconnect + unixSocket.setSocketState(QAbstractSocket::UnconnectedState); + bool stateChanged = (state != QLocalSocket::UnconnectedState); + state = QLocalSocket::UnconnectedState; + q->close(); + if (stateChanged) + q->emit stateChanged(state); +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState + || state() == ConnectingState) + return; + + d->errorString = QString(); + d->unixSocket.setSocketState(QAbstractSocket::ConnectingState); + d->state = ConnectingState; + emit stateChanged(d->state); + + if (name.isEmpty()) { + d->errorOccurred(ServerNotFoundError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + + // create the socket + if (-1 == (d->connectingSocket = qSocket(PF_UNIX, SOCK_STREAM, 0))) { + d->errorOccurred(UnsupportedSocketOperationError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + + // set non blocking so we can try to connect and it wont wait + int flags = fcntl(d->connectingSocket, F_GETFL, 0); + if (-1 == flags + || -1 == (fcntl(d->connectingSocket, F_SETFL, flags | O_NONBLOCK))) { + d->errorOccurred(UnknownSocketError, + QLatin1String("QLocalSocket::connectToServer")); + return; + } + + // _q_connectToSocket does the actual connecting + d->connectingName = name; + d->connectingOpenMode = openMode; + d->_q_connectToSocket(); +} + +/*! + \internal + + Tries to connect connectingName and connectingOpenMode + + \sa connectToServer() waitForConnected() + */ +void QLocalSocketPrivate::_q_connectToSocket() +{ + Q_Q(QLocalSocket); + QString connectingPathName; + + // determine the full server path + if (connectingName.startsWith(QLatin1Char('/'))) { + connectingPathName = connectingName; + } else { + connectingPathName = QDir::tempPath(); + connectingPathName += QLatin1Char('/') + connectingName; + } + + struct sockaddr_un name; + name.sun_family = PF_UNIX; + if (sizeof(name.sun_path) < (uint)connectingPathName.toLatin1().size() + 1) { + QString function = QLatin1String("QLocalSocket::connectToServer"); + errorOccurred(QLocalSocket::ServerNotFoundError, function); + return; + } + ::memcpy(name.sun_path, connectingPathName.toLatin1().data(), + connectingPathName.toLatin1().size() + 1); + if (-1 == qConnect(connectingSocket, (struct sockaddr *)&name, sizeof(name))) { + QString function = QLatin1String("QLocalSocket::connectToServer"); + switch (errno) + { + case EINVAL: + case ECONNREFUSED: + errorOccurred(QLocalSocket::ConnectionRefusedError, function); + break; + case ENOENT: + errorOccurred(QLocalSocket::ServerNotFoundError, function); + break; + case EACCES: + case EPERM: + errorOccurred(QLocalSocket::SocketAccessError, function); + break; + case ETIMEDOUT: + errorOccurred(QLocalSocket::SocketTimeoutError, function); + break; + case EAGAIN: + // Try again later, all of the sockets listening are full + if (!delayConnect) { + delayConnect = new QSocketNotifier(connectingSocket, QSocketNotifier::Write); + q->connect(delayConnect, SIGNAL(activated(int)), q, SLOT(_q_connectToSocket())); + } + if (!connectTimer) { + connectTimer = new QTimer(q); + q->connect(connectTimer, SIGNAL(timeout()), + q, SLOT(_q_abortConnectionAttempt()), + Qt::DirectConnection); + connectTimer->start(QT_CONNECT_TIMEOUT); + } + delayConnect->setEnabled(true); + break; + default: + errorOccurred(QLocalSocket::UnknownSocketError, function); + } + return; + } + + // connected! + if (delayConnect) { + delayConnect->setEnabled(false); + delete delayConnect; + delayConnect = 0; + } + serverName = connectingName; + fullServerName = connectingPathName; + if (unixSocket.setSocketDescriptor(connectingSocket, + QAbstractSocket::ConnectedState, connectingOpenMode)) { + q->QIODevice::open(connectingOpenMode); + q->emit connected(); + } else { + QString function = QLatin1String("QLocalSocket::connectToServer"); + errorOccurred(QLocalSocket::UnknownSocketError, function); + } + connectingSocket = -1; + connectingName = QString(); + connectingOpenMode = 0; +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + QAbstractSocket::SocketState newSocketState = QAbstractSocket::UnconnectedState; + switch (socketState) { + case ConnectingState: + newSocketState = QAbstractSocket::ConnectingState; + break; + case ConnectedState: + newSocketState = QAbstractSocket::ConnectedState; + break; + case ClosingState: + newSocketState = QAbstractSocket::ClosingState; + break; + case UnconnectedState: + newSocketState = QAbstractSocket::UnconnectedState; + break; + } + QIODevice::open(openMode); + d->state = socketState; + return d->unixSocket.setSocketDescriptor(socketDescriptor, + newSocketState, openMode); +} + +void QLocalSocketPrivate::_q_abortConnectionAttempt() +{ + Q_Q(QLocalSocket); + q->close(); +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.socketDescriptor(); +} + +qint64 QLocalSocket::readData(char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->unixSocket.readData(data, c); +} + +qint64 QLocalSocket::writeData(const char *data, qint64 c) +{ + Q_D(QLocalSocket); + return d->unixSocket.writeData(data, c); +} + +void QLocalSocket::abort() +{ + Q_D(QLocalSocket); + d->unixSocket.abort(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + return QIODevice::bytesAvailable() + d->unixSocket.bytesAvailable(); +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.bytesToWrite(); +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + return QIODevice::canReadLine() || d->unixSocket.canReadLine(); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + d->unixSocket.close(); + if (d->delayConnect) { + d->delayConnect->setEnabled(false); + delete d->delayConnect; + d->delayConnect = 0; + d->connectTimer->stop(); + delete d->connectTimer; + d->connectTimer = 0; + } + if (d->connectingSocket != -1) + ::close(d->connectingSocket); + d->connectingSocket = -1; + d->connectingName = QString(); + d->connectingOpenMode = 0; + d->serverName = QString(); + d->fullServerName = QString(); + QIODevice::close(); +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(QLocalSocket); + return d->unixSocket.waitForBytesWritten(msecs); +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + return d->unixSocket.flush(); +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + d->unixSocket.disconnectFromHost(); +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + switch (d->unixSocket.error()) { + case QAbstractSocket::ConnectionRefusedError: + return QLocalSocket::ConnectionRefusedError; + case QAbstractSocket::RemoteHostClosedError: + return QLocalSocket::PeerClosedError; + case QAbstractSocket::HostNotFoundError: + return QLocalSocket::ServerNotFoundError; + case QAbstractSocket::SocketAccessError: + return QLocalSocket::SocketAccessError; + case QAbstractSocket::SocketResourceError: + return QLocalSocket::SocketResourceError; + case QAbstractSocket::SocketTimeoutError: + return QLocalSocket::SocketTimeoutError; + case QAbstractSocket::DatagramTooLargeError: + return QLocalSocket::DatagramTooLargeError; + case QAbstractSocket::NetworkError: + return QLocalSocket::ConnectionError; + case QAbstractSocket::UnsupportedSocketOperationError: + return QLocalSocket::UnsupportedSocketOperationError; + case QAbstractSocket::UnknownSocketError: + return QLocalSocket::UnknownSocketError; + default: +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << d->unixSocket.error(); +#endif + break; + } + return UnknownSocketError; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.isValid(); +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->unixSocket.readBufferSize(); +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->unixSocket.setReadBufferSize(size); +} + +bool QLocalSocket::waitForConnected(int msec) +{ + Q_D(QLocalSocket); + if (state() != ConnectingState) + return (state() == ConnectedState); + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(d->connectingSocket, &readfds); + + timeval timeout; + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + + // timeout can not be 0 or else select will return an error. + if (0 == msec) + timeout.tv_usec = 1000; + + int result = -1; + // on Linux timeout will be updated by select, but _not_ on other systems. + QTime timer; + timer.start(); + while (state() == ConnectingState + && (-1 == msec || timer.elapsed() < msec)) { + result = ::select(d->connectingSocket + 1, &readfds, 0, 0, &timeout); + if (-1 == result && errno != EINTR) { + d->errorOccurred( QLocalSocket::UnknownSocketError, + QLatin1String("QLocalSocket::waitForConnected")); + break; + } + if (result > 0) + d->_q_connectToSocket(); + } + + return (state() == ConnectedState); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) { + qWarning() << "QLocalSocket::waitForDisconnected() is not allowed in UnconnectedState"; + return false; + } + return (d->unixSocket.waitForDisconnected(msecs)); +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + if (state() == QLocalSocket::UnconnectedState) + return false; + return (d->unixSocket.waitForReadyRead(msecs)); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/network/socket/qlocalsocket_win.cpp b/src/network/socket/qlocalsocket_win.cpp new file mode 100644 index 0000000000..e759d0bd1b --- /dev/null +++ b/src/network/socket/qlocalsocket_win.cpp @@ -0,0 +1,537 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlocalsocket.h" +#include "qlocalsocket_p.h" + +#include <private/qthread_p.h> +#include <qcoreapplication.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +#define NOTIFYTIMEOUT 100 + +void QLocalSocketPrivate::init() +{ + Q_Q(QLocalSocket); + QObject::connect(&dataNotifier, SIGNAL(timeout()), q, SLOT(_q_notified())); + overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +void QLocalSocketPrivate::setErrorString(const QString &function) +{ + Q_Q(QLocalSocket); + BOOL windowsError = GetLastError(); + QLocalSocket::LocalSocketState currentState = state; + + // If the connectToServer fails due to WaitNamedPipe() time-out, assume ConnectionError + if (state == QLocalSocket::ConnectingState && windowsError == ERROR_SEM_TIMEOUT) + windowsError = ERROR_NO_DATA; + + switch (windowsError) { + case ERROR_PIPE_NOT_CONNECTED: + case ERROR_BROKEN_PIPE: + case ERROR_NO_DATA: + error = QLocalSocket::ConnectionError; + errorString = QLocalSocket::tr("%1: Connection error").arg(function); + state = QLocalSocket::UnconnectedState; + break; + case ERROR_FILE_NOT_FOUND: + error = QLocalSocket::ServerNotFoundError; + errorString = QLocalSocket::tr("%1: Invalid name").arg(function); + state = QLocalSocket::UnconnectedState; + break; + default: + error = QLocalSocket::UnknownSocketError; + errorString = QLocalSocket::tr("%1: Unknown error %2").arg(function).arg(windowsError); +#if defined QLOCALSOCKET_DEBUG + qWarning() << "QLocalSocket error not handled:" << errorString; +#endif + state = QLocalSocket::UnconnectedState; + } + + if (currentState != state) { + q->emit stateChanged(state); + if (state == QLocalSocket::UnconnectedState) + q->emit disconnected(); + } + emit q->error(error); +} + +QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), + handle(INVALID_HANDLE_VALUE), + pipeWriter(0), + readBufferMaxSize(0), + error(QLocalSocket::UnknownSocketError), + readyReadEmitted(false), + pipeClosed(false), + state(QLocalSocket::UnconnectedState) +{ +} + +void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) +{ + Q_D(QLocalSocket); + if (state() == ConnectedState || state() == ConnectingState) + return; + + d->error = QLocalSocket::UnknownSocketError; + d->errorString = QString(); + d->state = ConnectingState; + emit stateChanged(d->state); + if (name.isEmpty()) { + d->error = QLocalSocket::ServerNotFoundError; + setErrorString(QLocalSocket::tr("%1: Invalid name").arg(QLatin1String("QLocalSocket::connectToServer"))); + d->state = UnconnectedState; + emit error(d->error); + emit stateChanged(d->state); + return; + } + + QString pipePath = QLatin1String("\\\\.\\pipe\\"); + if (name.startsWith(pipePath)) + d->fullServerName = name; + else + d->fullServerName = pipePath + name; + // Try to open a named pipe + HANDLE localSocket; + forever { + DWORD permissions = (openMode & QIODevice::ReadOnly) ? GENERIC_READ : 0; + permissions |= (openMode & QIODevice::WriteOnly) ? GENERIC_WRITE : 0; + QT_WA({ + localSocket = CreateFileW( + (TCHAR*)d->fullServerName.utf16(), // pipe name + permissions, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, // opens existing pipe + 0, // default attributes + NULL); // no template file + }, { + localSocket = CreateFileA( + d->fullServerName.toLocal8Bit().constData(), // pipe name + permissions, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, // opens existing pipe + 0, // default attributes + NULL); // no template file + }); + if (localSocket != INVALID_HANDLE_VALUE) + break; + DWORD error = GetLastError(); + // It is really an error only if it is not ERROR_PIPE_BUSY + if (ERROR_PIPE_BUSY != error) { + break; + } + + // All pipe instances are busy, so wait until connected or up to 5 seconds. + QT_WA({ + if (!WaitNamedPipeW((TCHAR*)d->fullServerName.utf16(), 5000)) + break; + }, { + if (!WaitNamedPipeA(d->fullServerName.toLocal8Bit().constData(), 5000)) + break; + }); + } + + if (localSocket == INVALID_HANDLE_VALUE) { + d->setErrorString(QLatin1String("QLocalSocket::connectToServer")); + d->fullServerName = QString(); + return; + } + + // we have a valid handle + d->serverName = name; + if (setSocketDescriptor((quintptr)localSocket), openMode) { + d->handle = localSocket; + emit connected(); + } +} + +// This is reading from the buffer +qint64 QLocalSocket::readData(char *data, qint64 maxSize) +{ + Q_D(QLocalSocket); + if (d->readBuffer.isEmpty()) { + if (!d->readFromSocket()) { + if (d->pipeClosed) + return -1; + return 0; + } + } + + if (!d->dataNotifier.isActive() && d->threadData->eventDispatcher) + d->dataNotifier.start(NOTIFYTIMEOUT); + + if (d->readBuffer.isEmpty()) + return qint64(0); + + // If readFromSocket() read data, copy it to its destination. + if (maxSize == 1) { + *data = d->readBuffer.getChar(); + return 1; + } + + qint64 bytesToRead = qMin(qint64(d->readBuffer.size()), maxSize); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = d->readBuffer.readPointer(); + int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar), + d->readBuffer.nextDataBlockSize()); + memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + d->readBuffer.free(bytesToReadFromThisBlock); + } + return readSoFar; +} + +/*! + \internal + read from the socket + */ +qint64 QLocalSocketPrivate::readData(char *data, qint64 maxSize) +{ + DWORD bytesRead = 0; + overlapped.Offset = 0; + overlapped.OffsetHigh = 0; + bool success = ReadFile(handle, data, maxSize, &bytesRead, &overlapped); + if (!success && GetLastError() == ERROR_IO_PENDING) + if (GetOverlappedResult(handle, &overlapped, &bytesRead, TRUE)) + success = true; + if (!success) { + setErrorString(QLatin1String("QLocalSocket::readData")); + return 0; + } + return bytesRead; +} + +/*! + \internal + Reads data from the socket into the readbuffer + */ +bool QLocalSocketPrivate::readFromSocket() +{ + qint64 bytesToRead = bytesAvailable(); + if (bytesToRead == 0) + return false; + + if (readBufferMaxSize && bytesToRead + > (readBufferMaxSize - readBuffer.size())) + bytesToRead = readBufferMaxSize - readBuffer.size(); + + char *ptr = readBuffer.reserve(bytesToRead); + qint64 readBytes = readData(ptr, bytesToRead); + if (readBytes == 0) { + readBuffer.chop(bytesToRead); + return false; + } + readyReadEmitted = false; + readBuffer.chop(int(bytesToRead - (readBytes < 0 ? qint64(0) : readBytes))); + return true; +} + +qint64 QLocalSocket::writeData(const char *data, qint64 maxSize) +{ + Q_D(QLocalSocket); + if (!d->pipeWriter) { + d->pipeWriter = new QWindowsPipeWriter(d->handle, this); + d->pipeWriter->start(); + connect(d->pipeWriter, SIGNAL(canWrite()), this, SLOT(_q_canWrite())); + } + return d->pipeWriter->write(data, maxSize); +} + +void QLocalSocket::abort() +{ + close(); +} + +/*! + The number of bytes available from the pipe + */ +qint64 QLocalSocketPrivate::bytesAvailable() +{ + Q_Q(QLocalSocket); + if (q->state() != QLocalSocket::ConnectedState) + return 0; + DWORD bytes; + if (PeekNamedPipe(handle, NULL, 0, NULL, &bytes, NULL)) { + return bytes; + } else { + if (ERROR_BROKEN_PIPE == GetLastError() && !pipeClosed) { + pipeClosed = true; + QTimer::singleShot(0, q, SLOT(_q_pipeClosed())); + } + } + return 0; +} + +void QLocalSocketPrivate::_q_pipeClosed() +{ + Q_Q(QLocalSocket); + q->close(); +} + +qint64 QLocalSocket::bytesAvailable() const +{ + Q_D(const QLocalSocket); + qint64 available = QIODevice::bytesAvailable(); + available += (qint64) d->readBuffer.size(); + return available; +} + +qint64 QLocalSocket::bytesToWrite() const +{ + Q_D(const QLocalSocket); + return (d->pipeWriter) ? d->pipeWriter->bytesToWrite() : 0; +} + +bool QLocalSocket::canReadLine() const +{ + Q_D(const QLocalSocket); + if (state() != ConnectedState) + return false; + return (d->readBuffer.indexOf('\n') != -1 || QIODevice::canReadLine()); +} + +void QLocalSocket::close() +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) + return; + + QIODevice::close(); + d->state = ClosingState; + emit stateChanged(d->state); + d->readyReadEmitted = false; + emit readChannelFinished(); + d->serverName = QString(); + d->fullServerName = QString(); + + if (state() != UnconnectedState && bytesToWrite() > 0) { + disconnectFromServer(); + return; + } + d->pipeClosed = false; + DisconnectNamedPipe(d->handle); + CloseHandle(d->handle); + d->handle = INVALID_HANDLE_VALUE; + d->state = UnconnectedState; + emit stateChanged(d->state); + emit disconnected(); + if (d->pipeWriter) { + delete d->pipeWriter; + d->pipeWriter = 0; + } + d->dataNotifier.stop(); +} + +bool QLocalSocket::flush() +{ + Q_D(QLocalSocket); + if (d->pipeWriter) + return d->pipeWriter->waitForWrite(0); + return false; +} + +void QLocalSocket::disconnectFromServer() +{ + Q_D(QLocalSocket); + flush(); + if (d->pipeWriter && d->pipeWriter->bytesToWrite() != 0) { + d->state = QLocalSocket::ClosingState; + emit stateChanged(d->state); + } else { + close(); + } +} + +QLocalSocket::LocalSocketError QLocalSocket::error() const +{ + Q_D(const QLocalSocket); + return d->error; +} + +bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, + LocalSocketState socketState, OpenMode openMode) +{ + Q_D(QLocalSocket); + d->readBuffer.clear(); + QIODevice::open(openMode); + d->handle = (int*)socketDescriptor; + d->state = socketState; + emit stateChanged(d->state); + if (d->threadData->eventDispatcher) + d->dataNotifier.start(NOTIFYTIMEOUT); + return true; +} + +void QLocalSocketPrivate::_q_canWrite() +{ + Q_Q(QLocalSocket); + if (state == QLocalSocket::ClosingState) + q->close(); +} + +void QLocalSocketPrivate::_q_notified() +{ + Q_Q(QLocalSocket); + if (0 != bytesAvailable()) { + if (readBufferMaxSize == 0 || readBuffer.size() < readBufferMaxSize) { + if (!readFromSocket()) { + return; + } + // wait until buffer is cleared before starting again + if (readBufferMaxSize && readBuffer.size() == readBufferMaxSize) { + dataNotifier.stop(); + } + } + if (!readyReadEmitted) { + readyReadEmitted = true; + q->emit readyRead(); + } + } +} + +quintptr QLocalSocket::socketDescriptor() const +{ + Q_D(const QLocalSocket); + return (quintptr)d->handle; +} + +qint64 QLocalSocket::readBufferSize() const +{ + Q_D(const QLocalSocket); + return d->readBufferMaxSize; +} + +void QLocalSocket::setReadBufferSize(qint64 size) +{ + Q_D(QLocalSocket); + d->readBufferMaxSize = size; +} + +bool QLocalSocket::waitForConnected(int msecs) +{ + Q_UNUSED(msecs); + return (state() == ConnectedState); +} + +bool QLocalSocket::waitForDisconnected(int msecs) +{ + Q_D(QLocalSocket); + if (state() == UnconnectedState) + return false; + QIncrementalSleepTimer timer(msecs); + forever { + d->_q_notified(); + if (d->pipeClosed) + close(); + if (state() == UnconnectedState) + return true; + Sleep(timer.nextSleepTime()); + if (timer.hasTimedOut()) + break; + } + + return false; +} + +bool QLocalSocket::isValid() const +{ + Q_D(const QLocalSocket); + return (d->handle != INVALID_HANDLE_VALUE); +} + +bool QLocalSocket::waitForReadyRead(int msecs) +{ + Q_D(QLocalSocket); + QIncrementalSleepTimer timer(msecs); + forever { + d->_q_notified(); + if (bytesAvailable() > 0) { + if (!d->readyReadEmitted) { + d->readyReadEmitted = true; + emit readyRead(); + } + return true; + } + + Sleep(timer.nextSleepTime()); + if (timer.hasTimedOut()) + break; + } + + return false; +} + +bool QLocalSocket::waitForBytesWritten(int msecs) +{ + Q_D(const QLocalSocket); + if (!d->pipeWriter) + return false; + + QIncrementalSleepTimer timer(msecs); + forever { + if (d->pipeWriter->hadWritten()) + return true; + + if (d->pipeWriter->bytesToWrite() == 0) + return false; + + // Wait for the pipe writer to acknowledge that it has + // written. This will succeed if either the pipe writer has + // already written the data, or if it manages to write data + // within the given timeout. + if (d->pipeWriter->waitForWrite(0)) + return true; + + Sleep(timer.nextSleepTime()); + if (timer.hasTimedOut()) + break; + } + + return false; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp new file mode 100644 index 0000000000..b25b887c29 --- /dev/null +++ b/src/network/socket/qnativesocketengine.cpp @@ -0,0 +1,1140 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QNATIVESOCKETENGINE_DEBUG + +/*! \class QNativeSocketEngine + \internal + + \brief The QNativeSocketEngine class provides low level access to a socket. + + \reentrant + \ingroup io + \inmodule QtNetwork + + QtSocketLayer provides basic socket functionality provided by the + operating system. It also keeps track of what state the socket is + in, and which errors that occur. + + The classes QTcpSocket, QUdpSocket and QTcpServer provide a + higher level API, and are in general more useful for the common + application. + + There are two main ways of initializing the a QNativeSocketEngine; either + create a new socket by passing the socket type (TcpSocket or + UdpSocket) and network layer protocol (IPv4Protocol or + IPv6Protocol) to initialize(), or pass an existing socket + descriptor and have QNativeSocketEngine determine the type and protocol + itself. The native socket descriptor can later be fetched by + calling socketDescriptor(). The socket is made non-blocking, but + blocking behavior can still be achieved by calling waitForRead() + and waitForWrite(). isValid() can be called to check if the socket + has been successfully initialized and is ready to use. + + To connect to a host, determine its address and pass this and the + port number to connectToHost(). The socket can then be used as a + TCP or UDP client. Otherwise; bind(), listen() and accept() are + used to have the socket function as a TCP or UDP server. Call + close() to close the socket. + + bytesAvailable() is called to determine how much data is available + for reading. read() and write() are used by both TCP and UDP + clients to exchange data with the connected peer. UDP clients can + also call hasMoreDatagrams(), nextDatagramSize(), + readDatagram(), and writeDatagram(). + + Call state() to determine the state of the socket, for + example, ListeningState or ConnectedState. socketType() tells + whether the socket is a TCP socket or a UDP socket, or if the + socket type is unknown. protocol() is used to determine the + socket's network layer protocol. + + localAddress(), localPort() are called to find the address and + port that are currently bound to the socket. If the socket is + connected, peerAddress() and peerPort() determine the address and + port of the connected peer. + + Finally, if any function should fail, error() and + errorString() can be called to determine the cause of the error. +*/ + +#include <qabstracteventdispatcher.h> +#include <qsocketnotifier.h> + +#include "qnativesocketengine_p.h" +#include <private/qthread_p.h> +#include <private/qobject_p.h> + +#if !defined(QT_NO_NETWORKPROXY) +# include "qnetworkproxy.h" +# include "qabstractsocket.h" +# include "qtcpserver.h" +#endif + +QT_BEGIN_NAMESPACE + +//#define QNATIVESOCKETENGINE_DEBUG + +#define Q_VOID + +// Common constructs +#define Q_CHECK_VALID_SOCKETLAYER(function, returnValue) do { \ + if (!isValid()) { \ + qWarning(""#function" was called on an uninitialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_INVALID_SOCKETLAYER(function, returnValue) do { \ + if (isValid()) { \ + qWarning(""#function" was called on an already initialized socket device"); \ + return returnValue; \ + } } while (0) +#define Q_CHECK_STATE(function, checkState, returnValue) do { \ + if (d->socketState != (checkState)) { \ + qWarning(""#function" was not called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_NOT_STATE(function, checkState, returnValue) do { \ + if (d->socketState == (checkState)) { \ + qWarning(""#function" was called in "#checkState); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_STATES(function, state1, state2, returnValue) do { \ + if (d->socketState != (state1) && d->socketState != (state2)) { \ + qWarning(""#function" was called" \ + " not in "#state1" or "#state2); \ + return (returnValue); \ + } } while (0) +#define Q_CHECK_TYPE(function, type, returnValue) do { \ + if (d->socketType != (type)) { \ + qWarning(#function" was called by a" \ + " socket other than "#type""); \ + return (returnValue); \ + } } while (0) +#define Q_TR(a) QT_TRANSLATE_NOOP(QNativeSocketEngine, a) + +/*! \internal + Constructs the private class and initializes all data members. + + On Windows, WSAStartup is called "recursively" for every + concurrent QNativeSocketEngine. This is safe, because WSAStartup and + WSACleanup are reference counted. +*/ +QNativeSocketEnginePrivate::QNativeSocketEnginePrivate() +{ + socketDescriptor = -1; + readNotifier = 0; + writeNotifier = 0; + exceptNotifier = 0; +} + +/*! \internal + Destructs the private class. +*/ +QNativeSocketEnginePrivate::~QNativeSocketEnginePrivate() +{ +} + +/*! \internal + + Sets the error and error string if not set already. The only + interesting error is the first one that occurred, and not the last + one. +*/ +void QNativeSocketEnginePrivate::setError(QAbstractSocket::SocketError error, ErrorString errorString) const +{ + if (hasSetSocketError) { + // Only set socket errors once for one engine; expect the + // socket to recreate its engine after an error. Note: There's + // one exception: SocketError(11) bypasses this as it's purely + // a temporary internal error condition. + return; + } + if (error != QAbstractSocket::SocketError(11)) + hasSetSocketError = true; + + socketError = error; + + switch (errorString) { + case NonBlockingInitFailedErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Unable to initialize non-blocking socket")); + break; + case BroadcastingInitFailedErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Unable to initialize broadcast socket")); + break; + case NoIpV6ErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Attempt to use IPv6 socket on a platform with no IPv6 support")); + break; + case RemoteHostClosedErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "The remote host closed the connection")); + break; + case TimeOutErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Network operation timed out")); + break; + case ResourceErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Out of resources")); + break; + case OperationUnsupportedErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Unsupported socket operation")); + break; + case ProtocolUnsupportedErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Protocol type not supported")); + break; + case InvalidSocketErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Invalid socket descriptor")); + break; + case HostUnreachableErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Host unreachable")); + break; + case NetworkUnreachableErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Network unreachable")); + break; + case AccessErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Permission denied")); + break; + case ConnectionTimeOutErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Connection timed out")); + break; + case ConnectionRefusedErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Connection refused")); + break; + case AddressInuseErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "The bound address is already in use")); + break; + case AddressNotAvailableErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "The address is not available")); + break; + case AddressProtectedErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "The address is protected")); + break; + case DatagramTooLargeErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Datagram was too large to send")); + break; + case SendDatagramErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Unable to send a message")); + break; + case ReceiveDatagramErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Unable to receive a message")); + break; + case WriteErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Unable to write")); + break; + case ReadErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Network error")); + break; + case PortInuseErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Another socket is already listening on the same port")); + break; + case NotSocketErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Operation on non-socket")); + break; + case InvalidProxyTypeString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "The proxy type is invalid for this operation")); + break; + case UnknownSocketErrorString: + socketErrorString = QLatin1String(QT_TRANSLATE_NOOP("QNativeSocketEngine", "Unknown error")); + break; + } +} + +bool QNativeSocketEnginePrivate::checkProxy(const QHostAddress &address) +{ + if (address == QHostAddress::LocalHost || address == QHostAddress::LocalHostIPv6) + return true; + +#if !defined(QT_NO_NETWORKPROXY) + QObject *parent = q_func()->parent(); + QNetworkProxy proxy; + if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(parent)) { + proxy = socket->proxy(); + } else if (QTcpServer *server = qobject_cast<QTcpServer *>(parent)) { + proxy = server->proxy(); + } else { + // no parent -> no proxy + return true; + } + + if (proxy.type() == QNetworkProxy::DefaultProxy) + proxy = QNetworkProxy::applicationProxy(); + + if (proxy.type() != QNetworkProxy::DefaultProxy && + proxy.type() != QNetworkProxy::NoProxy) { + // QNativeSocketEngine doesn't do proxies + setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::InvalidProxyTypeString); + return false; + } +#endif + + return true; +} + +/*! + Constructs a QNativeSocketEngine. + + \sa initialize() +*/ +QNativeSocketEngine::QNativeSocketEngine(QObject *parent) + : QAbstractSocketEngine(*new QNativeSocketEnginePrivate(), parent) +{ +} + +/*! + Destructs a QNativeSocketEngine. +*/ +QNativeSocketEngine::~QNativeSocketEngine() +{ + close(); +} + +/*! + Initializes a QNativeSocketEngine by creating a new socket of type \a + socketType and network layer protocol \a protocol. Returns true on + success; otherwise returns false. + + If the socket was already initialized, this function closes the + socket before reeinitializing it. + + The new socket is non-blocking, and for UDP sockets it's also + broadcast enabled. +*/ +bool QNativeSocketEngine::initialize(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QNativeSocketEngine); + if (isValid()) + close(); + +#if defined(QT_NO_IPV6) + if (protocol == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + + // Create the socket + if (!d->createNewSocket(socketType, protocol)) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString typeStr = QLatin1String("UnknownSocketType"); + if (socketType == QAbstractSocket::TcpSocket) typeStr = QLatin1String("TcpSocket"); + else if (socketType == QAbstractSocket::UdpSocket) typeStr = QLatin1String("UdpSocket"); + QString protocolStr = QLatin1String("UnknownProtocol"); + if (protocol == QAbstractSocket::IPv4Protocol) protocolStr = QLatin1String("IPv4Protocol"); + else if (protocol == QAbstractSocket::IPv6Protocol) protocolStr = QLatin1String("IPv6Protocol"); + qDebug("QNativeSocketEngine::initialize(type == %s, protocol == %s) failed: %s", + typeStr.toLatin1().constData(), protocolStr.toLatin1().constData(), d->socketErrorString.toLatin1().constData()); +#endif + return false; + } + + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); + close(); + return false; + } + + // Make sure we receive out-of-band data + if (socketType == QAbstractSocket::TcpSocket + && !setOption(ReceiveOutOfBandData, 1)) { + qWarning("QNativeSocketEngine::initialize unable to inline out-of-band data"); + } + + // Set the send and receive buffer sizes to a magic size, found + // most optimal for our platforms. + setReceiveBufferSize(49152); + setSendBufferSize(49152); + + d->socketType = socketType; + d->socketProtocol = protocol; + return true; +} + +/*! \overload + + Initializes the socket using \a socketDescriptor instead of + creating a new one. The socket type and network layer protocol are + determined automatically. The socket's state is set to \a + socketState. + + If the socket type is either TCP or UDP, it is made non-blocking. + UDP sockets are also broadcast enabled. + */ +bool QNativeSocketEngine::initialize(int socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QNativeSocketEngine); + + if (isValid()) + close(); + + d->socketDescriptor = socketDescriptor; + + // determine socket type and protocol + if (!d->fetchConnectionParameters()) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEngine::initialize(socketDescriptor == %i) failed: %s", + socketDescriptor, d->socketErrorString.toLatin1().constData()); +#endif + d->socketDescriptor = -1; + return false; + } + + if (d->socketType != QAbstractSocket::UnknownSocketType) { + // Make the socket nonblocking. + if (!setOption(NonBlockingSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NonBlockingInitFailedErrorString); + close(); + return false; + } + + // Set the broadcasting flag if it's a UDP socket. + if (d->socketType == QAbstractSocket::UdpSocket + && !setOption(BroadcastSocketOption, 1)) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::BroadcastingInitFailedErrorString); + close(); + return false; + } + } + + d->socketState = socketState; + return true; +} + +/*! + Returns true if the socket is valid; otherwise returns false. A + socket is valid if it has not been successfully initialized, or if + it has been closed. +*/ +bool QNativeSocketEngine::isValid() const +{ + Q_D(const QNativeSocketEngine); + return d->socketDescriptor != -1; +} + +/*! + Returns the native socket descriptor. Any use of this descriptor + stands the risk of being non-portable. +*/ +int QNativeSocketEngine::socketDescriptor() const +{ + Q_D(const QNativeSocketEngine); + return d->socketDescriptor; +} + +/*! + Connects to the IP address and port specified by \a address and \a + port. If the connection is established, this function returns true + and the socket enters ConnectedState. Otherwise, false is + returned. + + If false is returned, state() should be called to see if the + socket is in ConnectingState. If so, a delayed TCP connection is + taking place, and connectToHost() must be called again later to + determine if the connection was established successfully or + not. The second connection attempt must be made when the socket is + ready for writing. This state can be determined either by + connecting a QSocketNotifier to the socket descriptor returned by + socketDescriptor(), or by calling the blocking function + waitForWrite(). + + Example: + \snippet doc/src/snippets/code/src_network_socket_qnativesocketengine.cpp 0 + + Otherwise, error() should be called to determine the cause of the + error. +*/ +bool QNativeSocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::connectToHost(), false); + +#if defined (QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + if (!d->checkProxy(address)) + return false; + + Q_CHECK_STATES(QNativeSocketEngine::connectToHost(), + QAbstractSocket::UnconnectedState, QAbstractSocket::ConnectingState, false); + + d->peerAddress = address; + d->peerPort = port; + bool connected = d->nativeConnect(address, port); + if (connected) + d->fetchConnectionParameters(); + + return connected; +} + +/*! + If there's a connection activity on the socket, process it. Then + notify our parent if there really was activity. +*/ +void QNativeSocketEngine::connectionNotification() +{ + Q_D(QNativeSocketEngine); + Q_ASSERT(state() == QAbstractSocket::ConnectingState); + + connectToHost(d->peerAddress, d->peerPort); + if (state() != QAbstractSocket::ConnectingState) { + // we changed states + QAbstractSocketEngine::connectionNotification(); + } +} + +/*! + Connects to the remote host name given by \a name on port \a + port. When this function is called, the upper-level will not + perform a hostname lookup. + + The native socket engine does not support this operation, + but some other socket engines (notably proxy-based ones) do. +*/ +bool QNativeSocketEngine::connectToHostByName(const QString &name, quint16 port) +{ + Q_UNUSED(name); + Q_UNUSED(port); + Q_D(QNativeSocketEngine); + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::OperationUnsupportedErrorString); + return false; +} + +/*! + Binds the socket to the address \a address and port \a + port. Returns true on success; otherwise false is returned. The + port may be 0, in which case an arbitrary unused port is assigned + automatically by the operating system. + + Servers call this function to set up the server's address and + port. TCP servers must in addition call listen() after bind(). +*/ +bool QNativeSocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::bind(), false); + +#if defined (QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + d->setError(QAbstractSocket::UnsupportedSocketOperationError, + QNativeSocketEnginePrivate::NoIpV6ErrorString); + return false; + } +#endif + if (!d->checkProxy(address)) + return false; + + Q_CHECK_STATE(QNativeSocketEngine::bind(), QAbstractSocket::UnconnectedState, false); + + if (!d->nativeBind(address, port)) + return false; + + d->fetchConnectionParameters(); + return true; +} + +/*! + Prepares a TCP server for accepting incoming connections. This + function must be called after bind(), and only by TCP sockets. + + After this function has been called, pending client connections + are detected by checking if the socket is ready for reading. This + can be done by either creating a QSocketNotifier, passing the + socket descriptor returned by socketDescriptor(), or by calling + the blocking function waitForRead(). + + Example: + \snippet doc/src/snippets/code/src_network_socket_qnativesocketengine.cpp 1 + + \sa bind(), accept() +*/ +bool QNativeSocketEngine::listen() +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::listen(), false); + Q_CHECK_STATE(QNativeSocketEngine::listen(), QAbstractSocket::BoundState, false); + Q_CHECK_TYPE(QNativeSocketEngine::listen(), QAbstractSocket::TcpSocket, false); + + // We're using a backlog of 50. Most modern kernels support TCP + // syncookies by default, and if they do, the backlog is ignored. + // When there is no support for TCP syncookies, this value is + // fine. + return d->nativeListen(50); +} + +/*! + Accepts a pending connection from the socket, which must be in + ListeningState, and returns its socket descriptor. If no pending + connections are available, -1 is returned. + + \sa bind(), listen() +*/ +int QNativeSocketEngine::accept() +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::accept(), -1); + Q_CHECK_STATE(QNativeSocketEngine::accept(), QAbstractSocket::ListeningState, false); + Q_CHECK_TYPE(QNativeSocketEngine::accept(), QAbstractSocket::TcpSocket, false); + + return d->nativeAccept(); +} + +/*! + Returns the number of bytes that are currently available for + reading. On error, -1 is returned. + + For UDP sockets, this function returns the accumulated size of all + pending datagrams, and it is therefore more useful for UDP sockets + to call hasPendingDatagrams() and pendingDatagramSize(). +*/ +qint64 QNativeSocketEngine::bytesAvailable() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::bytesAvailable(), -1); + Q_CHECK_NOT_STATE(QNativeSocketEngine::bytesAvailable(), QAbstractSocket::UnconnectedState, false); + + return d->nativeBytesAvailable(); +} + +/*! + Returns true if there is at least one datagram pending. This + function is only called by UDP sockets, where a datagram can have + a size of 0. TCP sockets call bytesAvailable(). +*/ +bool QNativeSocketEngine::hasPendingDatagrams() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::hasPendingDatagrams(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::hasPendingDatagrams(), QAbstractSocket::UnconnectedState, false); + Q_CHECK_TYPE(QNativeSocketEngine::hasPendingDatagrams(), QAbstractSocket::UdpSocket, false); + + return d->nativeHasPendingDatagrams(); +} + +/*! + Returns the size of the pending datagram, or -1 if no datagram is + pending. A datagram size of 0 is perfectly valid. This function is + called by UDP sockets before receiveMessage(). For TCP sockets, + call bytesAvailable(). +*/ +qint64 QNativeSocketEngine::pendingDatagramSize() const +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::pendingDatagramSize(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::pendingDatagramSize(), QAbstractSocket::UdpSocket, false); + + return d->nativePendingDatagramSize(); +} + +/*! + Reads up to \a maxSize bytes of a datagram from the socket, + stores it in \a data and returns the number of bytes read. The + address and port of the sender are stored in \a address and \a + port. If either of these pointers is 0, the corresponding value is + discarded. + + To avoid unnecessarily loss of data, call pendingDatagramSize() to + determine the size of the pending message before reading it. If \a + maxSize is too small, the rest of the datagram will be lost. + + Returns -1 if an error occurred. + + \sa hasPendingDatagrams() +*/ +qint64 QNativeSocketEngine::readDatagram(char *data, qint64 maxSize, QHostAddress *address, + quint16 *port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::readDatagram(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::readDatagram(), QAbstractSocket::UdpSocket, false); + + return d->nativeReceiveDatagram(data, maxSize, address, port); +} + +/*! + Writes a UDP datagram of size \a size bytes to the socket from + \a data to the address \a host on port \a port, and returns the + number of bytes written, or -1 if an error occurred. + + Only one datagram is sent, and if there is too much data to fit + into a single datagram, the operation will fail and error() + will return QAbstractSocket::DatagramTooLargeError. Operating systems impose an + upper limit to the size of a datagram, but this size is different + on almost all platforms. Sending large datagrams is in general + disadvised, as even if they are sent successfully, they are likely + to be fragmented before arriving at their destination. + + Experience has shown that it is in general safe to send datagrams + no larger than 512 bytes. + + \sa readDatagram() +*/ +qint64 QNativeSocketEngine::writeDatagram(const char *data, qint64 size, + const QHostAddress &host, quint16 port) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::writeDatagram(), -1); + Q_CHECK_TYPE(QNativeSocketEngine::writeDatagram(), QAbstractSocket::UdpSocket, -1); + return d->nativeSendDatagram(data, size, host, port); +} + +/*! + Writes a block of \a size bytes from \a data to the socket. + Returns the number of bytes written, or -1 if an error occurred. +*/ +qint64 QNativeSocketEngine::write(const char *data, qint64 size) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::write(), -1); + Q_CHECK_STATE(QNativeSocketEngine::write(), QAbstractSocket::ConnectedState, -1); + return d->nativeWrite(data, size); +} + +/*! + Reads up to \a maxSize bytes into \a data from the socket. + Returns the number of bytes read, or -1 if an error occurred. +*/ +qint64 QNativeSocketEngine::read(char *data, qint64 maxSize) +{ + Q_D(QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::read(), -1); + Q_CHECK_STATES(QNativeSocketEngine::read(), QAbstractSocket::ConnectedState, QAbstractSocket::BoundState, -1); + + qint64 readBytes = d->nativeRead(data, maxSize); + + // Handle remote close + if (readBytes == 0 && d->socketType == QAbstractSocket::TcpSocket) { + d->setError(QAbstractSocket::RemoteHostClosedError, + QNativeSocketEnginePrivate::RemoteHostClosedErrorString); + close(); + return -1; + } + return readBytes; +} + +/*! + Closes the socket. In order to use the socket again, initialize() + must be called. +*/ +void QNativeSocketEngine::close() +{ + Q_D(QNativeSocketEngine); + if (d->readNotifier) + d->readNotifier->setEnabled(false); + if (d->writeNotifier) + d->writeNotifier->setEnabled(false); + if (d->exceptNotifier) + d->exceptNotifier->setEnabled(false); + + if(d->socketDescriptor != -1) { + d->nativeClose(); + d->socketDescriptor = -1; + } + d->socketState = QAbstractSocket::UnconnectedState; + d->hasSetSocketError = false; + d->localPort = 0; + d->localAddress.clear(); + d->peerPort = 0; + d->peerAddress.clear(); + if (d->readNotifier) { + qDeleteInEventHandler(d->readNotifier); + d->readNotifier = 0; + } + if (d->writeNotifier) { + qDeleteInEventHandler(d->writeNotifier); + d->writeNotifier = 0; + } + if (d->exceptNotifier) { + qDeleteInEventHandler(d->exceptNotifier); + d->exceptNotifier = 0; + } +} + +/*! + Waits for \a msecs milliseconds or until the socket is ready for + reading. If \a timedOut is not 0 and \a msecs milliseconds have + passed, the value of \a timedOut is set to true. + + Returns true if data is available for reading; otherwise returns + false. + + This is a blocking function call; its use is disadvised in a + single threaded application, as the whole thread will stop + responding until the function returns. waitForRead() is most + useful when there is no event loop available. The general approach + is to create a QSocketNotifier, passing the socket descriptor + returned by socketDescriptor() to its constructor. +*/ +bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForRead(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForRead(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, true); + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +/*! + Waits for \a msecs milliseconds or until the socket is ready for + writing. If \a timedOut is not 0 and \a msecs milliseconds have + passed, the value of \a timedOut is set to true. + + Returns true if data is available for writing; otherwise returns + false. + + This is a blocking function call; its use is disadvised in a + single threaded application, as the whole thread will stop + responding until the function returns. waitForWrite() is most + useful when there is no event loop available. The general approach + is to create a QSocketNotifier, passing the socket descriptor + returned by socketDescriptor() to its constructor. +*/ +bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForWrite(), + QAbstractSocket::UnconnectedState, false); + + if (timedOut) + *timedOut = false; + + int ret = d->nativeSelect(msecs, false); + // On Windows, the socket is in connected state if a call to + // select(writable) is successful. In this case we should not + // issue a second call to WSAConnect() +#if defined (Q_WS_WIN) + if (ret > 0) { + setState(QAbstractSocket::ConnectedState); + d_func()->fetchConnectionParameters(); + return true; + } +#endif + + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +bool QNativeSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_D(const QNativeSocketEngine); + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false); + Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForReadOrWrite(), + QAbstractSocket::UnconnectedState, false); + + int ret = d->nativeSelect(msecs, checkRead, checkWrite, readyToRead, readyToWrite); + // On Windows, the socket is in connected state if a call to + // select(writable) is successful. In this case we should not + // issue a second call to WSAConnect() +#if defined (Q_WS_WIN) + if (checkWrite && ((readyToWrite && *readyToWrite) || !readyToWrite) && ret > 0) { + setState(QAbstractSocket::ConnectedState); + d_func()->fetchConnectionParameters(); + return true; + } +#endif + if (ret == 0) { + if (timedOut) + *timedOut = true; + d->setError(QAbstractSocket::SocketTimeoutError, + QNativeSocketEnginePrivate::TimeOutErrorString); + return false; + } else if (state() == QAbstractSocket::ConnectingState) { + connectToHost(d->peerAddress, d->peerPort); + } + + return ret > 0; +} + +/*! + Returns the size of the operating system's socket receive + buffer. Depending on the operating system, this size may be + different from what has been set earlier with + setReceiveBufferSize(). +*/ +qint64 QNativeSocketEngine::receiveBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::receiveBufferSize(), -1); + return option(ReceiveBufferSocketOption); +} + +/*! + Sets the size of the operating system receive buffer to \a size. + + For clients, this should be set before connectToHost() is called; + otherwise it will have no effect. For servers, it should be called + before listen(). + + The operating system receive buffer size effectively limits two + things: how much data can be in transit at any one moment, and how + much data can be received in one iteration of the main event loop. + Setting the size of the receive buffer may have an impact on the + socket's performance. + + The default value is operating system-dependent. +*/ +void QNativeSocketEngine::setReceiveBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setReceiveBufferSize(), Q_VOID); + setOption(ReceiveBufferSocketOption, size); +} + +/*! + Returns the size of the operating system send buffer. Depending on + the operating system, this size may be different from what has + been set earlier with setSendBufferSize(). +*/ +qint64 QNativeSocketEngine::sendBufferSize() const +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setSendBufferSize(), -1); + return option(SendBufferSocketOption); +} + +/*! + Sets the size of the operating system send buffer to \a size. + + The operating system send buffer size effectively limits how much + data can be in transit at any one moment. Setting the size of the + send buffer may have an impact on the socket's performance. + + The default value is operating system-dependent. +*/ +void QNativeSocketEngine::setSendBufferSize(qint64 size) +{ + Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::setSendBufferSize(), Q_VOID); + setOption(SendBufferSocketOption, size); +} + + +/*! + Sets the option \a option to the value \a value. +*/ +bool QNativeSocketEngine::setOption(SocketOption option, int value) +{ + Q_D(QNativeSocketEngine); + return d->setOption(option, value); +} + +/*! + Returns the value of the option \a socketOption. +*/ +int QNativeSocketEngine::option(SocketOption socketOption) const +{ + Q_D(const QNativeSocketEngine); + return d->option(socketOption); +} + +bool QNativeSocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->readNotifier && d->readNotifier->isEnabled(); +} + +/* + \internal + \class QReadNotifier + \brief The QReadNotifer class is used to improve performance. + + QReadNotifier is a private class used for performance reasons vs + connecting to the QSocketNotifier activated() signal. + */ +class QReadNotifier : public QSocketNotifier +{ +public: + QReadNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Read, parent) + { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QReadNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + engine->readNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +/* + \internal + \class QWriteNotifier + \brief The QWriteNotifer class is used to improve performance. + + QWriteNotifier is a private class used for performance reasons vs + connecting to the QSocketNotifier activated() signal. + */ +class QWriteNotifier : public QSocketNotifier +{ +public: + QWriteNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Write, parent) { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QWriteNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + if (engine->state() == QAbstractSocket::ConnectingState) + engine->connectionNotification(); + else + engine->writeNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +class QExceptionNotifier : public QSocketNotifier +{ +public: + QExceptionNotifier(int fd, QNativeSocketEngine *parent) + : QSocketNotifier(fd, QSocketNotifier::Exception, parent) { engine = parent; } + +protected: + bool event(QEvent *); + + QNativeSocketEngine *engine; +}; + +bool QExceptionNotifier::event(QEvent *e) +{ + if (e->type() == QEvent::SockAct) { + engine->exceptionNotification(); + return true; + } + return QSocketNotifier::event(e); +} + +void QNativeSocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->readNotifier) { + d->readNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->readNotifier = new QReadNotifier(d->socketDescriptor, this); + d->readNotifier->setEnabled(true); + } +} + +bool QNativeSocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->writeNotifier && d->writeNotifier->isEnabled(); +} + +void QNativeSocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->writeNotifier) { + d->writeNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->writeNotifier = new QWriteNotifier(d->socketDescriptor, this); + d->writeNotifier->setEnabled(true); + } +} + +bool QNativeSocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QNativeSocketEngine); + return d->exceptNotifier && d->exceptNotifier->isEnabled(); +} + +void QNativeSocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QNativeSocketEngine); + if (d->exceptNotifier) { + d->exceptNotifier->setEnabled(enable); + } else if (enable && d->threadData->eventDispatcher) { + d->exceptNotifier = new QExceptionNotifier(d->socketDescriptor, this); + d->exceptNotifier->setEnabled(true); + } +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine_p.h b/src/network/socket/qnativesocketengine_p.h new file mode 100644 index 0000000000..3366f2de9a --- /dev/null +++ b/src/network/socket/qnativesocketengine_p.h @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNATIVESOCKETENGINE_P_H +#define QNATIVESOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include "QtNetwork/qhostaddress.h" +#include "private/qabstractsocketengine_p.h" +#ifndef Q_OS_WIN +#include "qplatformdefs.h" +#endif + +QT_BEGIN_NAMESPACE + +#ifndef Q_OS_WIN +// Almost always the same. If not, specify in qplatformdefs.h. +#if !defined(QT_SOCKOPTLEN_T) +# define QT_SOCKOPTLEN_T QT_SOCKLEN_T +#endif + +// Tru64 redefines accept -> _accept with _XOPEN_SOURCE_EXTENDED +static inline int qt_socket_accept(int s, struct sockaddr *addr, QT_SOCKLEN_T *addrlen) +{ return ::accept(s, addr, static_cast<QT_SOCKLEN_T *>(addrlen)); } +#if defined(accept) +# undef accept +#endif + +// UnixWare 7 redefines listen -> _listen +static inline int qt_socket_listen(int s, int backlog) +{ return ::listen(s, backlog); } +#if defined(listen) +# undef listen +#endif + +// UnixWare 7 redefines socket -> _socket +static inline int qt_socket_socket(int domain, int type, int protocol) +{ return ::socket(domain, type, protocol); } +#if defined(socket) +# undef socket +#endif + +#endif + +class QNativeSocketEnginePrivate; + +class Q_AUTOTEST_EXPORT QNativeSocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + QNativeSocketEngine(QObject *parent = 0); + ~QNativeSocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + + qint64 receiveBufferSize() const; + void setReceiveBufferSize(qint64 bufferSize); + + qint64 sendBufferSize() const; + void setSendBufferSize(qint64 bufferSize); + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +public Q_SLOTS: + // non-virtual override; + void connectionNotification(); + +private: + Q_DECLARE_PRIVATE(QNativeSocketEngine) + Q_DISABLE_COPY(QNativeSocketEngine) +}; + +#ifdef Q_OS_WIN +class QWindowsSockInit +{ +public: + QWindowsSockInit(); + ~QWindowsSockInit(); + int version; +}; +#endif + +class QSocketNotifier; + +class QNativeSocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QNativeSocketEngine) +public: + QNativeSocketEnginePrivate(); + ~QNativeSocketEnginePrivate(); + + int socketDescriptor; + + QSocketNotifier *readNotifier, *writeNotifier, *exceptNotifier; + +#ifdef Q_OS_WIN + QWindowsSockInit winSock; +#endif + + enum ErrorString { + NonBlockingInitFailedErrorString, + BroadcastingInitFailedErrorString, + NoIpV6ErrorString, + RemoteHostClosedErrorString, + TimeOutErrorString, + ResourceErrorString, + OperationUnsupportedErrorString, + ProtocolUnsupportedErrorString, + InvalidSocketErrorString, + HostUnreachableErrorString, + NetworkUnreachableErrorString, + AccessErrorString, + ConnectionTimeOutErrorString, + ConnectionRefusedErrorString, + AddressInuseErrorString, + AddressNotAvailableErrorString, + AddressProtectedErrorString, + DatagramTooLargeErrorString, + SendDatagramErrorString, + ReceiveDatagramErrorString, + WriteErrorString, + ReadErrorString, + PortInuseErrorString, + NotSocketErrorString, + InvalidProxyTypeString, + + UnknownSocketErrorString = -1 + }; + + void setError(QAbstractSocket::SocketError error, ErrorString errorString) const; + + // native functions + int option(QNativeSocketEngine::SocketOption option) const; + bool setOption(QNativeSocketEngine::SocketOption option, int value); + + bool createNewSocket(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol); + + bool nativeConnect(const QHostAddress &address, quint16 port); + bool nativeBind(const QHostAddress &address, quint16 port); + bool nativeListen(int backlog); + int nativeAccept(); + qint64 nativeBytesAvailable() const; + + bool nativeHasPendingDatagrams() const; + qint64 nativePendingDatagramSize() const; + qint64 nativeReceiveDatagram(char *data, qint64 maxLength, + QHostAddress *address, quint16 *port); + qint64 nativeSendDatagram(const char *data, qint64 length, + const QHostAddress &host, quint16 port); + qint64 nativeRead(char *data, qint64 maxLength); + qint64 nativeWrite(const char *data, qint64 length); + int nativeSelect(int timeout, bool selectForRead) const; + int nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const; + + void nativeClose(); + + bool checkProxy(const QHostAddress &address); + bool fetchConnectionParameters(); +}; + +QT_END_NAMESPACE + +#endif // QNATIVESOCKETENGINE_P_H diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp new file mode 100644 index 0000000000..534f7ecf70 --- /dev/null +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -0,0 +1,949 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QNATIVESOCKETENGINE_DEBUG + +#include "qnativesocketengine_p.h" +#include "qiodevice.h" +#include "qhostaddress.h" +#include "qvarlengtharray.h" +#include "qdatetime.h" +#include <time.h> +#include <errno.h> +#include <fcntl.h> +#ifndef QT_NO_IPV6IFNAME +#include <net/if.h> +#endif +#ifndef QT_NO_IPV6IFNAME +#include <net/if.h> +#endif +#ifdef QT_LINUXBASE +#include <arpa/inet.h> +#endif + +#if defined QNATIVESOCKETENGINE_DEBUG +#include <qstring.h> +#include <ctype.h> +#endif + +QT_BEGIN_NAMESPACE + +#if defined QNATIVESOCKETENGINE_DEBUG + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(c)) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1(); + } + } + + if (len < maxSize) + out += "..."; + + return out; +} +#endif + +static void qt_ignore_sigpipe() +{ + // Set to ignore SIGPIPE once only. + static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0); + if (atom.testAndSetRelaxed(0, 1)) { + struct sigaction noaction; + memset(&noaction, 0, sizeof(noaction)); + noaction.sa_handler = SIG_IGN; + ::sigaction(SIGPIPE, &noaction, 0); + } +} + +/* + Extracts the port and address from a sockaddr, and stores them in + \a port and \a addr if they are non-null. +*/ +static inline void qt_socket_getPortAndAddress(struct sockaddr *sa, quint16 *port, QHostAddress *addr) +{ +#if !defined(QT_NO_IPV6) + if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)sa; + Q_IPV6ADDR tmp; + memcpy(&tmp, &sa6->sin6_addr.s6_addr, sizeof(tmp)); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(tmp); + *addr = tmpAddress; +#ifndef QT_NO_IPV6IFNAME + char scopeid[IFNAMSIZ]; + if (::if_indextoname(sa6->sin6_scope_id, scopeid) > 0) { + addr->setScopeId(QLatin1String(scopeid)); + } else +#endif + addr->setScopeId(QString::number(sa6->sin6_scope_id)); + } + if (port) + *port = ntohs(sa6->sin6_port); + return; + } +#endif + struct sockaddr_in *sa4 = (struct sockaddr_in *)sa; + if (port) + *port = ntohs(sa4->sin_port); + if (addr) { + QHostAddress tmpAddress; + tmpAddress.setAddress(ntohl(sa4->sin_addr.s_addr)); + *addr = tmpAddress; + } +} + +/*! \internal + + Creates and returns a new socket descriptor of type \a socketType + and \a socketProtocol. Returns -1 on failure. +*/ +bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, + QAbstractSocket::NetworkLayerProtocol socketProtocol) +{ +#ifndef QT_NO_IPV6 + int protocol = (socketProtocol == QAbstractSocket::IPv6Protocol) ? AF_INET6 : AF_INET; +#else + Q_UNUSED(socketProtocol); + int protocol = AF_INET; +#endif + int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM; + int socket = qt_socket_socket(protocol, type, 0); + + if (socket <= 0) { + switch (errno) { + case EPROTONOSUPPORT: + case EAFNOSUPPORT: + case EINVAL: + setError(QAbstractSocket::UnsupportedSocketOperationError, ProtocolUnsupportedErrorString); + break; + case ENFILE: + case EMFILE: + case ENOBUFS: + case ENOMEM: + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + case EACCES: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + break; + default: + break; + } + + return false; + } + + // Ensure that the socket is closed on exec*(). + ::fcntl(socket, F_SETFD, FD_CLOEXEC); + socketDescriptor = socket; + return true; +} + +/* + Returns the value of the socket option \a opt. +*/ +int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) const +{ + Q_Q(const QNativeSocketEngine); + if (!q->isValid()) + return -1; + + int n = -1; + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::NonBlockingSocketOption: + break; + case QNativeSocketEngine::BroadcastSocketOption: + break; + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + return true; + case QNativeSocketEngine::ReceiveOutOfBandData: + n = SO_OOBINLINE; + break; + } + + int v = -1; + QT_SOCKOPTLEN_T len = sizeof(v); + if (getsockopt(socketDescriptor, SOL_SOCKET, n, (char *) &v, &len) != -1) + return v; + return -1; +} + + +/* + Sets the socket option \a opt to \a v. +*/ +bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt, int v) +{ + Q_Q(QNativeSocketEngine); + if (!q->isValid()) + return false; + + int n = 0; + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + case QNativeSocketEngine::NonBlockingSocketOption: { + // Make the socket nonblocking. + int flags = ::fcntl(socketDescriptor, F_GETFL, 0); + if (flags == -1) { +#ifdef QNATIVESOCKETENGINE_DEBUG + perror("QNativeSocketEnginePrivate::setOption(): fcntl(F_GETFL) failed"); +#endif + return false; + } + if (::fcntl(socketDescriptor, F_SETFL, flags | O_NONBLOCK) == -1) { +#ifdef QNATIVESOCKETENGINE_DEBUG + perror("QNativeSocketEnginePrivate::setOption(): fcntl(F_SETFL) failed"); +#endif + return false; + } + + return true; + } + case QNativeSocketEngine::AddressReusable: +#ifdef SO_REUSEPORT + n = SO_REUSEPORT; +#else + n = SO_REUSEADDR; +#endif + break; + case QNativeSocketEngine::BindExclusively: + return true; + case QNativeSocketEngine::ReceiveOutOfBandData: + n = SO_OOBINLINE; + break; + } + + return ::setsockopt(socketDescriptor, SOL_SOCKET, n, (char *) &v, sizeof(v)) == 0; +} + +bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &addr, quint16 port) +{ + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + + if (addr.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); +#ifndef QT_NO_IPV6IFNAME + sockAddrIPv6.sin6_scope_id = ::if_nametoindex(addr.scopeId().toLatin1().data()); +#else + sockAddrIPv6.sin6_scope_id = addr.scopeId().toInt(); +#endif + Q_IPV6ADDR ip6 = addr.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &ip6, sizeof(ip6)); + + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv6; + } else +#if 0 + {} +#endif +#endif + if (addr.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(addr.toIPv4Address()); + + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv4; + } else { + // unreachable + } + + int connectResult = QT_SOCKET_CONNECT(socketDescriptor, sockAddrPtr, sockAddrSize); + if (connectResult == -1) { + switch (errno) { + case EISCONN: + socketState = QAbstractSocket::ConnectedState; + break; + case ECONNREFUSED: + case EINVAL: + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case ETIMEDOUT: + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + break; + case EHOSTUNREACH: + setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case ENETUNREACH: + setError(QAbstractSocket::NetworkError, NetworkUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case EADDRINUSE: + setError(QAbstractSocket::NetworkError, AddressInuseErrorString); + break; + case EINPROGRESS: + case EALREADY: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + socketState = QAbstractSocket::ConnectingState; + break; + case EAGAIN: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + case EACCES: + case EPERM: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case EAFNOSUPPORT: + case EBADF: + case EFAULT: + case ENOTSOCK: + socketState = QAbstractSocket::UnconnectedState; + default: + break; + } + + if (socketState != QAbstractSocket::ConnectedState) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == false (%s)", + addr.toString().toLatin1().constData(), port, + socketState == QAbstractSocket::ConnectingState + ? "Connection in progress" : socketErrorString.toLatin1().constData()); +#endif + return false; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == true", + addr.toString().toLatin1().constData(), port); +#endif + + socketState = QAbstractSocket::ConnectedState; + return true; +} + +bool QNativeSocketEnginePrivate::nativeBind(const QHostAddress &address, quint16 port) +{ + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); +#ifndef QT_NO_IPV6IFNAME + sockAddrIPv6.sin6_scope_id = ::if_nametoindex(address.scopeId().toLatin1().data()); +#else + sockAddrIPv6.sin6_scope_id = address.scopeId().toInt(); +#endif + Q_IPV6ADDR tmp = address.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &tmp, sizeof(tmp)); + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv6; + } else +#endif + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(address.toIPv4Address()); + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *) &sockAddrIPv4; + } else { + // unreachable + } + + int bindResult = QT_SOCKET_BIND(socketDescriptor, sockAddrPtr, sockAddrSize); + if (bindResult < 0) { + switch(errno) { + case EADDRINUSE: + setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString); + break; + case EACCES: + setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString); + break; + case EINVAL: + setError(QAbstractSocket::UnsupportedSocketOperationError, OperationUnsupportedErrorString); + break; + case EADDRNOTAVAIL: + setError(QAbstractSocket::SocketAddressNotAvailableError, AddressNotAvailableErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, socketErrorString.toLatin1().constData()); +#endif + + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + socketState = QAbstractSocket::BoundState; + return true; +} + +bool QNativeSocketEnginePrivate::nativeListen(int backlog) +{ + if (qt_socket_listen(socketDescriptor, backlog) < 0) { + switch (errno) { + case EADDRINUSE: + setError(QAbstractSocket::AddressInUseError, + PortInuseErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == false (%s)", + backlog, socketErrorString.toLatin1().constData()); +#endif + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == true", backlog); +#endif + + socketState = QAbstractSocket::ListeningState; + return true; +} + +int QNativeSocketEnginePrivate::nativeAccept() +{ + int acceptedDescriptor = qt_socket_accept(socketDescriptor, 0, 0); +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeAccept() == %i", acceptedDescriptor); +#endif + // Ensure that the socket is closed on exec*() + ::fcntl(acceptedDescriptor, F_SETFD, FD_CLOEXEC); + return acceptedDescriptor; +} + +qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const +{ + /* + Apparently, there is not consistency among different operating + systems on how to use FIONREAD. + + FreeBSD, Linux and Solaris all expect the 3rd argument to + ioctl() to be an int, which is normally 32-bit even on 64-bit + machines. + + IRIX, on the other hand, expects a size_t, which is 64-bit on + 64-bit machines. + + So, the solution is to use size_t initialized to zero to make + sure all bits are set to zero, preventing underflow with the + FreeBSD/Linux/Solaris ioctls. + */ + size_t nbytes = 0; + // gives shorter than true amounts on Unix domain sockets. + qint64 available = 0; + if (::ioctl(socketDescriptor, FIONREAD, (char *) &nbytes) >= 0) + available = (qint64) *((int *) &nbytes); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBytesAvailable() == %lli", available); +#endif + return available; +} + +bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const +{ + // Create a sockaddr struct and reset its port number. +#if !defined(QT_NO_IPV6) + struct sockaddr_storage storage; + sockaddr_in6 *storagePtrIPv6 = reinterpret_cast<sockaddr_in6 *>(&storage); + storagePtrIPv6->sin6_port = 0; +#else + struct sockaddr storage; +#endif + sockaddr *storagePtr = reinterpret_cast<sockaddr *>(&storage); + storagePtr->sa_family = 0; + + sockaddr_in *storagePtrIPv4 = reinterpret_cast<sockaddr_in *>(&storage); + storagePtrIPv4->sin_port = 0; + QT_SOCKLEN_T storageSize = sizeof(storage); + + // Peek 0 bytes into the next message. The size of the message may + // well be 0, so we can't check recvfrom's return value. + ssize_t readBytes; + do { + char c; + readBytes = ::recvfrom(socketDescriptor, &c, 1, MSG_PEEK, storagePtr, &storageSize); + } while (readBytes == -1 && errno == EINTR); + + // If there's no error, or if our buffer was too small, there must be a + // pending datagram. + bool result = (readBytes != -1) || errno == EMSGSIZE; + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeHasPendingDatagrams() == %s", + result ? "true" : "false"); +#endif + return result; +} + +qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const +{ + QVarLengthArray<char, 8192> udpMessagePeekBuffer(8192); + ssize_t recvResult = -1; + for (;;) { + // the data written to udpMessagePeekBuffer is discarded, so + // this function is still reentrant although it might not look + // so. + recvResult = ::recv(socketDescriptor, udpMessagePeekBuffer.data(), + udpMessagePeekBuffer.size(), MSG_PEEK); + if (recvResult == -1 && errno == EINTR) + continue; + + if (recvResult != (ssize_t) udpMessagePeekBuffer.size()) + break; + + udpMessagePeekBuffer.resize(udpMessagePeekBuffer.size() * 2); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %i", recvResult); +#endif + + return qint64(recvResult); +} + +qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxSize, + QHostAddress *address, quint16 *port) +{ +#if !defined(QT_NO_IPV6) + struct sockaddr_storage aa; +#else + struct sockaddr_in aa; +#endif + memset(&aa, 0, sizeof(aa)); + QT_SOCKLEN_T sz; + sz = sizeof(aa); + + ssize_t recvFromResult = 0; + do { + char c; + recvFromResult = ::recvfrom(socketDescriptor, maxSize ? data : &c, maxSize ? maxSize : 1, + 0, (struct sockaddr *)&aa, &sz); + } while (recvFromResult == -1 && errno == EINTR); + + if (recvFromResult == -1) { + setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString); + } else if (port || address) { + qt_socket_getPortAndAddress((struct sockaddr *) &aa, port, address); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeReceiveDatagram(%p \"%s\", %lli, %s, %i) == %lli", + data, qt_prettyDebug(data, qMin(recvFromResult, ssize_t(16)), recvFromResult).data(), maxSize, + address ? address->toString().toLatin1().constData() : "(nil)", + port ? *port : 0, (qint64) recvFromResult); +#endif + + return qint64(maxSize ? recvFromResult : recvFromResult == -1 ? -1 : 0); +} + +qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 len, + const QHostAddress &host, quint16 port) +{ + struct sockaddr_in sockAddrIPv4; + struct sockaddr *sockAddrPtr = 0; + QT_SOCKLEN_T sockAddrSize = 0; + +#if !defined(QT_NO_IPV6) + struct sockaddr_in6 sockAddrIPv6; + if (host.protocol() == QAbstractSocket::IPv6Protocol) { + memset(&sockAddrIPv6, 0, sizeof(sockAddrIPv6)); + sockAddrIPv6.sin6_family = AF_INET6; + sockAddrIPv6.sin6_port = htons(port); + + Q_IPV6ADDR tmp = host.toIPv6Address(); + memcpy(&sockAddrIPv6.sin6_addr.s6_addr, &tmp, sizeof(tmp)); + sockAddrSize = sizeof(sockAddrIPv6); + sockAddrPtr = (struct sockaddr *)&sockAddrIPv6; + } else +#endif + if (host.protocol() == QAbstractSocket::IPv4Protocol) { + memset(&sockAddrIPv4, 0, sizeof(sockAddrIPv4)); + sockAddrIPv4.sin_family = AF_INET; + sockAddrIPv4.sin_port = htons(port); + sockAddrIPv4.sin_addr.s_addr = htonl(host.toIPv4Address()); + sockAddrSize = sizeof(sockAddrIPv4); + sockAddrPtr = (struct sockaddr *)&sockAddrIPv4; + } + + // ignore the SIGPIPE signal + qt_ignore_sigpipe(); + + ssize_t sentBytes; + do { + sentBytes = ::sendto(socketDescriptor, data, len, + 0, sockAddrPtr, sockAddrSize); + } while (sentBytes == -1 && errno == EINTR); + + if (sentBytes < 0) { + switch (errno) { + case EMSGSIZE: + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + break; + default: + setError(QAbstractSocket::NetworkError, SendDatagramErrorString); + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEngine::sendDatagram(%p \"%s\", %lli, \"%s\", %i) == %lli", data, + qt_prettyDebug(data, qMin<int>(len, 16), len).data(), len, host.toString().toLatin1().constData(), + port, (qint64) sentBytes); +#endif + + return qint64(sentBytes); +} + +bool QNativeSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + if (socketDescriptor == -1) + return false; + +#if !defined(QT_NO_IPV6) + struct sockaddr_storage sa; +#else + struct sockaddr_in sa; +#endif + struct sockaddr *sockAddrPtr = (struct sockaddr *) &sa; + QT_SOCKLEN_T sockAddrSize = sizeof(sa); + + // Determine local address + memset(&sa, 0, sizeof(sa)); + if (::getsockname(socketDescriptor, sockAddrPtr, &sockAddrSize) == 0) { + qt_socket_getPortAndAddress(sockAddrPtr, &localPort, &localAddress); + + // Determine protocol family + switch (sockAddrPtr->sa_family) { + case AF_INET: + socketProtocol = QAbstractSocket::IPv4Protocol; + break; +#if !defined (QT_NO_IPV6) + case AF_INET6: + socketProtocol = QAbstractSocket::IPv6Protocol; + break; +#endif + default: + socketProtocol = QAbstractSocket::UnknownNetworkLayerProtocol; + break; + } + + } else if (errno == EBADF) { + setError(QAbstractSocket::UnsupportedSocketOperationError, InvalidSocketErrorString); + return false; + } + + // Determine the remote address + if (!::getpeername(socketDescriptor, sockAddrPtr, &sockAddrSize)) + qt_socket_getPortAndAddress(sockAddrPtr, &peerPort, &peerAddress); + + // Determine the socket type (UDP/TCP) + int value = 0; + QT_SOCKOPTLEN_T valueSize = sizeof(int); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, &value, &valueSize) == 0) { + if (value == SOCK_STREAM) + socketType = QAbstractSocket::TcpSocket; + else if (value == SOCK_DGRAM) + socketType = QAbstractSocket::UdpSocket; + else + socketType = QAbstractSocket::UnknownSocketType; + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString socketProtocolStr = "UnknownProtocol"; + if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = "IPv4Protocol"; + else if (socketProtocol == QAbstractSocket::IPv6Protocol) socketProtocolStr = "IPv6Protocol"; + + QString socketTypeStr = "UnknownSocketType"; + if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = "TcpSocket"; + else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = "UdpSocket"; + + qDebug("QNativeSocketEnginePrivate::fetchConnectionParameters() local == %s:%i," + " peer == %s:%i, socket == %s - %s", + localAddress.toString().toLatin1().constData(), localPort, + peerAddress.toString().toLatin1().constData(), peerPort,socketTypeStr.toLatin1().constData(), + socketProtocolStr.toLatin1().constData()); +#endif + return true; +} + +void QNativeSocketEnginePrivate::nativeClose() +{ +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEngine::nativeClose()"); +#endif + ::close(socketDescriptor); +} + +qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len) +{ + Q_Q(QNativeSocketEngine); + + // ignore the SIGPIPE signal + qt_ignore_sigpipe(); + + // loop while ::write() returns -1 and errno == EINTR, in case + // of an interrupting signal. + ssize_t writtenBytes; + do { + writtenBytes = ::write(socketDescriptor, data, len); + } while (writtenBytes < 0 && errno == EINTR); + + if (writtenBytes < 0) { + switch (errno) { + case EPIPE: + case ECONNRESET: + writtenBytes = -1; + setError(QAbstractSocket::RemoteHostClosedError, RemoteHostClosedErrorString); + q->close(); + break; + case EAGAIN: + writtenBytes = 0; + break; + case EMSGSIZE: + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + break; + default: + break; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeWrite(%p \"%s\", %llu) == %i", + data, qt_prettyDebug(data, qMin((int) len, 16), + (int) len).data(), len, (int) writtenBytes); +#endif + + return qint64(writtenBytes); +} +/* +*/ +qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxSize) +{ + Q_Q(QNativeSocketEngine); + if (!q->isValid()) { + qWarning("QNativeSocketEngine::unbufferedRead: Invalid socket"); + return -1; + } + + ssize_t r = 0; + do { + r = ::read(socketDescriptor, data, maxSize); + } while (r == -1 && errno == EINTR); + + if (r < 0) { + r = -1; + switch (errno) { +#if EWOULDBLOCK-0 && EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + case EAGAIN: + // No data was available for reading + r = -2; + break; + case EBADF: + case EINVAL: + case EIO: + setError(QAbstractSocket::NetworkError, ReadErrorString); + break; + case ECONNRESET: + r = 0; + break; + default: + break; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeRead(%p \"%s\", %llu) == %i", + data, qt_prettyDebug(data, qMin(r, ssize_t(16)), r).data(), + maxSize, r); +#endif + + return qint64(r); +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const +{ + fd_set fds; + FD_ZERO(&fds); + FD_SET(socketDescriptor, &fds); + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + QTime timer; + timer.start(); + + int retval; + do { + if (selectForRead) + retval = select(socketDescriptor + 1, &fds, 0, 0, timeout < 0 ? 0 : &tv); + else + retval = select(socketDescriptor + 1, 0, &fds, 0, timeout < 0 ? 0 : &tv); + + if (retval != -1 || errno != EINTR) + break; + + if (timeout > 0) { + // recalculate the timeout + timeout -= timer.elapsed(); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + if (timeout < 0) { + // oops, timeout turned negative? + retval = -1; + break; + } + } + } while (true); + + return retval; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + fd_set fdread; + FD_ZERO(&fdread); + if (checkRead) + FD_SET(socketDescriptor, &fdread); + + fd_set fdwrite; + FD_ZERO(&fdwrite); + if (checkWrite) + FD_SET(socketDescriptor, &fdwrite); + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + QTime timer; + timer.start(); + + int ret; + do { + ret = select(socketDescriptor + 1, &fdread, &fdwrite, 0, timeout < 0 ? 0 : &tv); + if (ret != -1 || errno != EINTR) + break; + + if (timeout > 0) { + // recalculate the timeout + timeout -= timer.elapsed(); + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + if (timeout < 0) { + // oops, timeout turned negative? + ret = -1; + break; + } + } + } while (true); + if (ret <= 0) + return ret; + + *selectForRead = FD_ISSET(socketDescriptor, &fdread); + *selectForWrite = FD_ISSET(socketDescriptor, &fdwrite); + return ret; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp new file mode 100644 index 0000000000..d140be2c51 --- /dev/null +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -0,0 +1,1211 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <winsock2.h> + +#include "qnativesocketengine_p.h" + +#include <qabstracteventdispatcher.h> +#include <qsocketnotifier.h> +#include <qdebug.h> +#include <qdatetime.h> + +//#define QNATIVESOCKETENGINE_DEBUG +#if defined(QNATIVESOCKETENGINE_DEBUG) +# include <qstring.h> +# include <qbytearray.h> +#endif + +QT_BEGIN_NAMESPACE + +#if defined(QNATIVESOCKETENGINE_DEBUG) + +void verboseWSErrorDebug(int r) +{ + switch (r) { + case WSANOTINITIALISED : qDebug("WSA error : WSANOTINITIALISED"); break; + case WSAEINTR: qDebug("WSA error : WSAEINTR"); break; + case WSAEBADF: qDebug("WSA error : WSAEBADF"); break; + case WSAEACCES: qDebug("WSA error : WSAEACCES"); break; + case WSAEFAULT: qDebug("WSA error : WSAEFAULT"); break; + case WSAEINVAL: qDebug("WSA error : WSAEINVAL"); break; + case WSAEMFILE: qDebug("WSA error : WSAEMFILE"); break; + case WSAEWOULDBLOCK: qDebug("WSA error : WSAEWOULDBLOCK"); break; + case WSAEINPROGRESS: qDebug("WSA error : WSAEINPROGRESS"); break; + case WSAEALREADY: qDebug("WSA error : WSAEALREADY"); break; + case WSAENOTSOCK: qDebug("WSA error : WSAENOTSOCK"); break; + case WSAEDESTADDRREQ: qDebug("WSA error : WSAEDESTADDRREQ"); break; + case WSAEMSGSIZE: qDebug("WSA error : WSAEMSGSIZE"); break; + case WSAEPROTOTYPE: qDebug("WSA error : WSAEPROTOTYPE"); break; + case WSAENOPROTOOPT: qDebug("WSA error : WSAENOPROTOOPT"); break; + case WSAEPROTONOSUPPORT: qDebug("WSA error : WSAEPROTONOSUPPORT"); break; + case WSAESOCKTNOSUPPORT: qDebug("WSA error : WSAESOCKTNOSUPPORT"); break; + case WSAEOPNOTSUPP: qDebug("WSA error : WSAEOPNOTSUPP"); break; + case WSAEPFNOSUPPORT: qDebug("WSA error : WSAEPFNOSUPPORT"); break; + case WSAEAFNOSUPPORT: qDebug("WSA error : WSAEAFNOSUPPORT"); break; + case WSAEADDRINUSE: qDebug("WSA error : WSAEADDRINUSE"); break; + case WSAEADDRNOTAVAIL: qDebug("WSA error : WSAEADDRNOTAVAIL"); break; + case WSAENETDOWN: qDebug("WSA error : WSAENETDOWN"); break; + case WSAENETUNREACH: qDebug("WSA error : WSAENETUNREACH"); break; + case WSAENETRESET: qDebug("WSA error : WSAENETRESET"); break; + case WSAECONNABORTED: qDebug("WSA error : WSAECONNABORTED"); break; + case WSAECONNRESET: qDebug("WSA error : WSAECONNRESET"); break; + case WSAENOBUFS: qDebug("WSA error : WSAENOBUFS"); break; + case WSAEISCONN: qDebug("WSA error : WSAEISCONN"); break; + case WSAENOTCONN: qDebug("WSA error : WSAENOTCONN"); break; + case WSAESHUTDOWN: qDebug("WSA error : WSAESHUTDOWN"); break; + case WSAETOOMANYREFS: qDebug("WSA error : WSAETOOMANYREFS"); break; + case WSAETIMEDOUT: qDebug("WSA error : WSAETIMEDOUT"); break; + case WSAECONNREFUSED: qDebug("WSA error : WSAECONNREFUSED"); break; + case WSAELOOP: qDebug("WSA error : WSAELOOP"); break; + case WSAENAMETOOLONG: qDebug("WSA error : WSAENAMETOOLONG"); break; + case WSAEHOSTDOWN: qDebug("WSA error : WSAEHOSTDOWN"); break; + case WSAEHOSTUNREACH: qDebug("WSA error : WSAEHOSTUNREACH"); break; + case WSAENOTEMPTY: qDebug("WSA error : WSAENOTEMPTY"); break; + case WSAEPROCLIM: qDebug("WSA error : WSAEPROCLIM"); break; + case WSAEUSERS: qDebug("WSA error : WSAEUSERS"); break; + case WSAEDQUOT: qDebug("WSA error : WSAEDQUOT"); break; + case WSAESTALE: qDebug("WSA error : WSAESTALE"); break; + case WSAEREMOTE: qDebug("WSA error : WSAEREMOTE"); break; + case WSAEDISCON: qDebug("WSA error : WSAEDISCON"); break; + default: qDebug("WSA error : Unknown"); break; + } + qErrnoWarning(r, "more details"); +} + +/* + Returns a human readable representation of the first \a len + characters in \a data. +*/ +static QByteArray qt_prettyDebug(const char *data, int len, int maxLength) +{ + if (!data) return "(null)"; + QByteArray out; + for (int i = 0; i < len; ++i) { + char c = data[i]; + if (isprint(int(uchar(c)))) { + out += c; + } else switch (c) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + default: + QString tmp; + tmp.sprintf("\\%o", c); + out += tmp.toLatin1().constData(); + } + } + + if (len < maxLength) + out += "..."; + + return out; +} + + +#define WS_ERROR_DEBUG(x) verboseWSErrorDebug(x); + +#else + +#define WS_ERROR_DEBUG(x) Q_UNUSED(x) + +#endif + +#if !defined (QT_NO_IPV6) + +// Use our own defines and structs which we know are correct +# define QT_SS_MAXSIZE 128 +# define QT_SS_ALIGNSIZE (sizeof(__int64)) +# define QT_SS_PAD1SIZE (QT_SS_ALIGNSIZE - sizeof (short)) +# define QT_SS_PAD2SIZE (QT_SS_MAXSIZE - (sizeof (short) + QT_SS_PAD1SIZE + QT_SS_ALIGNSIZE)) +struct qt_sockaddr_storage { + short ss_family; + char __ss_pad1[QT_SS_PAD1SIZE]; + __int64 __ss_align; + char __ss_pad2[QT_SS_PAD2SIZE]; +}; + +// sockaddr_in6 size changed between old and new SDK +// Only the new version is the correct one, so always +// use this structure. +struct qt_in6_addr { + u_char qt_s6_addr[16]; +}; +typedef struct { + short sin6_family; /* AF_INET6 */ + u_short sin6_port; /* Transport level port number */ + u_long sin6_flowinfo; /* IPv6 flow information */ + struct qt_in6_addr sin6_addr; /* IPv6 address */ + u_long sin6_scope_id; /* set of interfaces for a scope */ +} qt_sockaddr_in6; + +#else + +typedef void * qt_sockaddr_in6 ; + + +#endif + +#ifndef AF_INET6 +#define AF_INET6 23 /* Internetwork Version 6 */ +#endif + +#ifndef SO_EXCLUSIVEADDRUSE +#define SO_EXCLUSIVEADDRUSE ((int)(~SO_REUSEADDR)) /* disallow local address reuse */ +#endif + +//### +#define QT_SOCKLEN_T int +#define QT_SOCKOPTLEN_T int + + +/* + Extracts the port and address from a sockaddr, and stores them in + \a port and \a addr if they are non-null. +*/ +static inline void qt_socket_getPortAndAddress(SOCKET socketDescriptor, struct sockaddr *sa, quint16 *port, QHostAddress *address) +{ +#if !defined (QT_NO_IPV6) + if (sa->sa_family == AF_INET6) { + qt_sockaddr_in6 *sa6 = (qt_sockaddr_in6 *)sa; + Q_IPV6ADDR tmp; + for (int i = 0; i < 16; ++i) + tmp.c[i] = sa6->sin6_addr.qt_s6_addr[i]; + QHostAddress a; + a.setAddress(tmp); + if (address) + *address = a; + if (port) + WSANtohs(socketDescriptor, sa6->sin6_port, port); + } else +#endif + if (sa->sa_family == AF_INET) { + struct sockaddr_in *sa4 = (struct sockaddr_in *)sa; + unsigned long addr; + WSANtohl(socketDescriptor, sa4->sin_addr.s_addr, &addr); + QHostAddress a; + a.setAddress(addr); + if (address) + *address = a; + if (port) + WSANtohs(socketDescriptor, sa4->sin_port, port); + } +} + + +/*! \internal + + Sets the port and address to a sockaddr. Requires that sa point to the IPv6 struct if the address is IPv6. +*/ +static inline void qt_socket_setPortAndAddress(SOCKET socketDescriptor, sockaddr_in * sockAddrIPv4, qt_sockaddr_in6 * sockAddrIPv6, + quint16 port, const QHostAddress & address, sockaddr ** sockAddrPtr, QT_SOCKLEN_T *sockAddrSize) +{ +#if !defined(QT_NO_IPV6) + if (address.protocol() == QAbstractSocket::IPv6Protocol) { + memset(sockAddrIPv6, 0, sizeof(qt_sockaddr_in6)); + sockAddrIPv6->sin6_family = AF_INET6; + WSAHtons(socketDescriptor, port, &(sockAddrIPv6->sin6_port)); + Q_IPV6ADDR tmp = address.toIPv6Address(); + memcpy(&(sockAddrIPv6->sin6_addr.qt_s6_addr), &tmp, sizeof(tmp)); + *sockAddrSize = sizeof(qt_sockaddr_in6); + *sockAddrPtr = (struct sockaddr *) sockAddrIPv6; + } else +#endif + if (address.protocol() == QAbstractSocket::IPv4Protocol + || address.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol) { + memset(sockAddrIPv4, 0, sizeof(sockaddr_in)); + sockAddrIPv4->sin_family = AF_INET; + WSAHtons(socketDescriptor, port, &(sockAddrIPv4->sin_port)); + WSAHtonl(socketDescriptor, address.toIPv4Address(), &(sockAddrIPv4->sin_addr.s_addr)); + *sockAddrSize = sizeof(sockaddr_in); + *sockAddrPtr = (struct sockaddr *) sockAddrIPv4; + } else { + // unreachable + } +} + +/*! \internal + +*/ +static inline QAbstractSocket::SocketType qt_socket_getType(int socketDescriptor) +{ + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_TYPE, (char *) &value, &valueSize) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + } else { + if (value == SOCK_STREAM) + return QAbstractSocket::TcpSocket; + else if (value == SOCK_DGRAM) + return QAbstractSocket::UdpSocket; + } + return QAbstractSocket::UnknownSocketType; +} + +/*! \internal + +*/ +static inline int qt_socket_getMaxMsgSize(int socketDescriptor) +{ + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_MAX_MSG_SIZE, (char *) &value, &valueSize) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + } + return value; +} + +QWindowsSockInit::QWindowsSockInit() +: version(0) +{ + //### should we try for 2.2 on all platforms ?? + WSAData wsadata; + + // IPv6 requires Winsock v2.0 or better. + if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) { + qWarning("QTcpSocketAPI: WinSock v2.0 initialization failed."); + } else { + version = 0x20; + } +} + +QWindowsSockInit::~QWindowsSockInit() +{ + WSACleanup(); +} + +// MS Transport Provider IOCTL to control +// reporting PORT_UNREACHABLE messages +// on UDP sockets via recv/WSARecv/etc. +// Path TRUE in input buffer to enable (default if supported), +// FALSE to disable. +#ifndef SIO_UDP_CONNRESET +# ifndef IOC_VENDOR +# define IOC_VENDOR 0x18000000 +# endif +# ifndef _WSAIOW +# define _WSAIOW(x,y) (IOC_IN|(x)|(y)) +# endif +# define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) +#endif + +bool QNativeSocketEnginePrivate::createNewSocket(QAbstractSocket::SocketType socketType, QAbstractSocket::NetworkLayerProtocol socketProtocol) +{ + + //### no ip6 support on winsocket 1.1 but we will try not to use this !!!!!!!!!!!!1 + /* + if (winsockVersion < 0x20 && socketProtocol == QAbstractSocket::IPv6Protocol) { + //### no ip6 support + return -1; + } + */ + + int protocol = (socketProtocol == QAbstractSocket::IPv6Protocol) ? AF_INET6 : AF_INET; + int type = (socketType == QAbstractSocket::UdpSocket) ? SOCK_DGRAM : SOCK_STREAM; + // MSDN KB179942 states that on winnt 4 WSA_FLAG_OVERLAPPED is needed if socket is to be non blocking + // and recomends alwasy doing it for cross windows version comapablity. + SOCKET socket = ::WSASocket(protocol, type, 0, NULL, 0, WSA_FLAG_OVERLAPPED); + + if (socket == INVALID_SOCKET) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEAFNOSUPPORT: + case WSAESOCKTNOSUPPORT: + case WSAEPROTOTYPE: + case WSAEINVAL: + setError(QAbstractSocket::UnsupportedSocketOperationError, ProtocolUnsupportedErrorString); + break; + case WSAEMFILE: + case WSAENOBUFS: + setError(QAbstractSocket::SocketResourceError, ResourceErrorString); + break; + default: + break; + } + + return false; + } + +#if !defined(Q_OS_WINCE) + // enable new behavior using + // SIO_UDP_CONNRESET + DWORD dwBytesReturned = 0; + int bNewBehavior = 1; + if (::WSAIoctl(socket, SIO_UDP_CONNRESET, &bNewBehavior, sizeof(bNewBehavior), + NULL, 0, &dwBytesReturned, NULL, NULL) == SOCKET_ERROR) { + // not to worry isBogusUdpReadNotification() should handle this otherwise + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + } +#endif + + socketDescriptor = socket; + return true; + +} + +/*! \internal + + Returns the value of the socket option \a opt. +*/ +int QNativeSocketEnginePrivate::option(QNativeSocketEngine::SocketOption opt) const +{ + Q_Q(const QNativeSocketEngine); + if (!q->isValid()) + return -1; + + int n = -1; + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + case QNativeSocketEngine::NonBlockingSocketOption: { + unsigned long buf = 0; + if (WSAIoctl(socketDescriptor, FIONBIO, 0,0, &buf, sizeof(buf), 0,0,0) == 0) + return buf; + else + return -1; + break; + } + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + n = SO_EXCLUSIVEADDRUSE; + break; + case QNativeSocketEngine::ReceiveOutOfBandData: + n = SO_OOBINLINE; + break; + } + + int v = -1; + QT_SOCKOPTLEN_T len = sizeof(v); + if (getsockopt(socketDescriptor, SOL_SOCKET, n, (char *) &v, &len) != -1) + return v; + return -1; +} + + +/*! \internal + Sets the socket option \a opt to \a v. +*/ +bool QNativeSocketEnginePrivate::setOption(QNativeSocketEngine::SocketOption opt, int v) +{ + Q_Q(const QNativeSocketEngine); + if (!q->isValid()) + return false; + + int n = 0; + switch (opt) { + case QNativeSocketEngine::ReceiveBufferSocketOption: + n = SO_RCVBUF; + break; + case QNativeSocketEngine::SendBufferSocketOption: + n = SO_SNDBUF; + break; + case QNativeSocketEngine::BroadcastSocketOption: + n = SO_BROADCAST; + break; + case QNativeSocketEngine::NonBlockingSocketOption: + { + unsigned long buf = v; + unsigned long outBuf; + DWORD sizeWritten = 0; + if (::WSAIoctl(socketDescriptor, FIONBIO, &buf, sizeof(unsigned long), &outBuf, sizeof(unsigned long), &sizeWritten, 0,0) == SOCKET_ERROR) { + WS_ERROR_DEBUG(WSAGetLastError()); + return false; + } + return true; + break; + } + case QNativeSocketEngine::AddressReusable: + n = SO_REUSEADDR; + break; + case QNativeSocketEngine::BindExclusively: + n = SO_EXCLUSIVEADDRUSE; + break; + case QNativeSocketEngine::ReceiveOutOfBandData: + n = SO_OOBINLINE; + break; + } + + if (::setsockopt(socketDescriptor, SOL_SOCKET, n, (char*)&v, sizeof(v)) != 0) { + WS_ERROR_DEBUG(WSAGetLastError()); + return false; + } + return true; +} + +/*! + Fetches information about both ends of the connection: whatever is + available. +*/ +bool QNativeSocketEnginePrivate::fetchConnectionParameters() +{ + localPort = 0; + localAddress.clear(); + peerPort = 0; + peerAddress.clear(); + + if (socketDescriptor == -1) + return false; + +#if !defined (QT_NO_IPV6) + struct qt_sockaddr_storage sa; +#else + struct sockaddr_in sa; +#endif + struct sockaddr *pSa = (struct sockaddr *) &sa; + + QT_SOCKLEN_T sz = sizeof(sa); + + memset(&sa, 0, sizeof(sa)); + if (::getsockname(socketDescriptor, pSa, &sz) == 0) { + qt_socket_getPortAndAddress(socketDescriptor, pSa, &localPort, &localAddress); + // Determine protocol family + switch (pSa->sa_family) { + case AF_INET: + socketProtocol = QAbstractSocket::IPv4Protocol; + break; +#if !defined (QT_NO_IPV6) + case AF_INET6: + socketProtocol = QAbstractSocket::IPv6Protocol; + break; +#endif + default: + socketProtocol = QAbstractSocket::UnknownNetworkLayerProtocol; + break; + } + } else { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + if (err == WSAENOTSOCK) { + setError(QAbstractSocket::UnsupportedSocketOperationError, + InvalidSocketErrorString); + return false; + } + } + + memset(&sa, 0, sizeof(sa)); + if (::getpeername(socketDescriptor, pSa, &sz) == 0) { + qt_socket_getPortAndAddress(socketDescriptor, pSa, &peerPort, &peerAddress); + } else { + WS_ERROR_DEBUG(WSAGetLastError()); + } + + socketType = qt_socket_getType(socketDescriptor); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + QString socketProtocolStr = "UnknownProtocol"; + if (socketProtocol == QAbstractSocket::IPv4Protocol) socketProtocolStr = "IPv4Protocol"; + else if (socketProtocol == QAbstractSocket::IPv6Protocol) socketProtocolStr = "IPv6Protocol"; + + QString socketTypeStr = "UnknownSocketType"; + if (socketType == QAbstractSocket::TcpSocket) socketTypeStr = "TcpSocket"; + else if (socketType == QAbstractSocket::UdpSocket) socketTypeStr = "UdpSocket"; + + qDebug("QNativeSocketEnginePrivate::fetchConnectionParameters() localAddress == %s, localPort = %i, peerAddress == %s, peerPort = %i, socketProtocol == %s, socketType == %s", localAddress.toString().toLatin1().constData(), localPort, peerAddress.toString().toLatin1().constData(), peerPort, socketProtocolStr.toLatin1().constData(), socketTypeStr.toLatin1().constData()); +#endif + + return true; +} + + +bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &address, quint16 port) +{ + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect() to %s :: %i", address.toString().toLatin1().constData(), port); +#endif + + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr; + QT_SOCKLEN_T sockAddrSize; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + forever { + int connectResult = ::WSAConnect(socketDescriptor, sockAddrPtr, sockAddrSize, 0,0,0,0); + if (connectResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEISCONN: + socketState = QAbstractSocket::ConnectedState; + break; + case WSAEWOULDBLOCK: { + // If WSAConnect returns WSAEWOULDBLOCK on the second + // connection attempt, we have to check SO_ERROR's + // value to detect ECONNREFUSED. If we don't get + // ECONNREFUSED, we'll have to treat it as an + // unfinished operation. + int value = 0; + QT_SOCKLEN_T valueSize = sizeof(value); + if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) { + if (value == WSAECONNREFUSED) { + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + if (value == WSAETIMEDOUT) { + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + } + } + // fall through + } + case WSAEINPROGRESS: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + socketState = QAbstractSocket::ConnectingState; + break; + case WSAEADDRINUSE: + setError(QAbstractSocket::NetworkError, AddressInuseErrorString); + break; + case WSAECONNREFUSED: + setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAETIMEDOUT: + setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); + break; + case WSAEACCES: + setError(QAbstractSocket::SocketAccessError, AccessErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEHOSTUNREACH: + setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAENETUNREACH: + setError(QAbstractSocket::NetworkError, NetworkUnreachableErrorString); + socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEINVAL: + case WSAEALREADY: + setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); + break; + default: + break; + } + if (socketState != QAbstractSocket::ConnectedState) { +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, + socketState == QAbstractSocket::ConnectingState + ? "Connection in progress" : socketErrorString.toLatin1().constData()); +#endif + return false; + } + } + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeConnect(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + + socketState = QAbstractSocket::ConnectedState; + return true; +} + + +bool QNativeSocketEnginePrivate::nativeBind(const QHostAddress &address, quint16 port) +{ + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr; + QT_SOCKLEN_T sockAddrSize; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + + int bindResult = ::bind(socketDescriptor, sockAddrPtr, sockAddrSize); + if (bindResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEADDRINUSE: + case WSAEINVAL: + setError(QAbstractSocket::AddressInUseError, AddressInuseErrorString); + break; + case WSAEACCES: + setError(QAbstractSocket::SocketAccessError, AddressProtectedErrorString); + break; + case WSAEADDRNOTAVAIL: + setError(QAbstractSocket::SocketAddressNotAvailableError, AddressNotAvailableErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == false (%s)", + address.toString().toLatin1().constData(), port, socketErrorString.toLatin1().constData()); +#endif + + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeBind(%s, %i) == true", + address.toString().toLatin1().constData(), port); +#endif + socketState = QAbstractSocket::BoundState; + return true; +} + + +bool QNativeSocketEnginePrivate::nativeListen(int backlog) +{ + if (::listen(socketDescriptor, backlog) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSANOTINITIALISED: + //### + break; + case WSAEADDRINUSE: + setError(QAbstractSocket::AddressInUseError, + PortInuseErrorString); + break; + default: + break; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == false (%s)", + backlog, socketErrorString.toLatin1().constData()); +#endif + return false; + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeListen(%i) == true", backlog); +#endif + + socketState = QAbstractSocket::ListeningState; + return true; +} + +int QNativeSocketEnginePrivate::nativeAccept() +{ + int acceptedDescriptor = WSAAccept(socketDescriptor, 0,0,0,0); + if (acceptedDescriptor != -1 && QAbstractEventDispatcher::instance()) { + // Becuase of WSAAsyncSelect() WSAAccept returns a non blocking socket + // with the same attributes as the listening socket including the current + // WSAAsyncSelect(). To be able to change the socket to blocking mode the + // WSAAsyncSelect() call must be cancled. + QSocketNotifier n(acceptedDescriptor, QSocketNotifier::Read); + n.setEnabled(true); + n.setEnabled(false); + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeAccept() == %i", acceptedDescriptor); +#endif + return acceptedDescriptor; +} + + +qint64 QNativeSocketEnginePrivate::nativeBytesAvailable() const +{ + unsigned long nbytes = 0; + unsigned long dummy = 0; + DWORD sizeWritten = 0; + if (::WSAIoctl(socketDescriptor, FIONREAD, &dummy, sizeof(dummy), &nbytes, sizeof(nbytes), &sizeWritten, 0,0) == SOCKET_ERROR) { + WS_ERROR_DEBUG(WSAGetLastError()); + return -1; + } + + // ioctlsocket sometimes reports 1 byte available for datagrams + // while the following recvfrom returns -1 and claims connection + // was reset (udp is connectionless). so we peek one byte to + // catch this case and return 0 bytes available if recvfrom + // fails. + if (nbytes == 1 && socketType == QAbstractSocket::UdpSocket) { + char c; + WSABUF buf; + buf.buf = &c; + buf.len = sizeof(c); + DWORD flags = MSG_PEEK; + if (::WSARecvFrom(socketDescriptor, &buf, 1, 0, &flags, 0,0,0,0) == SOCKET_ERROR) + return 0; + } + return nbytes; +} + + +bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const +{ +#if !defined(Q_OS_WINCE) + // Create a sockaddr struct and reset its port number. +#if !defined(QT_NO_IPV6) + qt_sockaddr_in6 storage; + qt_sockaddr_in6 *storagePtrIPv6 = reinterpret_cast<qt_sockaddr_in6 *>(&storage); + storagePtrIPv6->sin6_port = 0; +#else + struct sockaddr storage; +#endif + sockaddr *storagePtr = reinterpret_cast<sockaddr *>(&storage); + storagePtr->sa_family = 0; + + sockaddr_in *storagePtrIPv4 = reinterpret_cast<sockaddr_in *>(&storage); + storagePtrIPv4->sin_port = 0; + QT_SOCKLEN_T storageSize = sizeof(storage); + + + bool result = false; + + // Peek 0 bytes into the next message. The size of the message may + // well be 0, so we check if there was a sender. + char c; + WSABUF buf; + buf.buf = &c; + buf.len = sizeof(c); + DWORD available = 0; + DWORD flags = MSG_PEEK; + int ret = ::WSARecvFrom(socketDescriptor, &buf, 1, &available, &flags, storagePtr, &storageSize,0,0); + int err = WSAGetLastError(); + if (ret == SOCKET_ERROR && err != WSAEMSGSIZE) { + WS_ERROR_DEBUG(err); + if (err == WSAECONNRESET) { + // Discard error message to prevent QAbstractSocket from + // getting this message repeatedly after reenabling the + // notifiers. + flags = 0; + ::WSARecvFrom(socketDescriptor, &buf, 1, &available, &flags, + storagePtr, &storageSize, 0, 0); + } + } else { + // If there's no error, or if our buffer was too small, there must be + // a pending datagram. + result = true; + } + +#else // Q_OS_WINCE + bool result = false; + fd_set readS; + FD_ZERO(&readS); + FD_SET(socketDescriptor, &readS); + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 5000; + int available = ::select(1, &readS, 0, 0, &timeout); + result = available > 0 ? true : false; +#endif + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeHasPendingDatagrams() == %s", + result ? "true" : "false"); +#endif + return result; +} + + +qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const +{ + qint64 ret = -1; +#if !defined(Q_OS_WINCE) + int recvResult = 0; + DWORD flags; + DWORD bufferCount = 5; + WSABUF * buf = 0; + for (;;) { + // the data written to udpMessagePeekBuffer is discarded, so + // this function is still reentrant although it might not look + // so. + static char udpMessagePeekBuffer[8192]; + + buf = new WSABUF[bufferCount]; + for (DWORD i=0; i<bufferCount; i++) { + buf[i].buf = udpMessagePeekBuffer; + buf[i].len = sizeof(udpMessagePeekBuffer); + } + flags = MSG_PEEK; + DWORD bytesRead = 0; + recvResult = ::WSARecv(socketDescriptor, buf, bufferCount, &bytesRead, &flags, 0,0); + int err = WSAGetLastError(); + if (recvResult != SOCKET_ERROR) { + ret = qint64(bytesRead); + break; + } else if (recvResult == SOCKET_ERROR && err == WSAEMSGSIZE) { + bufferCount += 5; + delete[] buf; + } else if (recvResult == SOCKET_ERROR) { + WS_ERROR_DEBUG(err); + ret = -1; + break; + } + } + + if (buf) + delete[] buf; + +#else // Q_OS_WINCE + DWORD size = -1; + DWORD bytesReturned; + int ioResult = WSAIoctl(socketDescriptor, FIONREAD, 0,0, &size, sizeof(size), &bytesReturned, 0, 0); + if (ioResult == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + } else { + ret = qint64(size); + } +#endif + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativePendingDatagramSize() == %li", ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxLength, + QHostAddress *address, quint16 *port) +{ + qint64 ret = 0; + +#if !defined(QT_NO_IPV6) + qt_sockaddr_storage aa; +#else + struct sockaddr_in aa; +#endif + memset(&aa, 0, sizeof(aa)); + QT_SOCKLEN_T sz; + sz = sizeof(aa); + WSABUF buf; + buf.buf = data; + buf.len = maxLength; +#if !defined(Q_OS_WINCE) + buf.buf = data; + buf.len = maxLength; +#else + char tmpChar; + buf.buf = data ? data : &tmpChar; + buf.len = maxLength; +#endif + + DWORD flags = 0; + DWORD bytesRead = 0; + int wsaRet = ::WSARecvFrom(socketDescriptor, &buf, 1, &bytesRead, &flags, (struct sockaddr *) &aa, &sz,0,0); + if (wsaRet == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err == WSAEMSGSIZE) { + // it is ok the buffer was to small if bytesRead is larger than + // maxLength (win 9x) then assume bytes read is really maxLenth + ret = qint64(bytesRead) > maxLength ? maxLength : qint64(bytesRead); + } else { + WS_ERROR_DEBUG(err); + setError(QAbstractSocket::NetworkError, ReceiveDatagramErrorString); + ret = -1; + } + } else { + ret = qint64(bytesRead); + } + + qt_socket_getPortAndAddress(socketDescriptor, (struct sockaddr *) &aa, port, address); + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeReceiveDatagram(%p \"%s\", %li, %s, %i) == %li", + data, qt_prettyDebug(data, qMin<qint64>(ret, 16), ret).data(), maxLength, + address ? address->toString().toLatin1().constData() : "(nil)", + port ? *port : 0, ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeSendDatagram(const char *data, qint64 len, + const QHostAddress &address, quint16 port) +{ + qint64 ret = -1; + struct sockaddr_in sockAddrIPv4; + qt_sockaddr_in6 sockAddrIPv6; + struct sockaddr *sockAddrPtr; + QT_SOCKLEN_T sockAddrSize; + + qt_socket_setPortAndAddress(socketDescriptor, &sockAddrIPv4, &sockAddrIPv6, port, address, &sockAddrPtr, &sockAddrSize); + + if (QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based && len > qint64(qt_socket_getMaxMsgSize(socketDescriptor))) { + // WSAEMSGSIZE is not reliable enough (win 9x) so we check max size our self. + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + } else { + WSABUF buf; +#if !defined(Q_OS_WINCE) + buf.buf = len ? (char*)data : 0; +#else + char tmp; + buf.buf = len ? (char*)data : &tmp; +#endif + buf.len = len; + DWORD flags = 0; + DWORD bytesSent = 0; + if (::WSASendTo(socketDescriptor, &buf, 1, &bytesSent, flags, sockAddrPtr, sockAddrSize, 0,0) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSAEMSGSIZE: + setError(QAbstractSocket::DatagramTooLargeError, DatagramTooLargeErrorString); + break; + default: + setError(QAbstractSocket::NetworkError, SendDatagramErrorString); + break; + } + ret = -1; + } else { + ret = qint64(bytesSent); + } + } +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeSendDatagram(%p \"%s\", %li, \"%s\", %i) == %li", data, + qt_prettyDebug(data, qMin<qint64>(len, 16), len).data(), 0, address.toString().toLatin1().constData(), + port, ret); +#endif + + return ret; +} + + +qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len) +{ + Q_Q(QNativeSocketEngine); + qint64 ret = 0; + // don't send more than 49152 per call to WSASendTo to avoid getting a WSAENOBUFS + for (;;) { + qint64 bytesToSend = qMin<qint64>(49152, len - ret); + WSABUF buf; + buf.buf = (char*)data + ret; + buf.len = bytesToSend; + DWORD flags = 0; + DWORD bytesWritten = 0; + + int socketRet = ::WSASend(socketDescriptor, &buf, 1, &bytesWritten, flags, 0,0); + + ret += qint64(bytesWritten); + + if (socketRet != SOCKET_ERROR) { + if (ret == len) + break; + else + continue; + } else if (WSAGetLastError() == WSAEWOULDBLOCK) { + break; + } else { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSAECONNRESET: + case WSAECONNABORTED: + ret = -1; + setError(QAbstractSocket::NetworkError, WriteErrorString); + q->close(); + break; + default: + break; + } + break; + } + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeWrite(%p \"%s\", %li) == %li", + data, qt_prettyDebug(data, qMin((int)ret, 16), (int)ret).data(), (int)len, (int)ret); +#endif + + return ret; +} + +qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength) +{ + qint64 ret = -1; + WSABUF buf; + buf.buf = data; + buf.len = maxLength; + DWORD flags = 0; + DWORD bytesRead = 0; +#if defined(Q_OS_WINCE) + WSASetLastError(0); +#endif + if (::WSARecv(socketDescriptor, &buf, 1, &bytesRead, &flags, 0,0) == SOCKET_ERROR) { + int err = WSAGetLastError(); + WS_ERROR_DEBUG(err); + switch (err) { + case WSAEWOULDBLOCK: + ret = -2; + break; + case WSAEBADF: + case WSAEINVAL: + setError(QAbstractSocket::NetworkError, ReadErrorString); + break; + case WSAECONNRESET: + case WSAECONNABORTED: + // for tcp sockets this will be handled in QNativeSocketEngine::read + ret = 0; + break; + default: + break; + } + } else { + if (WSAGetLastError() == WSAEWOULDBLOCK) + ret = -2; + else + ret = qint64(bytesRead); + } + +#if defined (QNATIVESOCKETENGINE_DEBUG) + if (ret != -2) { + qDebug("QNativeSocketEnginePrivate::nativeRead(%p \"%s\", %l) == %li", + data, qt_prettyDebug(data, qMin((int)bytesRead, 16), (int)bytesRead).data(), (int)maxLength, (int)ret); + } else { + qDebug("QNativeSocketEnginePrivate::nativeRead(%p, %l) == -2 (WOULD BLOCK)", + data, int(maxLength)); + } +#endif + + return ret; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const +{ + bool readEnabled = selectForRead && readNotifier && readNotifier->isEnabled(); + if (readEnabled) + readNotifier->setEnabled(false); + + fd_set fds; + + int ret = 0; + + memset(&fds, 0, sizeof(fd_set)); + fds.fd_count = 1; + fds.fd_array[0] = socketDescriptor; + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + if (selectForRead) + ret = select(0, &fds, 0, 0, timeout < 0 ? 0 : &tv); + else + ret = select(0, 0, &fds, 0, timeout < 0 ? 0 : &tv); + + if (readEnabled) + readNotifier->setEnabled(true); + + return ret; +} + +int QNativeSocketEnginePrivate::nativeSelect(int timeout, + bool checkRead, bool checkWrite, + bool *selectForRead, bool *selectForWrite) const +{ + bool readEnabled = checkRead && readNotifier && readNotifier->isEnabled(); + if (readEnabled) + readNotifier->setEnabled(false); + + fd_set fdread; + fd_set fdwrite; + + int ret = 0; + + memset(&fdread, 0, sizeof(fd_set)); + if (checkRead) { + fdread.fd_count = 1; + fdread.fd_array[0] = socketDescriptor; + } + memset(&fdwrite, 0, sizeof(fd_set)); + if (checkWrite) { + fdwrite.fd_count = 1; + fdwrite.fd_array[0] = socketDescriptor; + } + + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + +#if !defined(Q_OS_WINCE) + ret = select(socketDescriptor + 1, &fdread, &fdwrite, 0, timeout < 0 ? 0 : &tv); +#else + ret = select(1, &fdread, &fdwrite, 0, timeout < 0 ? 0 : &tv); +#endif + if (readEnabled) + readNotifier->setEnabled(true); + + if (ret <= 0) + return ret; + + *selectForRead = FD_ISSET(socketDescriptor, &fdread); + *selectForWrite = FD_ISSET(socketDescriptor, &fdwrite); + + return ret; +} + +void QNativeSocketEnginePrivate::nativeClose() +{ +#if defined (QTCPSOCKETENGINE_DEBUG) + qDebug("QNativeSocketEnginePrivate::nativeClose()"); +#endif + linger l = {1, 0}; + ::setsockopt(socketDescriptor, SOL_SOCKET, SO_DONTLINGER, (char*)&l, sizeof(l)); + ::closesocket(socketDescriptor); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qsocks5socketengine.cpp b/src/network/socket/qsocks5socketengine.cpp new file mode 100644 index 0000000000..c41e32dc27 --- /dev/null +++ b/src/network/socket/qsocks5socketengine.cpp @@ -0,0 +1,1850 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsocks5socketengine_p.h" + +#ifndef QT_NO_SOCKS5 + +#include "qtcpsocket.h" +#include "qudpsocket.h" +#include "qtcpserver.h" +#include "qdebug.h" +#include "qhash.h" +#include "qqueue.h" +#include "qdatetime.h" +#include "qmutex.h" +#include "qthread.h" +#include "qcoreapplication.h" +#include "qurl.h" +#include "qauthenticator.h" +#include <qendian.h> + +QT_BEGIN_NAMESPACE + +static const int MaxWriteBufferSize = 128*1024; + +//#define QSOCKS5SOCKETLAYER_DEBUG + +#define MAX_DATA_DUMP 256 +#if !defined(Q_OS_WINCE) +#define SOCKS5_BLOCKING_BIND_TIMEOUT 5000 +#else +#define SOCKS5_BLOCKING_BIND_TIMEOUT 10000 +#endif + +#define Q_INIT_CHECK(returnValue) do { \ + if (!d->data) { \ + return returnValue; \ + } } while (0) + +#define S5_VERSION_5 0x05 +#define S5_CONNECT 0x01 +#define S5_BIND 0x02 +#define S5_UDP_ASSOCIATE 0x03 +#define S5_IP_V4 0x01 +#define S5_DOMAINNAME 0x03 +#define S5_IP_V6 0x04 +#define S5_SUCCESS 0x00 +#define S5_R_ERROR_SOCKS_FAILURE 0x01 +#define S5_R_ERROR_CON_NOT_ALLOWED 0x02 +#define S5_R_ERROR_NET_UNREACH 0x03 +#define S5_R_ERROR_HOST_UNREACH 0x04 +#define S5_R_ERROR_CONN_REFUSED 0x05 +#define S5_R_ERROR_TTL 0x06 +#define S5_R_ERROR_CMD_NOT_SUPPORTED 0x07 +#define S5_R_ERROR_ADD_TYPE_NOT_SUPORTED 0x08 + +#define S5_AUTHMETHOD_NONE 0x00 +#define S5_AUTHMETHOD_PASSWORD 0x02 +#define S5_AUTHMETHOD_NOTACCEPTABLE 0xFF + +#define S5_PASSWORDAUTH_VERSION 0x01 + +#ifdef QSOCKS5SOCKETLAYER_DEBUG +# define QSOCKS5_Q_DEBUG qDebug() << this +# define QSOCKS5_D_DEBUG qDebug() << q_ptr +# define QSOCKS5_DEBUG qDebug() << "[QSocks5]" +static QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State s) +{ + switch (s) { + case QSocks5SocketEnginePrivate::Uninitialized: return QLatin1String("Uninitialized"); + case QSocks5SocketEnginePrivate::ConnectError: return QLatin1String("ConnectError"); + case QSocks5SocketEnginePrivate::AuthenticationMethodsSent: return QLatin1String("AuthenticationMethodsSent"); + case QSocks5SocketEnginePrivate::Authenticating: return QLatin1String("Authenticating"); + case QSocks5SocketEnginePrivate::AuthenticatingError: return QLatin1String("AuthenticatingError"); + case QSocks5SocketEnginePrivate::RequestMethodSent: return QLatin1String("RequestMethodSent"); + case QSocks5SocketEnginePrivate::RequestError: return QLatin1String("RequestError"); + case QSocks5SocketEnginePrivate::Connected: return QLatin1String("Connected"); + case QSocks5SocketEnginePrivate::UdpAssociateSuccess: return QLatin1String("UdpAssociateSuccess"); + case QSocks5SocketEnginePrivate::BindSuccess: return QLatin1String("BindSuccess"); + case QSocks5SocketEnginePrivate::ControlSocketError: return QLatin1String("ControlSocketError"); + case QSocks5SocketEnginePrivate::SocksError: return QLatin1String("SocksError"); + case QSocks5SocketEnginePrivate::HostNameLookupError: return QLatin1String("HostNameLookupError"); + default: break; + } + return QLatin1String("unknown state"); +} + +static QString dump(const QByteArray &buf) +{ + QString data; + for (int i = 0; i < qMin<int>(MAX_DATA_DUMP, buf.size()); ++i) { + if (i) data += QLatin1Char(' '); + uint val = (unsigned char)buf.at(i); + // data += QString("0x%1").arg(val, 3, 16, QLatin1Char('0')); + data += QString::number(val); + } + if (buf.size() > MAX_DATA_DUMP) + data += QLatin1String(" ..."); + + return QString::fromLatin1("size: %1 data: { %2 }").arg(buf.size()).arg(data); +} + +#else +# define QSOCKS5_DEBUG if (0) qDebug() +# define QSOCKS5_Q_DEBUG if (0) qDebug() +# define QSOCKS5_D_DEBUG if (0) qDebug() + +static inline QString s5StateToString(QSocks5SocketEnginePrivate::Socks5State) { return QString(); } +static inline QString dump(const QByteArray &) { return QString(); } +#endif + +/* + inserts the host address in buf at pos and updates pos. + if the func fails the data in buf and the vallue of pos is undefined +*/ +static bool qt_socks5_set_host_address_and_port(const QHostAddress &address, quint16 port, QByteArray *pBuf) +{ + QSOCKS5_DEBUG << "setting [" << address << ":" << port << "]"; + + union { + quint16 port; + quint32 ipv4; + QIPv6Address ipv6; + char ptr; + } data; + + // add address + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + data.ipv4 = qToBigEndian<quint32>(address.toIPv4Address()); + pBuf->append(S5_IP_V4); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv4)); + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + data.ipv6 = address.toIPv6Address(); + pBuf->append(S5_IP_V6); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.ipv6)); + } else { + return false; + } + + // add port + data.port = qToBigEndian<quint16>(port); + pBuf->append(QByteArray::fromRawData(&data.ptr, sizeof data.port)); + return true; +} + +/* + like above, but for a hostname +*/ +static bool qt_socks5_set_host_name_and_port(const QString &hostname, quint16 port, QByteArray *pBuf) +{ + QSOCKS5_DEBUG << "setting [" << hostname << ":" << port << "]"; + + QByteArray encodedHostName = QUrl::toAce(hostname); + QByteArray &buf = *pBuf; + + if (encodedHostName.length() > 255) + return false; + + buf.append(S5_DOMAINNAME); + buf.append(uchar(encodedHostName.length())); + buf.append(encodedHostName); + + // add port + union { + quint16 port; + char ptr; + } data; + data.port = qToBigEndian<quint16>(port); + buf.append(QByteArray::fromRawData(&data.ptr, sizeof data.port)); + + return true; +} + + +/* + retrives the host address in buf at pos and updates pos. + if the func fails the value of the address and the pos is undefined +*/ +static bool qt_socks5_get_host_address_and_port(const QByteArray &buf, QHostAddress *pAddress, quint16 *pPort, int *pPos) +{ + bool ret = false; + int pos = *pPos; + const unsigned char *pBuf = reinterpret_cast<const unsigned char*>(buf.constData()); + QHostAddress address; + quint16 port = 0; + + if (buf.size() - pos < 1) { + QSOCKS5_DEBUG << "need more data address/port"; + return false; + } + if (pBuf[pos] == S5_IP_V4) { + pos++; + if (buf.size() - pos < 4) { + QSOCKS5_DEBUG << "need more data for ip4 address"; + return false; + } + address.setAddress(qFromBigEndian<quint32>(&pBuf[pos])); + pos += 4; + ret = true; + } else if (pBuf[pos] == S5_IP_V6) { + pos++; + if (buf.size() - pos < 16) { + QSOCKS5_DEBUG << "need more data for ip6 address"; + return false; + } + QIPv6Address add; + for (int i = 0; i < 16; ++i) + add[i] = buf[pos++]; + ret = true; + } else if (pBuf[pos] == S5_DOMAINNAME){ + // just skip it + pos++; + qDebug() << "skipping hostname of len" << uint(pBuf[pos]); + pos += uchar(pBuf[pos]); + } else { + QSOCKS5_DEBUG << "invalid address type" << (int)pBuf[pos]; + ret = false; + } + + if (ret) { + if (buf.size() - pos < 2) { + QSOCKS5_DEBUG << "need more data for port"; + return false; + } + port = qFromBigEndian<quint16>(&pBuf[pos]); + pos += 2; + } + + if (ret) { + QSOCKS5_DEBUG << "got [" << address << ":" << port << "]"; + *pAddress = address; + *pPort = port; + *pPos = pos; + } + + return ret; +} + +/* + Returns the difference between msecs and elapsed. If msecs is -1, + however, -1 is returned. +*/ +static int qt_timeout_value(int msecs, int elapsed) +{ + if (msecs == -1) + return -1; + + int timeout = msecs - elapsed; + return timeout < 0 ? 0 : timeout; +} + +struct QSocks5Data +{ + QTcpSocket *controlSocket; + QSocks5Authenticator *authenticator; +}; + +struct QSocks5ConnectData : public QSocks5Data +{ + QByteArray readBuffer; +}; + +struct QSocks5BindData : public QSocks5Data +{ + QHostAddress localAddress; + quint16 localPort; + QHostAddress peerAddress; + quint16 peerPort; + QDateTime timeStamp; +}; + +struct QSocks5RevivedDatagram +{ + QByteArray data; + QHostAddress address; + quint16 port; +}; + +#ifndef QT_NO_UDPSOCKET +struct QSocks5UdpAssociateData : public QSocks5Data +{ + QUdpSocket *udpSocket; + QHostAddress associateAddress; + quint16 associatePort; + QQueue<QSocks5RevivedDatagram> pendingDatagrams; +}; +#endif + +// needs to be thread safe +class QSocks5BindStore : public QObject +{ +public: + QSocks5BindStore(); + ~QSocks5BindStore(); + + void add(int socketDescriptor, QSocks5BindData *bindData); + bool contains(int socketDescriptor); + QSocks5BindData *retrieve(int socketDescriptor); + +protected: + void timerEvent(QTimerEvent * event); + + QMutex mutex; + int sweepTimerId; + //socket descriptor, data, timestamp + QHash<int, QSocks5BindData *> store; +}; + +Q_GLOBAL_STATIC(QSocks5BindStore, socks5BindStore) + +QSocks5BindStore::QSocks5BindStore() + : mutex(QMutex::Recursive) + , sweepTimerId(-1) +{ + QCoreApplication *app = QCoreApplication::instance(); + if (app && app->thread() != thread()) + moveToThread(app->thread()); +} + +QSocks5BindStore::~QSocks5BindStore() +{ +} + +void QSocks5BindStore::add(int socketDescriptor, QSocks5BindData *bindData) +{ + QMutexLocker lock(&mutex); + if (store.contains(socketDescriptor)) { + // qDebug() << "delete it"; + } + bindData->timeStamp = QDateTime::currentDateTime(); + store.insert(socketDescriptor, bindData); + // start sweep timer if not started + if (sweepTimerId == -1) + sweepTimerId = startTimer(60000); +} + +bool QSocks5BindStore::contains(int socketDescriptor) +{ + QMutexLocker lock(&mutex); + return store.contains(socketDescriptor); +} + +QSocks5BindData *QSocks5BindStore::retrieve(int socketDescriptor) +{ + QMutexLocker lock(&mutex); + if (!store.contains(socketDescriptor)) + return 0; + QSocks5BindData *bindData = store.take(socketDescriptor); + if (bindData) { + if (bindData->controlSocket->thread() != QThread::currentThread()) { + qWarning("Can not access socks5 bind data from different thread"); + return 0; + } + } else { + QSOCKS5_DEBUG << "__ERROR__ binddata == 0"; + } + // stop the sweep timer if not needed + if (store.isEmpty()) { + killTimer(sweepTimerId); + sweepTimerId = -1; + } + return bindData; +} + +void QSocks5BindStore::timerEvent(QTimerEvent * event) +{ + QMutexLocker lock(&mutex); + if (event->timerId() == sweepTimerId) { + QSOCKS5_DEBUG << "QSocks5BindStore performing sweep"; + QMutableHashIterator<int, QSocks5BindData *> it(store); + while (it.hasNext()) { + it.next(); + if (it.value()->timeStamp.secsTo(QDateTime::currentDateTime()) > 350) { + QSOCKS5_DEBUG << "QSocks5BindStore removing JJJJ"; + it.remove(); + } + } + } +} + +QSocks5Authenticator::QSocks5Authenticator() +{ +} + +QSocks5Authenticator::~QSocks5Authenticator() +{ +} + +char QSocks5Authenticator::methodId() +{ + return 0x00; +} + +bool QSocks5Authenticator::beginAuthenticate(QTcpSocket *socket, bool *completed) +{ + Q_UNUSED(socket); + *completed = true; + return true; +} + +bool QSocks5Authenticator::continueAuthenticate(QTcpSocket *socket, bool *completed) +{ + Q_UNUSED(socket); + *completed = true; + return true; +} + +bool QSocks5Authenticator::seal(const QByteArray buf, QByteArray *sealedBuf) +{ + *sealedBuf = buf; + return true; +} + +bool QSocks5Authenticator::unSeal(const QByteArray sealedBuf, QByteArray *buf) +{ + *buf = sealedBuf; + return true; +} + +bool QSocks5Authenticator::unSeal(QTcpSocket *sealedSocket, QByteArray *buf) +{ + return unSeal(sealedSocket->readAll(), buf); +} + +QSocks5PasswordAuthenticator::QSocks5PasswordAuthenticator(const QString &userName, const QString &password) +{ + this->userName = userName; + this->password = password; +} + +char QSocks5PasswordAuthenticator::methodId() +{ + return 0x02; +} + +bool QSocks5PasswordAuthenticator::beginAuthenticate(QTcpSocket *socket, bool *completed) +{ + *completed = false; + QByteArray uname = userName.toLatin1(); + QByteArray passwd = password.toLatin1(); + QByteArray dataBuf(3 + uname.size() + passwd.size(), 0); + char *buf = dataBuf.data(); + int pos = 0; + buf[pos++] = S5_PASSWORDAUTH_VERSION; + buf[pos++] = uname.size(); + memcpy(&buf[pos], uname.data(), uname.size()); + pos += uname.size(); + buf[pos++] = passwd.size(); + memcpy(&buf[pos], passwd.data(), passwd.size()); + return socket->write(dataBuf) == dataBuf.size(); +} + +bool QSocks5PasswordAuthenticator::continueAuthenticate(QTcpSocket *socket, bool *completed) +{ + *completed = false; + + if (socket->bytesAvailable() < 2) + return true; + + QByteArray buf = socket->read(2); + if (buf.at(0) == S5_PASSWORDAUTH_VERSION && buf.at(1) == 0x00) { + *completed = true; + return true; + } + + // must disconnect + socket->close(); + return false; +} + +QString QSocks5PasswordAuthenticator::errorString() +{ + return QLatin1String("Socks5 user name or password incorrect"); +} + + + +QSocks5SocketEnginePrivate::QSocks5SocketEnginePrivate() + : socks5State(Uninitialized) + , readNotificationEnabled(false) + , writeNotificationEnabled(false) + , exceptNotificationEnabled(false) + , socketDescriptor(-1) + , data(0) + , connectData(0) +#ifndef QT_NO_UDPSOCKET + , udpData(0) +#endif + , bindData(0) + , readNotificationActivated(false) + , writeNotificationActivated(false) + , readNotificationPending(false) + , writeNotificationPending(false) + , connectionNotificationPending(false) +{ + mode = NoMode; +} + +QSocks5SocketEnginePrivate::~QSocks5SocketEnginePrivate() +{ +} + +void QSocks5SocketEnginePrivate::initialize(Socks5Mode socks5Mode) +{ + Q_Q(QSocks5SocketEngine); + + mode = socks5Mode; + if (mode == ConnectMode) { + connectData = new QSocks5ConnectData; + data = connectData; +#ifndef QT_NO_UDPSOCKET + } else if (mode == UdpAssociateMode) { + udpData = new QSocks5UdpAssociateData; + data = udpData; + udpData->udpSocket = new QUdpSocket(q); + udpData->udpSocket->setProxy(QNetworkProxy::NoProxy); + QObject::connect(udpData->udpSocket, SIGNAL(readyRead()), + q, SLOT(_q_udpSocketReadNotification()), + Qt::DirectConnection); +#endif // QT_NO_UDPSOCKET + } else if (mode == BindMode) { + bindData = new QSocks5BindData; + data = bindData; + } + + data->controlSocket = new QTcpSocket(q); + data->controlSocket->setProxy(QNetworkProxy::NoProxy); + QObject::connect(data->controlSocket, SIGNAL(connected()), q, SLOT(_q_controlSocketConnected()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(readyRead()), q, SLOT(_q_controlSocketReadNotification()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(bytesWritten(qint64)), q, SLOT(_q_controlSocketBytesWritten()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(error(QAbstractSocket::SocketError)), + q, SLOT(_q_controlSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(disconnected()), q, SLOT(_q_controlSocketDisconnected()), + Qt::DirectConnection); + QObject::connect(data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + q, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + if (!proxyInfo.user().isEmpty() || !proxyInfo.password().isEmpty()) { + QSOCKS5_D_DEBUG << "using username/password authentication; user =" << proxyInfo.user(); + data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); + } else { + QSOCKS5_D_DEBUG << "not using authentication"; + data->authenticator = new QSocks5Authenticator(); + } +} + +void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, const QString &extraMessage) +{ + Q_Q(QSocks5SocketEngine); + + switch (state) { + case Uninitialized: + case Authenticating: + case AuthenticationMethodsSent: + case RequestMethodSent: + case Connected: + case UdpAssociateSuccess: + case BindSuccess: + // these aren't error states + return; + + case ConnectError: + case ControlSocketError: { + QAbstractSocket::SocketError controlSocketError = data->controlSocket->error(); + if (socks5State != Connected) { + switch (controlSocketError) { + case QAbstractSocket::ConnectionRefusedError: + q->setError(QAbstractSocket::ProxyConnectionRefusedError, + QSocks5SocketEngine::tr("Connection to proxy refused")); + break; + case QAbstractSocket::RemoteHostClosedError: + q->setError(QAbstractSocket::ProxyConnectionClosedError, + QSocks5SocketEngine::tr("Connection to proxy closed prematurely")); + break; + case QAbstractSocket::HostNotFoundError: + q->setError(QAbstractSocket::ProxyNotFoundError, + QSocks5SocketEngine::tr("Proxy host not found")); + break; + case QAbstractSocket::SocketTimeoutError: + if (state == ConnectError) { + q->setError(QAbstractSocket::ProxyConnectionTimeoutError, + QSocks5SocketEngine::tr("Connection to proxy timed out")); + break; + } + /* fall through */ + default: + q->setError(controlSocketError, data->controlSocket->errorString()); + break; + } + } else { + q->setError(controlSocketError, data->controlSocket->errorString()); + } + break; + } + + case AuthenticatingError: + q->setError(QAbstractSocket::ProxyAuthenticationRequiredError, + extraMessage.isEmpty() ? + QSocks5SocketEngine::tr("Proxy authentication failed") : + QSocks5SocketEngine::tr("Proxy authentication failed: %1").arg(extraMessage)); + break; + + case RequestError: + // error code set by caller (overload) + break; + + case SocksError: + q->setError(QAbstractSocket::ProxyProtocolError, + QSocks5SocketEngine::tr("SOCKS version 5 protocol error")); + break; + + case HostNameLookupError: + q->setError(QAbstractSocket::HostNotFoundError, + QAbstractSocket::tr("Host not found")); + break; + } + + q->setState(QAbstractSocket::UnconnectedState); + socks5State = state; +} + +void QSocks5SocketEnginePrivate::setErrorState(Socks5State state, Socks5Error socks5error) +{ + Q_Q(QSocks5SocketEngine); + switch (socks5error) { + case SocksFailure: + q->setError(QAbstractSocket::NetworkError, + QSocks5SocketEngine::tr("General SOCKSv5 server failure")); + break; + case ConnectionNotAllowed: + q->setError(QAbstractSocket::SocketAccessError, + QSocks5SocketEngine::tr("Connection not allowed by SOCKSv5 server")); + break; + case NetworkUnreachable: + q->setError(QAbstractSocket::NetworkError, + QAbstractSocket::tr("Network unreachable")); + break; + case HostUnreachable: + q->setError(QAbstractSocket::HostNotFoundError, + QAbstractSocket::tr("Host not found")); + break; + case ConnectionRefused: + q->setError(QAbstractSocket::ConnectionRefusedError, + QAbstractSocket::tr("Connection refused")); + break; + case TTLExpired: + q->setError(QAbstractSocket::NetworkError, + QSocks5SocketEngine::tr("TTL expired")); + break; + case CommandNotSupported: + q->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSocks5SocketEngine::tr("SOCKSv5 command not supported")); + break; + case AddressTypeNotSupported: + q->setError(QAbstractSocket::UnsupportedSocketOperationError, + QSocks5SocketEngine::tr("Address type not supported")); + break; + + default: + q->setError(QAbstractSocket::UnknownSocketError, + QSocks5SocketEngine::tr("Unknown SOCKSv5 proxy error code 0x%1").arg(int(socks5error), 16)); + break; + } + + setErrorState(state, QString()); +} + +void QSocks5SocketEnginePrivate::reauthenticate() +{ + Q_Q(QSocks5SocketEngine); + + // we require authentication + QAuthenticator auth; + emit q->proxyAuthenticationRequired(proxyInfo, &auth); + + if (!auth.user().isEmpty() || !auth.password().isEmpty()) { + // we have new credentials, let's try again + QSOCKS5_DEBUG << "authentication failure: retrying connection"; + socks5State = QSocks5SocketEnginePrivate::Uninitialized; + + delete data->authenticator; + proxyInfo.setUser(auth.user()); + proxyInfo.setPassword(auth.password()); + data->authenticator = new QSocks5PasswordAuthenticator(proxyInfo.user(), proxyInfo.password()); + + data->controlSocket->blockSignals(true); + data->controlSocket->abort(); + data->controlSocket->blockSignals(false); + data->controlSocket->connectToHost(proxyInfo.hostName(), proxyInfo.port()); + } else { + // authentication failure + + setErrorState(AuthenticatingError); + data->controlSocket->close(); + emitConnectionNotification(); + } +} + +void QSocks5SocketEnginePrivate::parseAuthenticationMethodReply() +{ + // not enough data to begin + if (data->controlSocket->bytesAvailable() < 2) + return; + + QByteArray buf = data->controlSocket->read(2); + if (buf.at(0) != S5_VERSION_5) { + QSOCKS5_D_DEBUG << "Socks5 version incorrect"; + setErrorState(SocksError); + data->controlSocket->close(); + emitConnectionNotification(); + return; + } + + bool authComplete = false; + if (uchar(buf.at(1)) == S5_AUTHMETHOD_NONE) { + authComplete = true; + } else if (uchar(buf.at(1)) == S5_AUTHMETHOD_NOTACCEPTABLE) { + reauthenticate(); + return; + } else if (buf.at(1) != data->authenticator->methodId() + || !data->authenticator->beginAuthenticate(data->controlSocket, &authComplete)) { + setErrorState(AuthenticatingError, QLatin1String("Socks5 host did not support authentication method.")); + socketError = QAbstractSocket::SocketAccessError; // change the socket error + emitConnectionNotification(); + return; + } + + if (authComplete) + sendRequestMethod(); + else + socks5State = Authenticating; +} + +void QSocks5SocketEnginePrivate::parseAuthenticatingReply() +{ + bool authComplete = false; + if (!data->authenticator->continueAuthenticate(data->controlSocket, &authComplete)) { + reauthenticate(); + return; + } + if (authComplete) + sendRequestMethod(); +} + +void QSocks5SocketEnginePrivate::sendRequestMethod() +{ + QHostAddress address; + quint16 port = 0; + char command = 0; + if (mode == ConnectMode) { + command = S5_CONNECT; + address = peerAddress; + port = peerPort; + } else if (mode == BindMode) { + command = S5_BIND; + address = localAddress; + port = localPort; + } else { +#ifndef QT_NO_UDPSOCKET + command = S5_UDP_ASSOCIATE; + address = localAddress; //data->controlSocket->localAddress(); + port = localPort; +#endif + } + + QByteArray buf; + buf.reserve(270); // big enough for domain name; + buf[0] = S5_VERSION_5; + buf[1] = command; + buf[2] = 0x00; + if (peerName.isEmpty() && !qt_socks5_set_host_address_and_port(address, port, &buf)) { + QSOCKS5_DEBUG << "error setting address" << address << " : " << port; + //### set error code .... + return; + } else if (!peerName.isEmpty() && !qt_socks5_set_host_name_and_port(peerName, port, &buf)) { + QSOCKS5_DEBUG << "error setting address" << address << " : " << port; + //### set error code .... + return; + } + QSOCKS5_DEBUG << "sending" << dump(buf); + QByteArray sealedBuf; + if (!data->authenticator->seal(buf, &sealedBuf)) { + // ### Handle this error. + } + data->controlSocket->write(sealedBuf); + data->controlSocket->flush(); + socks5State = RequestMethodSent; +} + +void QSocks5SocketEnginePrivate::parseRequestMethodReply() +{ + Q_Q(QSocks5SocketEngine); + QSOCKS5_DEBUG << "parseRequestMethodReply()"; + + QByteArray inBuf; + if (!data->authenticator->unSeal(data->controlSocket, &inBuf)) { + // ### check error and not just not enough data + QSOCKS5_DEBUG << "unSeal failed, needs more data"; + return; + } + QSOCKS5_DEBUG << dump(inBuf); + if (inBuf.size() < 2) { + QSOCKS5_DEBUG << "need more data for request reply header .. put this data somewhere"; + return; + } + + QHostAddress address; + quint16 port = 0; + + if (inBuf.at(0) != S5_VERSION_5 || inBuf.length() < 3 || inBuf.at(2) != 0x00) { + QSOCKS5_DEBUG << "socks protocol error"; + setErrorState(SocksError); + } else if (inBuf.at(1) != S5_SUCCESS) { + Socks5Error socks5Error = Socks5Error(inBuf.at(1)); + QSOCKS5_DEBUG << "Request error :" << socks5Error; + if ((socks5Error == SocksFailure || socks5Error == ConnectionNotAllowed) + && !peerName.isEmpty()) { + // Dante seems to use this error code to indicate hostname resolution failure + setErrorState(HostNameLookupError); + } else { + setErrorState(RequestError, socks5Error); + } + } else { + // connection success, retrieve the remote addresses + int pos = 3; + if (!qt_socks5_get_host_address_and_port(inBuf, &address, &port, &pos)) { + QSOCKS5_DEBUG << "error getting address"; + setErrorState(SocksError); + } else { + inBuf.remove(0, pos); + for (int i = inBuf.size() - 1; i >= 0 ; --i) + data->controlSocket->ungetChar(inBuf.at(i)); + } + } + + if (socks5State == RequestMethodSent) { + // no error + localAddress = address; + localPort = port; + + if (mode == ConnectMode) { + socks5State = Connected; + // notify the upper layer that we're done + q->setState(QAbstractSocket::ConnectedState); + emitConnectionNotification(); + } else if (mode == BindMode) { + socks5State = BindSuccess; + q->setState(QAbstractSocket::ListeningState); + } else { + socks5State = UdpAssociateSuccess; + } + } else if (socks5State == BindSuccess) { + // no error and we got a connection + bindData->peerAddress = address; + bindData->peerPort = port; + + emitReadNotification(); + } else { + // got an error + data->controlSocket->close(); + emitConnectionNotification(); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingReadNotification() +{ + Q_Q(QSocks5SocketEngine); + readNotificationPending = false; + if (readNotificationEnabled) { + QSOCKS5_D_DEBUG << "emitting readNotification"; + QPointer<QSocks5SocketEngine> qq = q; + emit q->readNotification(); + if (!qq) + return; + // check if there needs to be a new zero read notification + if (data && data->controlSocket->state() == QAbstractSocket::UnconnectedState + && data->controlSocket->error() == QAbstractSocket::RemoteHostClosedError) { + connectData->readBuffer.clear(); + emitReadNotification(); + } + } +} + +void QSocks5SocketEnginePrivate::emitReadNotification() +{ + Q_Q(QSocks5SocketEngine); + readNotificationActivated = true; + if (readNotificationEnabled && !readNotificationPending) { + QSOCKS5_D_DEBUG << "queueing readNotification"; + readNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingReadNotification", Qt::QueuedConnection); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingWriteNotification() +{ + writeNotificationPending = false; + Q_Q(QSocks5SocketEngine); + if (writeNotificationEnabled) { + QSOCKS5_D_DEBUG << "emitting writeNotification"; + emit q->writeNotification(); + } +} + +void QSocks5SocketEnginePrivate::emitWriteNotification() +{ + Q_Q(QSocks5SocketEngine); + writeNotificationActivated = true; + if (writeNotificationEnabled && !writeNotificationPending) { + QSOCKS5_D_DEBUG << "queueing writeNotification"; + writeNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingWriteNotification", Qt::QueuedConnection); + } +} + +void QSocks5SocketEnginePrivate::_q_emitPendingConnectionNotification() +{ + connectionNotificationPending = false; + Q_Q(QSocks5SocketEngine); + QSOCKS5_D_DEBUG << "emitting connectionNotification"; + emit q->connectionNotification(); +} + +void QSocks5SocketEnginePrivate::emitConnectionNotification() +{ + Q_Q(QSocks5SocketEngine); + QSOCKS5_D_DEBUG << "queueing connectionNotification"; + connectionNotificationPending = true; + QMetaObject::invokeMethod(q, "_q_emitPendingConnectionNotification", Qt::QueuedConnection); +} + +QSocks5SocketEngine::QSocks5SocketEngine(QObject *parent) +:QAbstractSocketEngine(*new QSocks5SocketEnginePrivate(), parent) +{ +} + +QSocks5SocketEngine::~QSocks5SocketEngine() +{ + Q_D(QSocks5SocketEngine); + + if (d->data) { + delete d->data->authenticator; + delete d->data->controlSocket; + } + if (d->connectData) + delete d->connectData; +#ifndef QT_NO_UDPSOCKET + if (d->udpData) { + delete d->udpData->udpSocket; + delete d->udpData; + } +#endif + if (d->bindData) + delete d->bindData; +} + +static QBasicAtomicInt descriptorCounter = Q_BASIC_ATOMIC_INITIALIZER(1); + +bool QSocks5SocketEngine::initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol) +{ + Q_D(QSocks5SocketEngine); + + d->socketDescriptor = descriptorCounter.fetchAndAddRelaxed(1); + + d->socketType = type; + d->socketProtocol = protocol; + + return true; +} + +bool QSocks5SocketEngine::initialize(int socketDescriptor, QAbstractSocket::SocketState socketState) +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "initialize" << socketDescriptor; + + // this is only valid for the other side of a bind, nothing else is supported + + if (socketState != QAbstractSocket::ConnectedState) { + //### must be connected state ??? + return false; + } + + QSocks5BindData *bindData = socks5BindStore()->retrieve(socketDescriptor); + if (bindData) { + + d->socketState = QAbstractSocket::ConnectedState; + d->socketType = QAbstractSocket::TcpSocket; + d->connectData = new QSocks5ConnectData; + d->data = d->connectData; + d->mode = QSocks5SocketEnginePrivate::ConnectMode; + d->data->controlSocket = bindData->controlSocket; + bindData->controlSocket = 0; + d->data->controlSocket->setParent(this); + d->socketProtocol = d->data->controlSocket->localAddress().protocol(); + d->data->authenticator = bindData->authenticator; + bindData->authenticator = 0; + d->localPort = bindData->localPort; + d->localAddress = bindData->localAddress; + d->peerPort = bindData->peerPort; + d->peerAddress = bindData->peerAddress; + delete bindData; + + QObject::connect(d->data->controlSocket, SIGNAL(connected()), this, SLOT(_q_controlSocketConnected()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(readyRead()), this, SLOT(_q_controlSocketReadNotification()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(_q_controlSocketBytesWritten()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(_q_controlSocketError(QAbstractSocket::SocketError)), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(disconnected()), this, SLOT(_q_controlSocketDisconnected()), + Qt::DirectConnection); + QObject::connect(d->data->controlSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(_q_controlSocketStateChanged(QAbstractSocket::SocketState)), + Qt::DirectConnection); + + d->socks5State = QSocks5SocketEnginePrivate::Connected; + + if (d->data->controlSocket->bytesAvailable() != 0) + d->_q_controlSocketReadNotification(); + return true; + } + return false; +} + +void QSocks5SocketEngine::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QSocks5SocketEngine); + d->proxyInfo = networkProxy; +} + +int QSocks5SocketEngine::socketDescriptor() const +{ + Q_D(const QSocks5SocketEngine); + return d->socketDescriptor; +} + +bool QSocks5SocketEngine::isValid() const +{ + Q_D(const QSocks5SocketEngine); + return d->socketType != QAbstractSocket::UnknownSocketType + && d->socks5State != QSocks5SocketEnginePrivate::SocksError + && (d->socketError == QAbstractSocket::UnknownSocketError + || d->socketError == QAbstractSocket::SocketTimeoutError + || d->socketError == QAbstractSocket::UnfinishedSocketOperationError); +} + +bool QSocks5SocketEngine::connectInternal() +{ + Q_D(QSocks5SocketEngine); + + if (!d->data) { + if (socketType() == QAbstractSocket::TcpSocket) { + d->initialize(QSocks5SocketEnginePrivate::ConnectMode); +#ifndef QT_NO_UDPSOCKET + } else if (socketType() == QAbstractSocket::UdpSocket) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); + // all udp needs to be bound + if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) + return false; + + setState(QAbstractSocket::ConnectedState); + return true; +#endif + } else { + qFatal("QSocks5SocketEngine::connectToHost: in QTcpServer mode"); + return false; + } + } + + if (d->socks5State == QSocks5SocketEnginePrivate::Uninitialized + && d->socketState != QAbstractSocket::ConnectingState) { + setState(QAbstractSocket::ConnectingState); + d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port()); + return false; + } + return false; +} + +bool QSocks5SocketEngine::connectToHost(const QHostAddress &address, quint16 port) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "connectToHost" << address << ":" << port; + + setPeerAddress(address); + setPeerPort(port); + d->peerName.clear(); + + return connectInternal(); +} + +bool QSocks5SocketEngine::connectToHostByName(const QString &hostname, quint16 port) +{ + Q_D(QSocks5SocketEngine); + + setPeerAddress(QHostAddress()); + setPeerPort(port); + d->peerName = hostname; + + return connectInternal(); +} + +void QSocks5SocketEnginePrivate::_q_controlSocketConnected() +{ + QSOCKS5_DEBUG << "_q_controlSocketConnected"; + QByteArray buf(3, 0); + buf[0] = S5_VERSION_5; + buf[1] = 0x01; + buf[2] = data->authenticator->methodId(); + data->controlSocket->write(buf); + socks5State = AuthenticationMethodsSent; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketReadNotification() +{ + QSOCKS5_D_DEBUG << "_q_controlSocketReadNotification socks5state" << s5StateToString(socks5State) + << "bytes available" << data->controlSocket->bytesAvailable(); + + if (data->controlSocket->bytesAvailable() == 0) { + QSOCKS5_D_DEBUG << "########## bogus read why do we get these ... on windows only"; + return; + } + + switch (socks5State) { + case AuthenticationMethodsSent: + parseAuthenticationMethodReply(); + break; + case Authenticating: + parseAuthenticatingReply(); + break; + case RequestMethodSent: + parseRequestMethodReply(); + break; + case Connected: { + QByteArray buf; + if (!data->authenticator->unSeal(data->controlSocket, &buf)) { + // qDebug() << "unseal error maybe need to wait for more data"; + } + if (buf.size()) { + QSOCKS5_DEBUG << dump(buf); + connectData->readBuffer += buf; + emitReadNotification(); + } + break; + } + case BindSuccess: + // only get here if command is bind + if (mode == BindMode) { + parseRequestMethodReply(); + break; + } + + // fall through + default: + qWarning("QSocks5SocketEnginePrivate::_q_controlSocketReadNotification: " + "Unexpectedly received data while in state=%d and mode=%d", + socks5State, mode); + break; + }; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketBytesWritten() +{ + QSOCKS5_DEBUG << "_q_controlSocketBytesWritten"; + + if (socks5State != Connected + || (mode == ConnectMode + && data->controlSocket->bytesToWrite())) + return; + if (data->controlSocket->bytesToWrite() < MaxWriteBufferSize) { + emitWriteNotification(); + writeNotificationActivated = false; + } +} + +void QSocks5SocketEnginePrivate::_q_controlSocketError(QAbstractSocket::SocketError error) +{ + QSOCKS5_D_DEBUG << "controlSocketError" << error << data->controlSocket->errorString(); + + if (error == QAbstractSocket::SocketTimeoutError) + return; // ignore this error -- comes from the waitFor* functions + + if (error == QAbstractSocket::RemoteHostClosedError + && socks5State == Connected) { + // clear the read buffer in connect mode so that bytes available returns 0 + // if there already is a read notification pending then this will be processed first + if (!readNotificationPending) + connectData->readBuffer.clear(); + emitReadNotification(); + } else if (socks5State == Uninitialized + || socks5State == AuthenticationMethodsSent + || socks5State == Authenticating + || socks5State == RequestMethodSent) { + setErrorState(socks5State == Uninitialized ? ConnectError : ControlSocketError); + data->controlSocket->close(); + emitConnectionNotification(); + } else { + q_func()->setError(data->controlSocket->error(), data->controlSocket->errorString()); + emitReadNotification(); + } +} + +void QSocks5SocketEnginePrivate::_q_controlSocketDisconnected() +{ + QSOCKS5_D_DEBUG << "_q_controlSocketDisconnected"; +} + +void QSocks5SocketEnginePrivate::_q_controlSocketStateChanged(QAbstractSocket::SocketState state) +{ + QSOCKS5_D_DEBUG << "_q_controlSocketStateChanged" << state; +} + +#ifndef QT_NO_UDPSOCKET +void QSocks5SocketEnginePrivate::checkForDatagrams() const +{ + // udp should be unbuffered so we need to do some polling at certain points + if (udpData->udpSocket->hasPendingDatagrams()) + const_cast<QSocks5SocketEnginePrivate *>(this)->_q_udpSocketReadNotification(); +} + +void QSocks5SocketEnginePrivate::_q_udpSocketReadNotification() +{ + QSOCKS5_D_DEBUG << "_q_udpSocketReadNotification()"; + + // check some state stuff + if (!udpData->udpSocket->hasPendingDatagrams()) { + QSOCKS5_D_DEBUG << "false read ??"; + return; + } + + while (udpData->udpSocket->hasPendingDatagrams()) { + QByteArray sealedBuf(udpData->udpSocket->pendingDatagramSize(), 0); + QSOCKS5_D_DEBUG << "new datagram"; + udpData->udpSocket->readDatagram(sealedBuf.data(), sealedBuf.size()); + QByteArray inBuf; + if (!data->authenticator->unSeal(sealedBuf, &inBuf)) { + QSOCKS5_D_DEBUG << "failed unsealing datagram discarding"; + return; + } + QSOCKS5_DEBUG << dump(inBuf); + int pos = 0; + const char *buf = inBuf.constData(); + if (inBuf.size() < 4) { + QSOCKS5_D_DEBUG << "bugus udp data, discarding"; + return; + } + QSocks5RevivedDatagram datagram; + if (buf[pos++] != 0 || buf[pos++] != 0) { + QSOCKS5_D_DEBUG << "invalid datagram discarding"; + return; + } + if (buf[pos++] != 0) { //### add fragmentation reading support + QSOCKS5_D_DEBUG << "don't support fragmentation yet disgarding"; + return; + } + if (!qt_socks5_get_host_address_and_port(inBuf, &datagram.address, &datagram.port, &pos)) { + QSOCKS5_D_DEBUG << "failed to get address from datagram disgarding"; + return; + } + datagram.data = QByteArray(&buf[pos], inBuf.size() - pos); + udpData->pendingDatagrams.enqueue(datagram); + } + emitReadNotification(); +} +#endif // QT_NO_UDPSOCKET + +bool QSocks5SocketEngine::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QSocks5SocketEngine); + + // when bind wee will block until the bind is finished as the info from the proxy server is needed + + if (!d->data) { + if (socketType() == QAbstractSocket::TcpSocket) { + d->initialize(QSocks5SocketEnginePrivate::BindMode); +#ifndef QT_NO_UDPSOCKET + } else if (socketType() == QAbstractSocket::UdpSocket) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); +#endif + } else { + //### something invalid + return false; + } + } + +#ifndef QT_NO_UDPSOCKET + if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + if (!d->udpData->udpSocket->bind(address, port)) { + QSOCKS5_Q_DEBUG << "local udp bind failed"; + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + return false; + } + d->localAddress = d->udpData->udpSocket->localAddress(); + d->localPort = d->udpData->udpSocket->localPort(); + } else +#endif + if (d->mode == QSocks5SocketEnginePrivate::BindMode) { + d->localAddress = address; + d->localPort = port; + } else { + //### something invalid + return false; + } + + int msecs = SOCKS5_BLOCKING_BIND_TIMEOUT; + QTime stopWatch; + stopWatch.start(); + d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port()); + if (!d->waitForConnected(msecs, 0) || + d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) { + // waitForConnected sets the error state and closes the socket + QSOCKS5_Q_DEBUG << "waitForConnected to proxy server" << d->data->controlSocket->errorString(); + return false; + } + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) { + setState(QAbstractSocket::BoundState); + return true; +#ifndef QT_NO_UDPSOCKET + } else if (d->socks5State == QSocks5SocketEnginePrivate::UdpAssociateSuccess) { + setState(QAbstractSocket::BoundState); + d->udpData->associateAddress = d->localAddress; + d->localAddress = QHostAddress(); + d->udpData->associatePort = d->localPort; + d->localPort = 0; + QUdpSocket dummy; + dummy.setProxy(QNetworkProxy::NoProxy); + if (!dummy.bind() + || writeDatagram(0,0, d->data->controlSocket->localAddress(), dummy.localPort()) != 0 + || !dummy.waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed())) + || dummy.readDatagram(0,0, &d->localAddress, &d->localPort) != 0) { + QSOCKS5_DEBUG << "udp actual address and port lookup failed"; + setState(QAbstractSocket::UnconnectedState); + setError(dummy.error(), dummy.errorString()); + d->data->controlSocket->close(); + //### reset and error + return false; + } + QSOCKS5_DEBUG << "udp actual address and port" << d->localAddress << ":" << d->localPort; + return true; +#endif // QT_NO_UDPSOCKET + } + + // binding timed out + setError(QAbstractSocket::SocketTimeoutError, + QLatin1String(QT_TRANSLATE_NOOP("QSocks5SocketEngine", "Network operation timed out"))); + +///### delete d->udpSocket; +///### d->udpSocket = 0; + return false; +} + + +bool QSocks5SocketEngine::listen() +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "listen()"; + + // check that we are in bound and then go to listening. + if (d->socketState == QAbstractSocket::BoundState) { + d->socketState = QAbstractSocket::ListeningState; + + // check if we already have a connection + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) + d->emitReadNotification(); + + return true; + } + return false; +} + +int QSocks5SocketEngine::accept() +{ + Q_D(QSocks5SocketEngine); + // check we are listing --- + + QSOCKS5_Q_DEBUG << "accept()"; + + if (d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) { + QSOCKS5_Q_DEBUG << "BindSuccess adding" << d->socketDescriptor << "to the bind store"; + d->data->controlSocket->disconnect(); + d->data->controlSocket->setParent(0); + d->bindData->localAddress = d->localAddress; + d->bindData->localPort = d->localPort; + int sd = d->socketDescriptor; + socks5BindStore()->add(sd, d->bindData); + d->data = 0; + d->bindData = 0; + d->socketDescriptor = 0; + //### do something about this socket layer ... set it closed and an error about why ... + // reset state and local port/address + d->socks5State = QSocks5SocketEnginePrivate::Uninitialized; // ..?? + d->socketState = QAbstractSocket::UnconnectedState; + return sd; + } + return -1; +} + +void QSocks5SocketEngine::close() +{ + QSOCKS5_Q_DEBUG << "close()"; + Q_D(QSocks5SocketEngine); + if (d->data && d->data->controlSocket) { + if (d->data->controlSocket->state() == QAbstractSocket::ConnectedState) { + int msecs = 100; + QTime stopWatch; + stopWatch.start(); + while (!d->data->controlSocket->bytesToWrite()) { + if (!d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed()))) + break; + } + } + d->data->controlSocket->close(); + } +#ifndef QT_NO_UDPSOCKET + if (d->udpData && d->udpData->udpSocket) + d->udpData->udpSocket->close(); +#endif +} + +qint64 QSocks5SocketEngine::bytesAvailable() const +{ + Q_D(const QSocks5SocketEngine); + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) + return d->connectData->readBuffer.size(); +#ifndef QT_NO_UDPSOCKET + else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode + && !d->udpData->pendingDatagrams.isEmpty()) + return d->udpData->pendingDatagrams.first().data.size(); +#endif + return 0; +} + +qint64 QSocks5SocketEngine::read(char *data, qint64 maxlen) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_Q_DEBUG << "read( , maxlen = " << maxlen << ")"; + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) { + if (d->connectData->readBuffer.size() == 0) { + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) { + //imitate remote closed + close(); + setError(QAbstractSocket::RemoteHostClosedError, + QLatin1String("Remote host closed connection###")); + setState(QAbstractSocket::UnconnectedState); + return -1; + } else { + return 0; // nothing to be read + } + } + qint64 copy = qMin<qint64>(d->connectData->readBuffer.size(), maxlen); + memcpy(data, d->connectData->readBuffer.constData(), copy); + d->connectData->readBuffer.remove(0, copy); + QSOCKS5_DEBUG << "read" << dump(QByteArray(data, copy)); + return copy; +#ifndef QT_NO_UDPSOCKET + } else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + return readDatagram(data, maxlen); +#endif + } + return 0; +} + +qint64 QSocks5SocketEngine::write(const char *data, qint64 len) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_Q_DEBUG << "write" << dump(QByteArray(data, len)); + + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) { + // clamp down the amount of bytes to transfer at once + len = qMin<qint64>(len, MaxWriteBufferSize) - d->data->controlSocket->bytesToWrite(); + if (len <= 0) + return 0; + + QByteArray buf = QByteArray::fromRawData(data, len); + QByteArray sealedBuf; + if (!d->data->authenticator->seal(buf, &sealedBuf)) { + // ### Handle this error. + } + + d->data->controlSocket->write(sealedBuf); + d->data->controlSocket->waitForBytesWritten(0); + return len; +#ifndef QT_NO_UDPSOCKET + } else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) { + // send to connected address + return writeDatagram(data, len, d->peerAddress, d->peerPort); +#endif + } + //### set an error ??? + return -1; +} + +#ifndef QT_NO_UDPSOCKET +qint64 QSocks5SocketEngine::readDatagram(char *data, qint64 maxlen, QHostAddress *addr, + quint16 *port) +{ + Q_D(QSocks5SocketEngine); + + d->checkForDatagrams(); + + if (d->udpData->pendingDatagrams.isEmpty()) + return 0; + + QSocks5RevivedDatagram datagram = d->udpData->pendingDatagrams.dequeue(); + int copyLen = qMin<int>(maxlen, datagram.data.size()); + memcpy(data, datagram.data.constData(), copyLen); + if (addr) + *addr = datagram.address; + if (port) + *port = datagram.port; + return copyLen; +} + +qint64 QSocks5SocketEngine::writeDatagram(const char *data, qint64 len, const QHostAddress &address, + quint16 port) +{ + Q_D(QSocks5SocketEngine); + + // it is possible to send with out first binding with udp, but socks5 requires a bind. + if (!d->data) { + d->initialize(QSocks5SocketEnginePrivate::UdpAssociateMode); + // all udp needs to be bound + if (!bind(QHostAddress(QLatin1String("0.0.0.0")), 0)) { + //### set error + return -1; + } + } + + QByteArray outBuf; + outBuf.reserve(270 + len); + outBuf[0] = 0x00; + outBuf[1] = 0x00; + outBuf[2] = 0x00; + if (!qt_socks5_set_host_address_and_port(address, port, &outBuf)) { + } + outBuf += QByteArray(data, len); + QSOCKS5_DEBUG << "sending" << dump(outBuf); + QByteArray sealedBuf; + if (!d->data->authenticator->seal(outBuf, &sealedBuf)) { + QSOCKS5_DEBUG << "sealing data failed"; + setError(QAbstractSocket::SocketAccessError, d->data->authenticator->errorString()); + return -1; + } + if (d->udpData->udpSocket->writeDatagram(sealedBuf, d->udpData->associateAddress, d->udpData->associatePort) != sealedBuf.size()) { + //### try frgamenting + if (d->udpData->udpSocket->error() == QAbstractSocket::DatagramTooLargeError) + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + //### else maybe more serious error + return -1; + } + + return len; +} + +bool QSocks5SocketEngine::hasPendingDatagrams() const +{ + Q_D(const QSocks5SocketEngine); + Q_INIT_CHECK(false); + + d->checkForDatagrams(); + + return !d->udpData->pendingDatagrams.isEmpty(); +} + +qint64 QSocks5SocketEngine::pendingDatagramSize() const +{ + Q_D(const QSocks5SocketEngine); + + d->checkForDatagrams(); + + if (!d->udpData->pendingDatagrams.isEmpty()) + return d->udpData->pendingDatagrams.head().data.size(); + return 0; +} +#endif // QT_NO_UDPSOCKET + +int QSocks5SocketEngine::option(SocketOption option) const +{ + Q_UNUSED(option); + return -1; +} + +bool QSocks5SocketEngine::setOption(SocketOption option, int value) +{ + Q_UNUSED(option); + Q_UNUSED(value); + return false; +} + +bool QSocks5SocketEnginePrivate::waitForConnected(int msecs, bool *timedOut) +{ + if (data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return false; + + const Socks5State wantedState = + mode == ConnectMode ? Connected : + mode == BindMode ? BindSuccess : + UdpAssociateSuccess; + + QTime stopWatch; + stopWatch.start(); + + while (socks5State != wantedState) { + if (!data->controlSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + setErrorState(QSocks5SocketEnginePrivate::ControlSocketError); + if (timedOut && data->controlSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } + + return true; +} + +bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "waitForRead" << msecs; + + d->readNotificationActivated = false; + + QTime stopWatch; + stopWatch.start(); + + // are we connected yet? + if (!d->waitForConnected(msecs, timedOut)) + return false; + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + // we're connected + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode || + d->mode == QSocks5SocketEnginePrivate::BindMode) { + while (!d->readNotificationActivated) { + if (!d->data->controlSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + setError(d->data->controlSocket->error(), d->data->controlSocket->errorString()); + if (timedOut && d->data->controlSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } +#ifndef QT_NO_UDPSOCKET + } else { + while (!d->readNotificationActivated) { + if (!d->udpData->udpSocket->waitForReadyRead(qt_timeout_value(msecs, stopWatch.elapsed()))) { + setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString()); + if (timedOut && d->udpData->udpSocket->error() == QAbstractSocket::SocketTimeoutError) + *timedOut = true; + return false; + } + } +#endif // QT_NO_UDPSOCKET + } + + + bool ret = d->readNotificationActivated; + d->readNotificationActivated = false; + + QSOCKS5_DEBUG << "waitForRead returned" << ret; + return ret; +} + + +bool QSocks5SocketEngine::waitForWrite(int msecs, bool *timedOut) +{ + Q_D(QSocks5SocketEngine); + QSOCKS5_DEBUG << "waitForWrite" << msecs; + + QTime stopWatch; + stopWatch.start(); + + // are we connected yet? + if (!d->waitForConnected(msecs, timedOut)) + return false; + if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) + return true; + + // we're connected + + // flush any bytes we may still have buffered in the time that we have left + if (d->data->controlSocket->bytesToWrite()) + d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed())); + while ((msecs == -1 || stopWatch.elapsed() < msecs) + && d->data->controlSocket->state() == QAbstractSocket::ConnectedState + && d->data->controlSocket->bytesToWrite() >= MaxWriteBufferSize) + d->data->controlSocket->waitForBytesWritten(qt_timeout_value(msecs, stopWatch.elapsed())); + return d->data->controlSocket->bytesToWrite() < MaxWriteBufferSize; +} + +bool QSocks5SocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_UNUSED(checkRead); + if (!checkWrite) { + bool canRead = waitForRead(msecs, timedOut); + if (readyToRead) + *readyToRead = canRead; + return canRead; + } + + bool canWrite = waitForWrite(msecs, timedOut); + if (readyToWrite) + *readyToWrite = canWrite; + return canWrite; +} + +bool QSocks5SocketEngine::isReadNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->readNotificationEnabled; +} + +void QSocks5SocketEngine::setReadNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + + QSOCKS5_Q_DEBUG << "setReadNotificationEnabled(" << enable << ")"; + + bool emitSignal = false; + if (!d->readNotificationEnabled + && enable) { + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode) + emitSignal = !d->connectData->readBuffer.isEmpty(); +#ifndef QT_NO_UDPSOCKET + else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode) + emitSignal = !d->udpData->pendingDatagrams.isEmpty(); +#endif + else if (d->mode == QSocks5SocketEnginePrivate::BindMode + && d->socketState == QAbstractSocket::ListeningState + && d->socks5State == QSocks5SocketEnginePrivate::BindSuccess) + emitSignal = true; + } + + d->readNotificationEnabled = enable; + + if (emitSignal) + d->emitReadNotification(); +} + +bool QSocks5SocketEngine::isWriteNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->writeNotificationEnabled; +} + +void QSocks5SocketEngine::setWriteNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + d->writeNotificationEnabled = enable; + if (enable && d->socketState == QAbstractSocket::ConnectedState) { + if (d->mode == QSocks5SocketEnginePrivate::ConnectMode && d->data->controlSocket->bytesToWrite()) + return; // will be emitted as a result of bytes written + d->emitWriteNotification(); + d->writeNotificationActivated = false; + } +} + +bool QSocks5SocketEngine::isExceptionNotificationEnabled() const +{ + Q_D(const QSocks5SocketEngine); + return d->exceptNotificationEnabled; +} + +void QSocks5SocketEngine::setExceptionNotificationEnabled(bool enable) +{ + Q_D(QSocks5SocketEngine); + d->exceptNotificationEnabled = enable; +} + +QAbstractSocketEngine * +QSocks5SocketEngineHandler::createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &proxy, QObject *parent) +{ + Q_UNUSED(socketType); + + // proxy type must have been resolved by now + if (proxy.type() != QNetworkProxy::Socks5Proxy) { + QSOCKS5_DEBUG << "not proxying"; + return 0; + } + QSocks5SocketEngine *engine = new QSocks5SocketEngine(parent); + engine->setProxy(proxy); + return engine; +} + +QAbstractSocketEngine *QSocks5SocketEngineHandler::createSocketEngine(int socketDescriptor, QObject *parent) +{ + QSOCKS5_DEBUG << "createSocketEngine" << socketDescriptor; + if (socks5BindStore()->contains(socketDescriptor)) { + QSOCKS5_DEBUG << "bind store contains" << socketDescriptor; + return new QSocks5SocketEngine(parent); + } + return 0; +} + +#endif // QT_NO_SOCKS5 + +QT_END_NAMESPACE diff --git a/src/network/socket/qsocks5socketengine_p.h b/src/network/socket/qsocks5socketengine_p.h new file mode 100644 index 0000000000..e11b1b6e27 --- /dev/null +++ b/src/network/socket/qsocks5socketengine_p.h @@ -0,0 +1,288 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSOCKS5SOCKETENGINE_P_H +#define QSOCKS5SOCKETENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qabstractsocketengine_p.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SOCKS5 + +class QSocks5SocketEnginePrivate; + +class Q_AUTOTEST_EXPORT QSocks5SocketEngine : public QAbstractSocketEngine +{ + Q_OBJECT +public: + QSocks5SocketEngine(QObject *parent = 0); + ~QSocks5SocketEngine(); + + bool initialize(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::IPv4Protocol); + bool initialize(int socketDescriptor, QAbstractSocket::SocketState socketState = QAbstractSocket::ConnectedState); + + void setProxy(const QNetworkProxy &networkProxy); + + int socketDescriptor() const; + + bool isValid() const; + + bool connectInternal(); + bool connectToHost(const QHostAddress &address, quint16 port); + bool connectToHostByName(const QString &name, quint16 port); + bool bind(const QHostAddress &address, quint16 port); + bool listen(); + int accept(); + void close(); + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxlen); + qint64 write(const char *data, qint64 len); + +#ifndef QT_NO_UDPSOCKET + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *addr = 0, + quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &addr, + quint16 port); + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; +#endif // QT_NO_UDPSOCKET + + int option(SocketOption option) const; + bool setOption(SocketOption option, int value); + + bool waitForRead(int msecs = 30000, bool *timedOut = 0); + bool waitForWrite(int msecs = 30000, bool *timedOut = 0); + bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite, + bool checkRead, bool checkWrite, + int msecs = 30000, bool *timedOut = 0); + + bool isReadNotificationEnabled() const; + void setReadNotificationEnabled(bool enable); + bool isWriteNotificationEnabled() const; + void setWriteNotificationEnabled(bool enable); + bool isExceptionNotificationEnabled() const; + void setExceptionNotificationEnabled(bool enable); + +private: + Q_DECLARE_PRIVATE(QSocks5SocketEngine) + Q_DISABLE_COPY(QSocks5SocketEngine) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketConnected()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketReadNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketError(QAbstractSocket::SocketError)) +#ifndef QT_NO_UDPSOCKET + Q_PRIVATE_SLOT(d_func(), void _q_udpSocketReadNotification()) +#endif + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketBytesWritten()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingReadNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingWriteNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_emitPendingConnectionNotification()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketDisconnected()) + Q_PRIVATE_SLOT(d_func(), void _q_controlSocketStateChanged(QAbstractSocket::SocketState)) + +}; + + +class QTcpSocket; + +class QSocks5Authenticator +{ +public: + QSocks5Authenticator(); + virtual ~QSocks5Authenticator(); + virtual char methodId(); + virtual bool beginAuthenticate(QTcpSocket *socket, bool *completed); + virtual bool continueAuthenticate(QTcpSocket *socket, bool *completed); + + virtual bool seal(const QByteArray buf, QByteArray *sealedBuf); + virtual bool unSeal(const QByteArray sealedBuf, QByteArray *buf); + virtual bool unSeal(QTcpSocket *sealedSocket, QByteArray *buf); + + virtual QString errorString() { return QString(); } +}; + +class QSocks5PasswordAuthenticator : public QSocks5Authenticator +{ +public: + QSocks5PasswordAuthenticator(const QString &userName, const QString &password); + char methodId(); + bool beginAuthenticate(QTcpSocket *socket, bool *completed); + bool continueAuthenticate(QTcpSocket *socket, bool *completed); + + QString errorString(); + +private: + QString userName; + QString password; +}; + +struct QSocks5Data; +struct QSocks5ConnectData; +struct QSocks5UdpAssociateData; +struct QSocks5BindData; + +class QSocks5SocketEnginePrivate : public QAbstractSocketEnginePrivate +{ + Q_DECLARE_PUBLIC(QSocks5SocketEngine) +public: + QSocks5SocketEnginePrivate(); + ~QSocks5SocketEnginePrivate(); + + enum Socks5State + { + Uninitialized = 0, + ConnectError, + AuthenticationMethodsSent, + Authenticating, + AuthenticatingError, + RequestMethodSent, + RequestError, + Connected, + UdpAssociateSuccess, + BindSuccess, + ControlSocketError, + SocksError, + HostNameLookupError + }; + Socks5State socks5State; + + enum Socks5Mode + { + NoMode, + ConnectMode, + BindMode, + UdpAssociateMode + }; + Socks5Mode mode; + + enum Socks5Error + { + SocksFailure = 0x01, + ConnectionNotAllowed = 0x02, + NetworkUnreachable = 0x03, + HostUnreachable = 0x04, + ConnectionRefused = 0x05, + TTLExpired = 0x06, + CommandNotSupported = 0x07, + AddressTypeNotSupported = 0x08, + LastKnownError = AddressTypeNotSupported, + UnknownError + }; + + void initialize(Socks5Mode socks5Mode); + + void setErrorState(Socks5State state, const QString &extraMessage = QString()); + void setErrorState(Socks5State state, Socks5Error socks5error); + + void reauthenticate(); + void parseAuthenticationMethodReply(); + void parseAuthenticatingReply(); + void sendRequestMethod(); + void parseRequestMethodReply(); + void parseNewConnection(); + + bool waitForConnected(int msecs, bool *timedOut); + + void _q_controlSocketConnected(); + void _q_controlSocketReadNotification(); + void _q_controlSocketError(QAbstractSocket::SocketError); +#ifndef QT_NO_UDPSOCKET + void checkForDatagrams() const; + void _q_udpSocketReadNotification(); +#endif + void _q_controlSocketBytesWritten(); + void _q_controlSocketDisconnected(); + void _q_controlSocketStateChanged(QAbstractSocket::SocketState); + + QNetworkProxy proxyInfo; + + bool readNotificationEnabled, writeNotificationEnabled, exceptNotificationEnabled; + + int socketDescriptor; + + QSocks5Data *data; + QSocks5ConnectData *connectData; +#ifndef QT_NO_UDPSOCKET + QSocks5UdpAssociateData *udpData; +#endif + QSocks5BindData *bindData; + QString peerName; + + mutable bool readNotificationActivated; + mutable bool writeNotificationActivated; + + bool readNotificationPending; + void _q_emitPendingReadNotification(); + void emitReadNotification(); + bool writeNotificationPending; + void _q_emitPendingWriteNotification(); + void emitWriteNotification(); + bool connectionNotificationPending; + void _q_emitPendingConnectionNotification(); + void emitConnectionNotification(); +}; + +class Q_AUTOTEST_EXPORT QSocks5SocketEngineHandler : public QSocketEngineHandler +{ +public: + virtual QAbstractSocketEngine *createSocketEngine(QAbstractSocket::SocketType socketType, + const QNetworkProxy &, QObject *parent); + virtual QAbstractSocketEngine *createSocketEngine(int socketDescripter, QObject *parent); +}; + + +QT_END_NAMESPACE +#endif // QT_NO_SOCKS5 +#endif // QSOCKS5SOCKETENGINE_H diff --git a/src/network/socket/qtcpserver.cpp b/src/network/socket/qtcpserver.cpp new file mode 100644 index 0000000000..c1fedc396d --- /dev/null +++ b/src/network/socket/qtcpserver.cpp @@ -0,0 +1,643 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QTCPSERVER_DEBUG + +/*! \class QTcpServer + + \brief The QTcpServer class provides a TCP-based server. + + \reentrant + \ingroup io + \inmodule QtNetwork + + This class makes it possible to accept incoming TCP connections. + You can specify the port or have QTcpServer pick one + automatically. You can listen on a specific address or on all the + machine's addresses. + + Call listen() to have the server listen for incoming connections. + The newConnection() signal is then emitted each time a client + connects to the server. + + Call nextPendingConnection() to accept the pending connection as + a connected QTcpSocket. The function returns a pointer to a + QTcpSocket in QAbstractSocket::ConnectedState that you can use for + communicating with the client. + + If an error occurs, serverError() returns the type of error, and + errorString() can be called to get a human readable description of + what happened. + + When listening for connections, the address and port on which the + server is listening are available as serverAddress() and + serverPort(). + + Calling close() makes QTcpServer stop listening for incoming + connections. + + Although QTcpServer is mostly designed for use with an event + loop, it's possible to use it without one. In that case, you must + use waitForNewConnection(), which blocks until either a + connection is available or a timeout expires. + + \sa QTcpSocket, {Fortune Server Example}, {Threaded Fortune Server Example}, + {Loopback Example}, {Torrent Example} +*/ + +/*! \fn void QTcpServer::newConnection() + + This signal is emitted every time a new connection is available. + + \sa hasPendingConnections(), nextPendingConnection() +*/ + +#include "private/qobject_p.h" +#include "qalgorithms.h" +#include "qhostaddress.h" +#include "qlist.h" +#include "qpointer.h" +#include "qnativesocketengine_p.h" +#include "qtcpserver.h" +#include "qtcpsocket.h" +#include "qnetworkproxy.h" + +QT_BEGIN_NAMESPACE + +#define Q_CHECK_SOCKETENGINE(returnValue) do { \ + if (!d->socketEngine) { \ + return returnValue; \ + } } while (0) + +class QTcpServerPrivate : public QObjectPrivate, public QAbstractSocketEngineReceiver +{ + Q_DECLARE_PUBLIC(QTcpServer) +public: + QTcpServerPrivate(); + ~QTcpServerPrivate(); + + QList<QTcpSocket *> pendingConnections; + + quint16 port; + QHostAddress address; + + QAbstractSocket::SocketState state; + QAbstractSocketEngine *socketEngine; + + QAbstractSocket::SocketError serverSocketError; + QString serverSocketErrorString; + + int maxConnections; + +#ifndef QT_NO_NETWORKPROXY + QNetworkProxy proxy; + QNetworkProxy resolveProxy(const QHostAddress &address, quint16 port); +#endif + + // from QAbstractSocketEngineReceiver + void readNotification(); + inline void writeNotification() {} + inline void exceptionNotification() {} + inline void connectionNotification() {} +#ifndef QT_NO_NETWORKPROXY + inline void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *) {} +#endif + +}; + +/*! \internal +*/ +QTcpServerPrivate::QTcpServerPrivate() + : port(0) + , state(QAbstractSocket::UnconnectedState) + , socketEngine(0) + , serverSocketError(QAbstractSocket::UnknownSocketError) + , maxConnections(30) +{ +} + +/*! \internal +*/ +QTcpServerPrivate::~QTcpServerPrivate() +{ +} + +#ifndef QT_NO_NETWORKPROXY +/*! \internal + + Resolve the proxy to its final value. +*/ +QNetworkProxy QTcpServerPrivate::resolveProxy(const QHostAddress &address, quint16 port) +{ + if (address == QHostAddress::LocalHost || + address == QHostAddress::LocalHostIPv6) + return QNetworkProxy::NoProxy; + + QList<QNetworkProxy> proxies; + if (proxy.type() != QNetworkProxy::DefaultProxy) { + // a non-default proxy was set with setProxy + proxies << proxy; + } else { + // try the application settings instead + QNetworkProxyQuery query(port, QString(), QNetworkProxyQuery::TcpServer); + proxies = QNetworkProxyFactory::proxyForQuery(query); + } + + // return the first that we can use + foreach (const QNetworkProxy &p, proxies) { + if (p.capabilities() & QNetworkProxy::ListeningCapability) + return p; + } + + // no proxy found + // DefaultProxy will raise an error + return QNetworkProxy(QNetworkProxy::DefaultProxy); +} +#endif + +/*! \internal +*/ +void QTcpServerPrivate::readNotification() +{ + Q_Q(QTcpServer); + for (;;) { + if (pendingConnections.count() >= maxConnections) { +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServerPrivate::_q_processIncomingConnection() too many connections"); +#endif + if (socketEngine->isReadNotificationEnabled()) + socketEngine->setReadNotificationEnabled(false); + return; + } + + int descriptor = socketEngine->accept(); + if (descriptor == -1) + break; +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServerPrivate::_q_processIncomingConnection() accepted socket %i", descriptor); +#endif + q->incomingConnection(descriptor); + + QPointer<QTcpServer> that = q; + emit q->newConnection(); + if (!that || !q->isListening()) + return; + } +} + +/*! + Constructs a QTcpServer object. + + \a parent is passed to the QObject constructor. + + \sa listen(), setSocketDescriptor() +*/ +QTcpServer::QTcpServer(QObject *parent) + : QObject(*new QTcpServerPrivate, parent) +{ +} + +/*! + Destroys the QTcpServer object. If the server is listening for + connections, the socket is automatically closed. + + Any client \l{QTcpSocket}s that are still connected must either + disconnect or be reparented before the server is deleted. + + \sa close() +*/ +QTcpServer::~QTcpServer() +{ + close(); +} + +/*! + Tells the server to listen for incoming connections on address \a + address and port \a port. If \a port is 0, a port is chosen + automatically. If \a address is QHostAddress::Any, the server + will listen on all network interfaces. + + Returns true on success; otherwise returns false. + + \sa isListening() +*/ +bool QTcpServer::listen(const QHostAddress &address, quint16 port) +{ + Q_D(QTcpServer); + if (d->state == QAbstractSocket::ListeningState) { + qWarning("QTcpServer::listen() called when already listening"); + return false; + } + + QAbstractSocket::NetworkLayerProtocol proto = address.protocol(); + +#ifdef QT_NO_NETWORKPROXY + static const QNetworkProxy &proxy = *(QNetworkProxy *)0; +#else + QNetworkProxy proxy = d->resolveProxy(address, port); +#endif + + delete d->socketEngine; + d->socketEngine = QAbstractSocketEngine::createSocketEngine(QAbstractSocket::TcpSocket, proxy, this); + if (!d->socketEngine) { + d->serverSocketError = QAbstractSocket::UnsupportedSocketOperationError; + d->serverSocketErrorString = tr("Operation on socket is not supported"); + return false; + } + if (!d->socketEngine->initialize(QAbstractSocket::TcpSocket, proto)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + +#if defined(Q_OS_UNIX) + // Under Unix, we want to be able to bind to the port, even if a socket on + // the same address-port is in TIME_WAIT. Under Windows this is possible + // anyway -- furthermore, the meaning of reusable on Windows is different: + // it means that you can use the same address-port for multiple listening + // sockets. + // Don't abort though if we can't set that option. For example the socks + // engine doesn't support that option, but that shouldn't prevent us from + // trying to bind/listen. + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); +#endif + + if (!d->socketEngine->bind(address, port)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + if (!d->socketEngine->listen()) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + d->socketEngine->setReceiver(d); + d->socketEngine->setReadNotificationEnabled(true); + + d->state = QAbstractSocket::ListeningState; + d->address = d->socketEngine->localAddress(); + d->port = d->socketEngine->localPort(); + +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::listen(%i, \"%s\") == true (listening on port %i)", port, + address.toString().toLatin1().constData(), d->socketEngine->localPort()); +#endif + return true; +} + +/*! + Returns true if the server is currently listening for incoming + connections; otherwise returns false. + + \sa listen() +*/ +bool QTcpServer::isListening() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(false); + return d->socketEngine->state() == QAbstractSocket::ListeningState; +} + +/*! + Closes the server. The server will no longer listen for incoming + connections. + + \sa listen() +*/ +void QTcpServer::close() +{ + Q_D(QTcpServer); + + qDeleteAll(d->pendingConnections); + d->pendingConnections.clear(); + + if (d->socketEngine) { + d->socketEngine->close(); + d->socketEngine->deleteLater(); + d->socketEngine = 0; + } + + d->state = QAbstractSocket::UnconnectedState; +} + +/*! + Returns the native socket descriptor the server uses to listen + for incoming instructions, or -1 if the server is not listening. + + If the server is using QNetworkProxy, the returned descriptor may + not be usable with native socket functions. + + \sa setSocketDescriptor(), isListening() +*/ +int QTcpServer::socketDescriptor() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(-1); + return d->socketEngine->socketDescriptor(); +} + +/*! + Sets the socket descriptor this server should use when listening + for incoming connections to \a socketDescriptor. Returns true if + the socket is set successfully; otherwise returns false. + + The socket is assumed to be in listening state. + + \sa socketDescriptor(), isListening() +*/ +bool QTcpServer::setSocketDescriptor(int socketDescriptor) +{ + Q_D(QTcpServer); + if (isListening()) { + qWarning("QTcpServer::setSocketDescriptor() called when already listening"); + return false; + } + + if (d->socketEngine) + delete d->socketEngine; + d->socketEngine = QAbstractSocketEngine::createSocketEngine(socketDescriptor, this); + if (!d->socketEngine->initialize(socketDescriptor, QAbstractSocket::ListeningState)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::setSocketDescriptor(%i) failed (%s)", socketDescriptor, + d->serverSocketErrorString.toLatin1().constData()); +#endif + return false; + } + + d->socketEngine->setReceiver(d); + d->socketEngine->setReadNotificationEnabled(true); + + d->state = d->socketEngine->state(); + d->address = d->socketEngine->localAddress(); + d->port = d->socketEngine->localPort(); + +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::setSocketDescriptor(%i) succeeded.", socketDescriptor); +#endif + return true; +} + +/*! + Returns the server's port if the server is listening for + connections; otherwise returns 0. + + \sa serverAddress(), listen() +*/ +quint16 QTcpServer::serverPort() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(0); + return d->socketEngine->localPort(); +} + +/*! + Returns the server's address if the server is listening for + connections; otherwise returns QHostAddress::Null. + + \sa serverPort(), listen() +*/ +QHostAddress QTcpServer::serverAddress() const +{ + Q_D(const QTcpServer); + Q_CHECK_SOCKETENGINE(QHostAddress(QHostAddress::Null)); + return d->socketEngine->localAddress(); +} + +/*! + Waits for at most \a msec milliseconds or until an incoming + connection is available. Returns true if a connection is + available; otherwise returns false. If the operation timed out + and \a timedOut is not 0, *\a timedOut will be set to true. + + This is a blocking function call. Its use is disadvised in a + single-threaded GUI application, since the whole application will + stop responding until the function returns. + waitForNewConnection() is mostly useful when there is no event + loop available. + + The non-blocking alternative is to connect to the newConnection() + signal. + + If msec is -1, this function will not time out. + + \sa hasPendingConnections(), nextPendingConnection() +*/ +bool QTcpServer::waitForNewConnection(int msec, bool *timedOut) +{ + Q_D(QTcpServer); + if (d->state != QAbstractSocket::ListeningState) + return false; + + if (!d->socketEngine->waitForRead(msec, timedOut)) { + d->serverSocketError = d->socketEngine->error(); + d->serverSocketErrorString = d->socketEngine->errorString(); + return false; + } + + if (timedOut && *timedOut) + return false; + + d->readNotification(); + + return true; +} + +/*! + Returns true if the server has a pending connection; otherwise + returns false. + + \sa nextPendingConnection(), setMaxPendingConnections() +*/ +bool QTcpServer::hasPendingConnections() const +{ + return !d_func()->pendingConnections.isEmpty(); +} + +/*! + Returns the next pending connection as a connected QTcpSocket + object. + + The socket is created as a child of the server, which means that + it is automatically deleted when the QTcpServer object is + destroyed. It is still a good idea to delete the object + explicitly when you are done with it, to avoid wasting memory. + + 0 is returned if this function is called when there are no pending + connections. + + \sa hasPendingConnections() +*/ +QTcpSocket *QTcpServer::nextPendingConnection() +{ + Q_D(QTcpServer); + if (d->pendingConnections.isEmpty()) + return 0; + + if (!d->socketEngine->isReadNotificationEnabled()) + d->socketEngine->setReadNotificationEnabled(true); + + return d->pendingConnections.takeFirst(); +} + +/*! + This virtual function is called by QTcpServer when a new + connection is available. The \a socketDescriptor argument is the + native socket descriptor for the accepted connection. + + The base implementation creates a QTcpSocket, sets the socket + descriptor and then stores the QTcpSocket in an internal list of + pending connections. Finally newConnection() is emitted. + + Reimplement this function to alter the server's behavior when a + connection is available. + + If this server is using QNetworkProxy then the \a socketDescriptor + may not be usable with native socket functions, and should only be + used with QTcpSocket::setSocketDescriptor(). + + \sa newConnection(), nextPendingConnection() +*/ +void QTcpServer::incomingConnection(int socketDescriptor) +{ +#if defined (QTCPSERVER_DEBUG) + qDebug("QTcpServer::incomingConnection(%i)", socketDescriptor); +#endif + + QTcpSocket *socket = new QTcpSocket(this); + socket->setSocketDescriptor(socketDescriptor); + d_func()->pendingConnections.append(socket); +} + +/*! + Sets the maximum number of pending accepted connections to \a + numConnections. QTcpServer will accept no more than \a + numConnections incoming connections before + nextPendingConnection() is called. By default, the limit is 30 + pending connections. + + Clients may still able to connect after the server has reached + its maximum number of pending connections (i.e., QTcpSocket can + still emit the connected() signal). QTcpServer will stop + accepting the new connections, but the operating system may + still keep them in queue. + + \sa maxPendingConnections(), hasPendingConnections() +*/ +void QTcpServer::setMaxPendingConnections(int numConnections) +{ + d_func()->maxConnections = numConnections; +} + +/*! + Returns the maximum number of pending accepted connections. The + default is 30. + + \sa setMaxPendingConnections(), hasPendingConnections() +*/ +int QTcpServer::maxPendingConnections() const +{ + return d_func()->maxConnections; +} + +/*! + Returns an error code for the last error that occurred. + + \sa errorString() +*/ +QAbstractSocket::SocketError QTcpServer::serverError() const +{ + return d_func()->serverSocketError; +} + +/*! + Returns a human readable description of the last error that + occurred. + + \sa serverError() +*/ +QString QTcpServer::errorString() const +{ + return d_func()->serverSocketErrorString; +} + +#ifndef QT_NO_NETWORKPROXY +/*! + \since 4.1 + + Sets the explicit network proxy for this socket to \a networkProxy. + + To disable the use of a proxy for this socket, use the + QNetworkProxy::NoProxy proxy type: + + \snippet doc/src/snippets/code/src_network_socket_qtcpserver.cpp 0 + + \sa proxy(), QNetworkProxy +*/ +void QTcpServer::setProxy(const QNetworkProxy &networkProxy) +{ + Q_D(QTcpServer); + d->proxy = networkProxy; +} + +/*! + \since 4.1 + + Returns the network proxy for this socket. + By default QNetworkProxy::DefaultProxy is used. + + \sa setProxy(), QNetworkProxy +*/ +QNetworkProxy QTcpServer::proxy() const +{ + Q_D(const QTcpServer); + return d->proxy; +} +#endif // QT_NO_NETWORKPROXY + +QT_END_NAMESPACE + +#include "moc_qtcpserver.cpp" + diff --git a/src/network/socket/qtcpserver.h b/src/network/socket/qtcpserver.h new file mode 100644 index 0000000000..71fdde8c2d --- /dev/null +++ b/src/network/socket/qtcpserver.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTCPSERVER_H +#define QTCPSERVER_H + +#include <QtCore/qobject.h> +#include <QtNetwork/qabstractsocket.h> +#include <QtNetwork/qhostaddress.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QTcpServerPrivate; +#ifndef QT_NO_NETWORKPROXY +class QNetworkProxy; +#endif +class QTcpSocket; + +class Q_NETWORK_EXPORT QTcpServer : public QObject +{ + Q_OBJECT +public: + explicit QTcpServer(QObject *parent = 0); + virtual ~QTcpServer(); + + bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); + void close(); + + bool isListening() const; + + void setMaxPendingConnections(int numConnections); + int maxPendingConnections() const; + + quint16 serverPort() const; + QHostAddress serverAddress() const; + + int socketDescriptor() const; + bool setSocketDescriptor(int socketDescriptor); + + bool waitForNewConnection(int msec = 0, bool *timedOut = 0); + virtual bool hasPendingConnections() const; + virtual QTcpSocket *nextPendingConnection(); + + QAbstractSocket::SocketError serverError() const; + QString errorString() const; + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + +protected: + virtual void incomingConnection(int handle); + +Q_SIGNALS: + void newConnection(); + +private: + Q_DISABLE_COPY(QTcpServer) + Q_DECLARE_PRIVATE(QTcpServer) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTCPSERVER_H diff --git a/src/network/socket/qtcpsocket.cpp b/src/network/socket/qtcpsocket.cpp new file mode 100644 index 0000000000..086a4206bc --- /dev/null +++ b/src/network/socket/qtcpsocket.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QTCPSOCKET_DEBUG + +/*! + \class QTcpSocket + + \brief The QTcpSocket class provides a TCP socket. + + \reentrant + \ingroup io + \inmodule QtNetwork + + TCP (Transmission Control Protocol) is a reliable, + stream-oriented, connection-oriented transport protocol. It is + especially well suited for continuous transmission of data. + + QTcpSocket is a convenience subclass of QAbstractSocket that + allows you to establish a TCP connection and transfer streams of + data. See the QAbstractSocket documentation for details. + + \bold{Note:} TCP sockets cannot be opened in QIODevice::Unbuffered mode. + + \sa QTcpServer, QUdpSocket, QFtp, QHttp, {Fortune Server Example}, + {Fortune Client Example}, {Threaded Fortune Server Example}, + {Blocking Fortune Client Example}, {Loopback Example}, + {Torrent Example} +*/ + +#include "qlist.h" +#include "qtcpsocket_p.h" +#include "qtcpsocket.h" +#include "qhostaddress.h" + +QT_BEGIN_NAMESPACE + +/*! + Creates a QTcpSocket object in state \c UnconnectedState. + + \a parent is passed on to the QObject constructor. + + \sa socketType() +*/ +QTcpSocket::QTcpSocket(QObject *parent) + : QAbstractSocket(TcpSocket, *new QTcpSocketPrivate, parent) +{ +#if defined(QTCPSOCKET_DEBUG) + qDebug("QTcpSocket::QTcpSocket()"); +#endif + d_func()->isBuffered = true; +} + +/*! + Destroys the socket, closing the connection if necessary. + + \sa close() +*/ + +QTcpSocket::~QTcpSocket() +{ +#if defined(QTCPSOCKET_DEBUG) + qDebug("QTcpSocket::~QTcpSocket()"); +#endif +} + +/*! + \internal +*/ +QTcpSocket::QTcpSocket(QTcpSocketPrivate &dd, QObject *parent) + : QAbstractSocket(TcpSocket, dd, parent) +{ + d_func()->isBuffered = true; +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qtcpsocket.h b/src/network/socket/qtcpsocket.h new file mode 100644 index 0000000000..ade70becae --- /dev/null +++ b/src/network/socket/qtcpsocket.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTCPSOCKET_H +#define QTCPSOCKET_H + +#include <QtNetwork/qabstractsocket.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +class QTcpSocketPrivate; + +class Q_NETWORK_EXPORT QTcpSocket : public QAbstractSocket +{ + Q_OBJECT +public: + explicit QTcpSocket(QObject *parent = 0); + virtual ~QTcpSocket(); + +protected: + QTcpSocket(QTcpSocketPrivate &dd, QObject *parent = 0); + +private: + Q_DISABLE_COPY(QTcpSocket) + Q_DECLARE_PRIVATE(QTcpSocket) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QTCPSOCKET_H diff --git a/src/network/socket/qtcpsocket_p.h b/src/network/socket/qtcpsocket_p.h new file mode 100644 index 0000000000..3016cdbb5e --- /dev/null +++ b/src/network/socket/qtcpsocket_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTCPSOCKET_P_H +#define QTCPSOCKET_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QLibrary class. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/qtcpsocket.h> +#include <private/qabstractsocket_p.h> + +QT_BEGIN_NAMESPACE + +class QTcpSocketPrivate : public QAbstractSocketPrivate +{ + Q_DECLARE_PUBLIC(QTcpSocket) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp new file mode 100644 index 0000000000..820404db0f --- /dev/null +++ b/src/network/socket/qudpsocket.cpp @@ -0,0 +1,424 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define QUDPSOCKET_DEBUG + +/*! \class QUdpSocket + + \reentrant + \brief The QUdpSocket class provides a UDP socket. + + \ingroup io + \inmodule QtNetwork + + UDP (User Datagram Protocol) is a lightweight, unreliable, + datagram-oriented, connectionless protocol. It can be used when + reliability isn't important. QUdpSocket is a subclass of + QAbstractSocket that allows you to send and receive UDP + datagrams. + + The most common way to use this class is to bind to an address and port + using bind(), then call writeDatagram() and readDatagram() to transfer + data. If you want to use the standard QIODevice functions read(), + readLine(), write(), etc., you must first connect the socket directly to a + peer by calling connectToHost(). + + The socket emits the bytesWritten() signal every time a datagram + is written to the network. If you just want to send datagrams, + you don't need to call bind(). + + The readyRead() signal is emitted whenever datagrams arrive. In + that case, hasPendingDatagrams() returns true. Call + pendingDatagramSize() to obtain the size of the first pending + datagram, and readDatagram() to read it. + + Example: + + \snippet doc/src/snippets/code/src_network_socket_qudpsocket.cpp 0 + + With QUdpSocket, you can also establish a virtual connection to a + UDP server using connectToHost() and then use read() and write() + to exchange datagrams without specifying the receiver for each + datagram. + + The \l{network/broadcastsender}{Broadcast Sender} and + \l{network/broadcastreceiver}{Broadcast Receiver} examples + illustrate how to use QUdpSocket in applications. + + \sa QTcpSocket +*/ + +/*! \enum QUdpSocket::BindFlag + \since 4.1 + + This enum describes the different flags you can pass to modify the + behavior of QUdpSocket::bind(). + + \value ShareAddress Allow other services to bind to the same address + and port. This is useful when multiple processes share + the load of a single service by listening to the same address and port + (e.g., a web server with several pre-forked listeners can greatly + improve response time). However, because any service is allowed to + rebind, this option is subject to certain security considerations. + Note that by combining this option with ReuseAddressHint, you will + also allow your service to rebind an existing shared address. On + Unix, this is equivalent to the SO_REUSEADDR socket option. On Windows, + this option is ignored. + + \value DontShareAddress Bind the address and port exclusively, so that + no other services are allowed to rebind. By passing this option to + QUdpSocket::bind(), you are guaranteed that on successs, your service + is the only one that listens to the address and port. No services are + allowed to rebind, even if they pass ReuseAddressHint. This option + provides more security than ShareAddress, but on certain operating + systems, it requires you to run the server with administrator privileges. + On Unix and Mac OS X, not sharing is the default behavior for binding + an address and port, so this option is ignored. On Windows, this + option uses the SO_EXCLUSIVEADDRUSE socket option. + + \value ReuseAddressHint Provides a hint to QUdpSocket that it should try + to rebind the service even if the address and port are already bound by + another socket. On Windows, this is equivalent to the SO_REUSEADDR + socket option. On Unix, this option is ignored. + + \value DefaultForPlatform The default option for the current platform. + On Unix and Mac OS X, this is equivalent to (DontShareAddress + + ReuseAddressHint), and on Windows, its equivalent to ShareAddress. +*/ + +#include "qhostaddress.h" +#include "qabstractsocket_p.h" +#include "qudpsocket.h" + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_UDPSOCKET + +#define QT_CHECK_BOUND(function, a) do { \ + if (!isValid()) { \ + qWarning(function" called on a QUdpSocket when not in QUdpSocket::BoundState"); \ + return (a); \ + } } while (0) + +class QUdpSocketPrivate : public QAbstractSocketPrivate +{ + Q_DECLARE_PUBLIC(QUdpSocket) + + bool doEnsureInitialized(const QHostAddress &bindAddress, quint16 bindPort, + const QHostAddress &remoteAddress); +public: + inline bool ensureInitialized(const QHostAddress &bindAddress, quint16 bindPort) + { return doEnsureInitialized(bindAddress, bindPort, QHostAddress()); } + + inline bool ensureInitialized(const QHostAddress &remoteAddress) + { return doEnsureInitialized(QHostAddress(), 0, remoteAddress); } +}; + +bool QUdpSocketPrivate::doEnsureInitialized(const QHostAddress &bindAddress, quint16 bindPort, + const QHostAddress &remoteAddress) +{ + const QHostAddress *address = &bindAddress; + QAbstractSocket::NetworkLayerProtocol proto = address->protocol(); + if (proto == QUdpSocket::UnknownNetworkLayerProtocol) { + address = &remoteAddress; + proto = address->protocol(); + } + +#if defined(QT_NO_IPV6) + Q_Q(QUdpSocket); + if (proto == QUdpSocket::IPv6Protocol) { + socketError = QUdpSocket::UnsupportedSocketOperationError; + q->setErrorString(QUdpSocket::tr("This platform does not support IPv6")); + return false; + } +#endif + + // now check if the socket engine is initialized and to the right type + if (!socketEngine || !socketEngine->isValid() || socketEngine->protocol() != proto) { + resolveProxy(remoteAddress.toString(), bindPort); + if (!initSocketLayer(address->protocol())) + return false; + } + + return true; +} + +/*! + Creates a QUdpSocket object. + + \a parent is passed to the QObject constructor. + + \sa socketType() +*/ +QUdpSocket::QUdpSocket(QObject *parent) + : QAbstractSocket(UdpSocket, *new QUdpSocketPrivate, parent) +{ + d_func()->isBuffered = false; +} + +/*! + Destroys the socket, closing the connection if necessary. + + \sa close() +*/ +QUdpSocket::~QUdpSocket() +{ +} + +/*! + Binds this socket to the address \a address and the port \a port. + When bound, the signal readyRead() is emitted whenever a UDP + datagram arrives on the specified address and port. This function + is useful to write UDP servers. + + On success, the functions returns true and the socket enters + BoundState; otherwise it returns false. + + The socket is bound using the DefaultForPlatform BindMode. + + \sa readDatagram() +*/ +bool QUdpSocket::bind(const QHostAddress &address, quint16 port) +{ + Q_D(QUdpSocket); + if (!d->ensureInitialized(address, port)) + return false; + + bool result = d_func()->socketEngine->bind(address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (!result) { + d->socketError = d_func()->socketEngine->error(); + setErrorString(d_func()->socketEngine->errorString()); + emit error(d_func()->socketError); + return false; + } + + d->state = BoundState; + d->localAddress = d->socketEngine->localAddress(); + d->localPort = d->socketEngine->localPort(); + + emit stateChanged(d_func()->state); + d_func()->socketEngine->setReadNotificationEnabled(true); + return true; +} + +/*! + \since 4.1 + \overload + + Binds to \a address on port \a port, using the BindMode \a mode. +*/ +bool QUdpSocket::bind(const QHostAddress &address, quint16 port, BindMode mode) +{ + Q_D(QUdpSocket); + if (!d->ensureInitialized(address, port)) + return false; + +#ifdef Q_OS_UNIX + if ((mode & ShareAddress) || (mode & ReuseAddressHint)) + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); +#endif +#ifdef Q_OS_WIN + if (mode & ReuseAddressHint) + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 0); + if (mode & DontShareAddress) + d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 1); + else + d->socketEngine->setOption(QAbstractSocketEngine::BindExclusively, 0); +#endif + bool result = d_func()->socketEngine->bind(address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (!result) { + d->socketError = d_func()->socketEngine->error(); + setErrorString(d_func()->socketEngine->errorString()); + emit error(d_func()->socketError); + return false; + } + + d->state = BoundState; + d->localAddress = d->socketEngine->localAddress(); + d->localPort = d->socketEngine->localPort(); + + emit stateChanged(d_func()->state); + d_func()->socketEngine->setReadNotificationEnabled(true); + return true; +} + +/*! \overload + + Binds to QHostAddress:Any on port \a port. +*/ +bool QUdpSocket::bind(quint16 port) +{ + return bind(QHostAddress::Any, port); +} + +/*! + \since 4.1 + \overload + + Binds to QHostAddress:Any on port \a port, using the BindMode \a mode. +*/ +bool QUdpSocket::bind(quint16 port, BindMode mode) +{ + return bind(QHostAddress::Any, port, mode); +} + +/*! + Returns true if at least one datagram is waiting to be read; + otherwise returns false. + + \sa pendingDatagramSize(), readDatagram() +*/ +bool QUdpSocket::hasPendingDatagrams() const +{ + QT_CHECK_BOUND("QUdpSocket::hasPendingDatagrams()", false); + return d_func()->socketEngine->hasPendingDatagrams(); +} + +/*! + Returns the size of the first pending UDP datagram. If there is + no datagram available, this function returns -1. + + \sa hasPendingDatagrams(), readDatagram() +*/ +qint64 QUdpSocket::pendingDatagramSize() const +{ + QT_CHECK_BOUND("QUdpSocket::pendingDatagramSize()", -1); + return d_func()->socketEngine->pendingDatagramSize(); +} + +/*! + Sends the datagram at \a data of size \a size to the host + address \a address at port \a port. Returns the number of + bytes sent on success; otherwise returns -1. + + Datagrams are always written as one block. The maximum size of a + datagram is highly platform-dependent, but can be as low as 8192 + bytes. If the datagram is too large, this function will return -1 + and error() will return DatagramTooLargeError. + + Sending datagrams larger than 512 bytes is in general disadvised, + as even if they are sent successfully, they are likely to be + fragmented by the IP layer before arriving at their final + destination. + + \warning Calling this function on a connected UDP socket may + result in an error and no packet being sent. If you are using a + connected socket, use write() to send datagrams. + + \sa readDatagram(), write() +*/ +qint64 QUdpSocket::writeDatagram(const char *data, qint64 size, const QHostAddress &address, + quint16 port) +{ + Q_D(QUdpSocket); +#if defined QUDPSOCKET_DEBUG + qDebug("QUdpSocket::writeDatagram(%p, %llu, \"%s\", %i)", data, size, + address.toString().toLatin1().constData(), port); +#endif + if (!d->ensureInitialized(address)) + return -1; + + qint64 sent = d->socketEngine->writeDatagram(data, size, address, port); + d->cachedSocketDescriptor = d->socketEngine->socketDescriptor(); + + if (sent >= 0) { + emit bytesWritten(sent); + } else { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + emit error(d->socketError); + } + return sent; +} + +/*! + \fn qint64 QUdpSocket::writeDatagram(const QByteArray &datagram, + const QHostAddress &host, quint16 port) + \overload + + Sends the datagram \a datagram to the host address \a host and at + port \a port. +*/ + +/*! + Receives a datagram no larger than \a maxSize bytes and stores + it in \a data. The sender's host address and port is stored in + *\a address and *\a port (unless the pointers are 0). + + Returns the size of the datagram on success; otherwise returns + -1. + + If \a maxSize is too small, the rest of the datagram will be + lost. To avoid loss of data, call pendingDatagramSize() to + determine the size of the pending datagram before attempting to + read it. If \a maxSize is 0, the datagram will be discarded. + + \sa writeDatagram(), hasPendingDatagrams(), pendingDatagramSize() +*/ +qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address, + quint16 *port) +{ + Q_D(QUdpSocket); + +#if defined QUDPSOCKET_DEBUG + qDebug("QUdpSocket::readDatagram(%p, %llu, %p, %p)", data, maxSize, address, port); +#endif + QT_CHECK_BOUND("QUdpSocket::readDatagram()", -1); + qint64 readBytes = d->socketEngine->readDatagram(data, maxSize, address, port); + d_func()->socketEngine->setReadNotificationEnabled(true); + if (readBytes < 0) { + d->socketError = d->socketEngine->error(); + setErrorString(d->socketEngine->errorString()); + emit error(d->socketError); + } + return readBytes; +} +#endif // QT_NO_UDPSOCKET + +QT_END_NAMESPACE diff --git a/src/network/socket/qudpsocket.h b/src/network/socket/qudpsocket.h new file mode 100644 index 0000000000..4e02f8402d --- /dev/null +++ b/src/network/socket/qudpsocket.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QUDPSOCKET_H +#define QUDPSOCKET_H + +#include <QtNetwork/qabstractsocket.h> +#include <QtNetwork/qhostaddress.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Network) + +#ifndef QT_NO_UDPSOCKET + +class QUdpSocketPrivate; + +class Q_NETWORK_EXPORT QUdpSocket : public QAbstractSocket +{ + Q_OBJECT +public: + enum BindFlag { + DefaultForPlatform = 0x0, + ShareAddress = 0x1, + DontShareAddress = 0x2, + ReuseAddressHint = 0x4 + }; + Q_DECLARE_FLAGS(BindMode, BindFlag) + + explicit QUdpSocket(QObject *parent = 0); + virtual ~QUdpSocket(); + + bool bind(const QHostAddress &address, quint16 port); + bool bind(quint16 port = 0); + bool bind(const QHostAddress &address, quint16 port, BindMode mode); + bool bind(quint16 port, BindMode mode); + // ### Qt 5: Merge the bind functions + + bool hasPendingDatagrams() const; + qint64 pendingDatagramSize() const; + qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *host = 0, quint16 *port = 0); + qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port); + inline qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port) + { return writeDatagram(datagram.constData(), datagram.size(), host, port); } + +private: + Q_DISABLE_COPY(QUdpSocket) + Q_DECLARE_PRIVATE(QUdpSocket) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QUdpSocket::BindMode) + +#endif // QT_NO_UDPSOCKET + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QUDPSOCKET_H diff --git a/src/network/socket/socket.pri b/src/network/socket/socket.pri new file mode 100644 index 0000000000..b1fe64ad97 --- /dev/null +++ b/src/network/socket/socket.pri @@ -0,0 +1,46 @@ +# Qt network socket + +HEADERS += socket/qabstractsocketengine_p.h \ + socket/qnativesocketengine_p.h \ + socket/qhttpsocketengine_p.h \ + socket/qsocks5socketengine_p.h \ + socket/qabstractsocket.h \ + socket/qabstractsocket_p.h \ + socket/qtcpsocket.h \ + socket/qudpsocket.h \ + socket/qtcpserver.h \ + socket/qlocalserver.h \ + socket/qlocalserver_p.h \ + socket/qlocalsocket.h \ + socket/qlocalsocket_p.h + +SOURCES += socket/qabstractsocketengine.cpp \ + socket/qnativesocketengine.cpp \ + socket/qhttpsocketengine.cpp \ + socket/qsocks5socketengine.cpp \ + socket/qabstractsocket.cpp \ + socket/qtcpsocket.cpp \ + socket/qudpsocket.cpp \ + socket/qtcpserver.cpp \ + socket/qlocalsocket.cpp \ + socket/qlocalserver.cpp + +unix:SOURCES += socket/qnativesocketengine_unix.cpp \ + socket/qlocalsocket_unix.cpp \ + socket/qlocalserver_unix.cpp + + +win32:SOURCES += socket/qnativesocketengine_win.cpp \ + socket/qlocalsocket_win.cpp \ + socket/qlocalserver_win.cpp + +wince*: { + SOURCES -= socket/qlocalsocket_win.cpp \ + socket/qlocalserver_win.cpp + SOURCES += socket/qlocalsocket_tcp.cpp \ + socket/qlocalserver_tcp.cpp + + DEFINES += QT_LOCALSOCKET_TCP +} + + |