File indexing completed on 2024-04-28 15:26:13

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
0004     SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0005     SPDX-FileCopyrightText: 2007 Thiago Macieira <thiago@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "connectionbackend_p.h"
0011 #include <KLocalizedString>
0012 #include <QCoreApplication>
0013 #include <QElapsedTimer>
0014 #include <QFile>
0015 #include <QLocalServer>
0016 #include <QLocalSocket>
0017 #include <QPointer>
0018 #include <QStandardPaths>
0019 #include <QTemporaryFile>
0020 #include <cerrno>
0021 
0022 #include "kiocoredebug.h"
0023 
0024 using namespace KIO;
0025 
0026 ConnectionBackend::ConnectionBackend(QObject *parent)
0027     : QObject(parent)
0028     , state(Idle)
0029     , socket(nullptr)
0030     , len(-1)
0031     , cmd(0)
0032     , signalEmitted(false)
0033 {
0034     localServer = nullptr;
0035 }
0036 
0037 ConnectionBackend::~ConnectionBackend()
0038 {
0039 }
0040 
0041 void ConnectionBackend::setSuspended(bool enable)
0042 {
0043     if (state != Connected) {
0044         return;
0045     }
0046     Q_ASSERT(socket);
0047     Q_ASSERT(!localServer); // !tcpServer as well
0048 
0049     if (enable) {
0050         // qCDebug(KIO_CORE) << socket << "suspending";
0051         socket->setReadBufferSize(1);
0052     } else {
0053         // qCDebug(KIO_CORE) << socket << "resuming";
0054         // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119
0055         socket->setReadBufferSize(StandardBufferSize);
0056         if (socket->bytesAvailable() >= HeaderSize) {
0057             // there are bytes available
0058             QMetaObject::invokeMethod(this, &ConnectionBackend::socketReadyRead, Qt::QueuedConnection);
0059         }
0060 
0061         // We read all bytes here, but we don't use readAll() because we need
0062         // to read at least one byte (even if there isn't any) so that the
0063         // socket notifier is re-enabled
0064         QByteArray data = socket->read(socket->bytesAvailable() + 1);
0065         for (int i = data.size(); --i >= 0;) {
0066             socket->ungetChar(data[i]);
0067         }
0068         // Workaround Qt5 bug, readyRead isn't always emitted here...
0069         QMetaObject::invokeMethod(this, &ConnectionBackend::socketReadyRead, Qt::QueuedConnection);
0070     }
0071 }
0072 
0073 bool ConnectionBackend::connectToRemote(const QUrl &url)
0074 {
0075     Q_ASSERT(state == Idle);
0076     Q_ASSERT(!socket);
0077     Q_ASSERT(!localServer); // !tcpServer as well
0078 
0079     QLocalSocket *sock = new QLocalSocket(this);
0080     QString path = url.path();
0081     sock->connectToServer(path);
0082     socket = sock;
0083 
0084     connect(socket, &QIODevice::readyRead, this, &ConnectionBackend::socketReadyRead);
0085     connect(socket, &QLocalSocket::disconnected, this, &ConnectionBackend::socketDisconnected);
0086     state = Connected;
0087     return true;
0088 }
0089 
0090 void ConnectionBackend::socketDisconnected()
0091 {
0092     state = Idle;
0093     Q_EMIT disconnected();
0094 }
0095 
0096 bool ConnectionBackend::listenForRemote()
0097 {
0098     Q_ASSERT(state == Idle);
0099     Q_ASSERT(!socket);
0100     Q_ASSERT(!localServer); // !tcpServer as well
0101 
0102     const QString prefix = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
0103     static QBasicAtomicInt s_socketCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
0104     QString appName = QCoreApplication::instance()->applicationName();
0105     appName.replace(QLatin1Char('/'), QLatin1Char('_')); // #357499
0106     QTemporaryFile socketfile(prefix + QLatin1Char('/') + appName + QStringLiteral("XXXXXX.%1.kioworker.socket").arg(s_socketCounter.fetchAndAddAcquire(1)));
0107     if (!socketfile.open()) {
0108         errorString = i18n("Unable to create KIO worker: %1", QString::fromUtf8(strerror(errno)));
0109         return false;
0110     }
0111 
0112     QString sockname = socketfile.fileName();
0113     address.clear();
0114     address.setScheme(QStringLiteral("local"));
0115     address.setPath(sockname);
0116     socketfile.setAutoRemove(false);
0117     socketfile.remove(); // can't bind if there is such a file
0118 
0119     localServer = new QLocalServer(this);
0120     if (!localServer->listen(sockname)) {
0121         errorString = localServer->errorString();
0122         delete localServer;
0123         localServer = nullptr;
0124         return false;
0125     }
0126 
0127     connect(localServer, &QLocalServer::newConnection, this, &ConnectionBackend::newConnection);
0128 
0129     state = Listening;
0130     return true;
0131 }
0132 
0133 bool ConnectionBackend::waitForIncomingTask(int ms)
0134 {
0135     Q_ASSERT(state == Connected);
0136     Q_ASSERT(socket);
0137     if (socket->state() != QLocalSocket::LocalSocketState::ConnectedState) {
0138         state = Idle;
0139         return false; // socket has probably closed, what do we do?
0140     }
0141 
0142     signalEmitted = false;
0143     if (socket->bytesAvailable()) {
0144         socketReadyRead();
0145     }
0146     if (signalEmitted) {
0147         return true; // there was enough data in the socket
0148     }
0149 
0150     // not enough data in the socket, so wait for more
0151     QElapsedTimer timer;
0152     timer.start();
0153 
0154     while (socket->state() == QLocalSocket::LocalSocketState::ConnectedState && !signalEmitted && (ms == -1 || timer.elapsed() < ms)) {
0155         if (!socket->waitForReadyRead(ms == -1 ? -1 : ms - timer.elapsed())) {
0156             break;
0157         }
0158     }
0159 
0160     if (signalEmitted) {
0161         return true;
0162     }
0163     if (socket->state() != QLocalSocket::LocalSocketState::ConnectedState) {
0164         state = Idle;
0165     }
0166     return false;
0167 }
0168 
0169 bool ConnectionBackend::sendCommand(int cmd, const QByteArray &data) const
0170 {
0171     Q_ASSERT(state == Connected);
0172     Q_ASSERT(socket);
0173 
0174     char buffer[HeaderSize + 2];
0175 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0176     sprintf(buffer, "%6llx_%2x_", data.size(), cmd);
0177 #else
0178     sprintf(buffer, "%6x_%2x_", data.size(), cmd);
0179 #endif
0180     socket->write(buffer, HeaderSize);
0181     socket->write(data);
0182 
0183     // qCDebug(KIO_CORE) << this << "Sending command" << hex << cmd << "of"
0184     //         << data.size() << "bytes (" << socket->bytesToWrite()
0185     //         << "bytes left to write )";
0186 
0187     // blocking mode:
0188     while (socket->bytesToWrite() > 0 && socket->state() == QLocalSocket::LocalSocketState::ConnectedState) {
0189         socket->waitForBytesWritten(-1);
0190     }
0191 
0192     return socket->state() == QLocalSocket::LocalSocketState::ConnectedState;
0193 }
0194 
0195 ConnectionBackend *ConnectionBackend::nextPendingConnection()
0196 {
0197     Q_ASSERT(state == Listening);
0198     Q_ASSERT(localServer);
0199     Q_ASSERT(!socket);
0200 
0201     // qCDebug(KIO_CORE) << "Got a new connection";
0202 
0203     QLocalSocket *newSocket = localServer->nextPendingConnection();
0204 
0205     if (!newSocket) {
0206         return nullptr; // there was no connection...
0207     }
0208 
0209     ConnectionBackend *result = new ConnectionBackend();
0210     result->state = Connected;
0211     result->socket = newSocket;
0212     newSocket->setParent(result);
0213     connect(newSocket, &QIODevice::readyRead, result, &ConnectionBackend::socketReadyRead);
0214     connect(newSocket, &QLocalSocket::disconnected, result, &ConnectionBackend::socketDisconnected);
0215 
0216     return result;
0217 }
0218 
0219 void ConnectionBackend::socketReadyRead()
0220 {
0221     bool shouldReadAnother;
0222     do {
0223         if (!socket)
0224         // might happen if the invokeMethods were delivered after we disconnected
0225         {
0226             return;
0227         }
0228 
0229         // qCDebug(KIO_CORE) << this << "Got" << socket->bytesAvailable() << "bytes";
0230         if (len == -1) {
0231             // We have to read the header
0232             char buffer[HeaderSize];
0233 
0234             if (socket->bytesAvailable() < HeaderSize) {
0235                 return; // wait for more data
0236             }
0237 
0238             socket->read(buffer, sizeof buffer);
0239             buffer[6] = 0;
0240             buffer[9] = 0;
0241 
0242             char *p = buffer;
0243             while (*p == ' ') {
0244                 p++;
0245             }
0246             len = strtol(p, nullptr, 16);
0247 
0248             p = buffer + 7;
0249             while (*p == ' ') {
0250                 p++;
0251             }
0252             cmd = strtol(p, nullptr, 16);
0253 
0254             // qCDebug(KIO_CORE) << this << "Beginning of command" << hex << cmd << "of size" << len;
0255         }
0256 
0257         QPointer<ConnectionBackend> that = this;
0258 
0259         // qCDebug(KIO_CORE) << socket << "Want to read" << len << "bytes";
0260         if (socket->bytesAvailable() >= len) {
0261             Task task;
0262             task.cmd = cmd;
0263             if (len) {
0264                 task.data = socket->read(len);
0265             }
0266             len = -1;
0267 
0268             signalEmitted = true;
0269             Q_EMIT commandReceived(task);
0270         } else if (len > StandardBufferSize) {
0271             qCDebug(KIO_CORE) << socket << "Jumbo packet of" << len << "bytes";
0272             // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119
0273             socket->setReadBufferSize(len + 1);
0274         }
0275 
0276         // If we're dead, better don't try anything.
0277         if (that.isNull()) {
0278             return;
0279         }
0280 
0281         // Do we have enough for an another read?
0282         if (len == -1) {
0283             shouldReadAnother = socket->bytesAvailable() >= HeaderSize;
0284         } else {
0285             shouldReadAnother = socket->bytesAvailable() >= len;
0286         }
0287     } while (shouldReadAnother);
0288 }
0289 
0290 #include "moc_connectionbackend_p.cpp"