File indexing completed on 2024-04-14 03:52:48

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     // KF6 TODO: check if this breaks 32bit support,
0176     // see https://invent.kde.org/frameworks/kio/-/merge_requests/1141#note_606633
0177     sprintf(buffer, "%6llx_%2x_", data.size(), cmd);
0178     socket->write(buffer, HeaderSize);
0179     socket->write(data);
0180 
0181     // qCDebug(KIO_CORE) << this << "Sending command" << hex << cmd << "of"
0182     //         << data.size() << "bytes (" << socket->bytesToWrite()
0183     //         << "bytes left to write )";
0184 
0185     // blocking mode:
0186     while (socket->bytesToWrite() > 0 && socket->state() == QLocalSocket::LocalSocketState::ConnectedState) {
0187         socket->waitForBytesWritten(-1);
0188     }
0189 
0190     return socket->state() == QLocalSocket::LocalSocketState::ConnectedState;
0191 }
0192 
0193 ConnectionBackend *ConnectionBackend::nextPendingConnection()
0194 {
0195     Q_ASSERT(state == Listening);
0196     Q_ASSERT(localServer);
0197     Q_ASSERT(!socket);
0198 
0199     // qCDebug(KIO_CORE) << "Got a new connection";
0200 
0201     QLocalSocket *newSocket = localServer->nextPendingConnection();
0202 
0203     if (!newSocket) {
0204         return nullptr; // there was no connection...
0205     }
0206 
0207     ConnectionBackend *result = new ConnectionBackend();
0208     result->state = Connected;
0209     result->socket = newSocket;
0210     newSocket->setParent(result);
0211     connect(newSocket, &QIODevice::readyRead, result, &ConnectionBackend::socketReadyRead);
0212     connect(newSocket, &QLocalSocket::disconnected, result, &ConnectionBackend::socketDisconnected);
0213 
0214     return result;
0215 }
0216 
0217 void ConnectionBackend::socketReadyRead()
0218 {
0219     bool shouldReadAnother;
0220     do {
0221         if (!socket)
0222         // might happen if the invokeMethods were delivered after we disconnected
0223         {
0224             return;
0225         }
0226 
0227         // qCDebug(KIO_CORE) << this << "Got" << socket->bytesAvailable() << "bytes";
0228         if (len == -1) {
0229             // We have to read the header
0230             char buffer[HeaderSize];
0231 
0232             if (socket->bytesAvailable() < HeaderSize) {
0233                 return; // wait for more data
0234             }
0235 
0236             socket->read(buffer, sizeof buffer);
0237             buffer[6] = 0;
0238             buffer[9] = 0;
0239 
0240             char *p = buffer;
0241             while (*p == ' ') {
0242                 p++;
0243             }
0244             len = strtol(p, nullptr, 16);
0245 
0246             p = buffer + 7;
0247             while (*p == ' ') {
0248                 p++;
0249             }
0250             cmd = strtol(p, nullptr, 16);
0251 
0252             // qCDebug(KIO_CORE) << this << "Beginning of command" << hex << cmd << "of size" << len;
0253         }
0254 
0255         QPointer<ConnectionBackend> that = this;
0256 
0257         // qCDebug(KIO_CORE) << socket << "Want to read" << len << "bytes";
0258         if (socket->bytesAvailable() >= len) {
0259             Task task;
0260             task.cmd = cmd;
0261             if (len) {
0262                 task.data = socket->read(len);
0263             }
0264             len = -1;
0265 
0266             signalEmitted = true;
0267             Q_EMIT commandReceived(task);
0268         } else if (len > StandardBufferSize) {
0269             qCDebug(KIO_CORE) << socket << "Jumbo packet of" << len << "bytes";
0270             // Calling setReadBufferSize from a readyRead slot leads to a bug in Qt, fixed in 13c246ee119
0271             socket->setReadBufferSize(len + 1);
0272         }
0273 
0274         // If we're dead, better don't try anything.
0275         if (that.isNull()) {
0276             return;
0277         }
0278 
0279         // Do we have enough for an another read?
0280         if (len == -1) {
0281             shouldReadAnother = socket->bytesAvailable() >= HeaderSize;
0282         } else {
0283             shouldReadAnother = socket->bytesAvailable() >= len;
0284         }
0285     } while (shouldReadAnother);
0286 }
0287 
0288 #include "moc_connectionbackend_p.cpp"