#include "snapdconnection.h" #include "extern-plugininfo.h" #include #include SnapdConnection::SnapdConnection(QObject *parent) : QLocalSocket(parent) { connect(this, &QLocalSocket::connected, this, &SnapdConnection::onConnected); connect(this, &QLocalSocket::disconnected, this, &SnapdConnection::onDisconnected); connect(this, &QLocalSocket::readyRead, this, &SnapdConnection::onReadyRead); connect(this, &QLocalSocket::stateChanged, this, &SnapdConnection::onStateChanged); connect(this, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(onError(QLocalSocket::LocalSocketError))); } SnapdReply *SnapdConnection::get(const QString &path) { SnapdReply *reply = new SnapdReply(this); reply->setRequestPath(path); reply->setRequestMethod("GET"); reply->setRequestRawMessage(createRequestHeader("GET", path)); // Check if currently a reply is running if (m_currentReply) { m_replyQueue.enqueue(reply); } else { // Send request m_currentReply = reply; if (m_debug) qCDebug(dcSnapd()) << "-->" << reply->requestMethod() << reply->requestPath(); if (write(reply->requestRawMessage()) <= 0) { m_currentReply = nullptr; reply->setFinished(false); sendNextRequest(); } } // Note: the caller owns the object now return reply; } SnapdReply *SnapdConnection::post(const QString &path, const QByteArray &payload) { SnapdReply *reply = new SnapdReply(this); reply->setRequestPath(path); reply->setRequestMethod("POST"); QByteArray header = createRequestHeader("POST", path, payload); reply->setRequestRawMessage(header.append(payload)); // Check if currently a reply is running if (m_currentReply) { m_replyQueue.enqueue(reply); } else { // Send request m_currentReply = reply; if (m_debug) qCDebug(dcSnapd()) << "-->" << reply->requestMethod() << reply->requestPath() << payload; if (write(reply->requestRawMessage()) <= 0) { m_currentReply = nullptr; reply->setFinished(false); sendNextRequest(); } } // Note: the caller owns the object now return reply; } bool SnapdConnection::isConnected() const { return m_connected; } void SnapdConnection::setConnected(const bool &connected) { if (m_connected == connected) return; qCDebug(dcSnapd()) << "Connected"; m_connected = connected; emit connectedChanged(m_connected); } QByteArray SnapdConnection::createRequestHeader(const QString &method, const QString &path, const QByteArray &payload) { QByteArray request; request.append(QString("%1 %2 HTTP/1.1\r\n").arg(method).arg(path).toUtf8()); request.append("Host: http\r\n"); request.append("Accept: *\r\n"); if (!payload.isEmpty()) { request.append("Content-Type: application/json\r\n"); request.append(QString("Content-Length: %1\r\n").arg(payload.count()).toUtf8()); } request.append("\r\n"); return request; } QByteArray SnapdConnection::getChunckedPayload(const QByteArray &payload) { // Read line by line QStringList payloadLines = QString::fromUtf8(payload).split(QRegExp("\r\n")); if (payloadLines.count() < 4) { qCWarning(dcSnapd()) << "Chuncked payload invalid linecount" << payloadLines.count(); return QByteArray(); } int payloadSize = payloadLines.at(2).toInt(0, 16); if (m_debug) qCDebug(dcSnapd()) << "Payload size" << payloadSize; if (payloadLines.at(3).toUtf8().size() != payloadSize) { qCWarning(dcSnapd()) << "Invalid payload size" << payloadLines.at(3).toUtf8().size() << "!=" << payloadSize; return QByteArray(); } // Return just the payload return payloadLines.at(3).toUtf8(); } void SnapdConnection::processData() { if (!m_currentReply) { qCWarning(dcSnapd()) << "Data received without current reply" << m_payload; return; } if (m_header.isEmpty()) { qCWarning(dcSnapd()) << "Could not process data. There is no header."; m_currentReply->setFinished(); return; } // Get the raw payload QByteArray payloadData; if (m_chuncked) { payloadData = getChunckedPayload(m_payload); } else { payloadData = m_payload; } // Check if there are data to process if (m_payload.isEmpty()) { qCWarning(dcSnapd()) << "Could not process data. There is no payload to process."; return; } // Parse header QHash parsedHeader; QStringList headerLines = QString::fromUtf8(m_header).split(QRegExp("\r\n")); // Read status line QString statusLine = headerLines.takeFirst(); QStringList statusLineTokens = statusLine.split(QRegExp("[ \r\n][ \r\n]*")); if (statusLineTokens.count() < 3) { qCWarning(dcSnapd()) << "Could not parse HTTP status line:" << statusLine; return; } bool statusCodeOk = false; int statusCode = statusLineTokens.at(1).simplified().toInt(&statusCodeOk); if (!statusCodeOk) { qCWarning(dcSnapd()) << "Could not parse HTTP status code:" << statusLineTokens.at(1); return; } QString statusMessage; for (int i = 2; i < statusLineTokens.count(); i++) { statusMessage.append(statusLineTokens.at(i).simplified()); if (i < statusLineTokens.count() -1) { statusMessage.append(" "); } } // Verify header formating foreach (const QString &line, headerLines) { if (!line.contains(":")) { qCWarning(dcSnapd()) << "Invalid HTTP header. Missing \":\" in line" << line; return; } int index = line.indexOf(":"); QString key = line.left(index).toUtf8().simplified(); QString value = line.right(line.count() - index - 1).toUtf8().simplified(); //qCDebug(dcSnapd()) << " Key:" << key << "Value:" << value; parsedHeader.insert(key, value); } // Parse payload QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(payloadData, &error); if (error.error != QJsonParseError::NoError) { qCWarning(dcSnapd()) << "Got invalid JSON data from snapd:" << error.offset << error.errorString(); qCWarning(dcSnapd()) << qUtf8Printable(payloadData); return; } if (m_debug) qCDebug(dcSnapd()) << "<--" << m_currentReply->requestPath() << statusCode << statusMessage; // Fill reply m_currentReply->setStatusCode(statusCode); m_currentReply->setStatusMessage(statusMessage); m_currentReply->setHeader(parsedHeader); m_currentReply->setDataMap(jsonDoc.toVariant().toMap()); m_currentReply->setFinished(); m_currentReply = nullptr; sendNextRequest(); // Current data stream finished, reset for new messages m_payload.clear(); m_header.clear(); m_chuncked = false; } void SnapdConnection::sendNextRequest() { if (m_replyQueue.isEmpty()) return; SnapdReply *reply = m_replyQueue.dequeue(); m_currentReply = reply; if (m_debug) qCDebug(dcSnapd()) << "-->" << reply->requestMethod() << reply->requestPath(); if (write(reply->requestRawMessage()) < 0) { m_currentReply->setFinished(false); m_currentReply = nullptr; sendNextRequest(); } } void SnapdConnection::onConnected() { setConnected(true); } void SnapdConnection::onDisconnected() { setConnected(false); } void SnapdConnection::onError(const QLocalSocket::LocalSocketError &socketError) { qCWarning(dcSnapd()) << "Socket error" << socketError << errorString(); } void SnapdConnection::onStateChanged(const QLocalSocket::LocalSocketState &state) { switch (state) { case QLocalSocket::UnconnectedState: qCDebug(dcSnapd()) << "Disconnected from snapd."; break; case QLocalSocket::ConnectingState: qCDebug(dcSnapd()) << "Connecting to snapd..."; break; case QLocalSocket::ConnectedState: qCDebug(dcSnapd()) << "Connected to snapd."; break; case QLocalSocket::ClosingState: qCDebug(dcSnapd()) << "Closing connection to snapd."; break; default: break; } } void SnapdConnection::onReadyRead() { QByteArray data = readAll(); if (m_debug) qCDebug(dcSnapd()) << "Data received:" << data; // If we are not appending to a reply if (!m_chuncked) { // Parse header int headerIndex = data.indexOf("\r\n\r\n"); if (headerIndex < 0) { qCWarning(dcSnapd()) << "Invalid response format. Could not find header/payload mark."; return; } m_header = data.left(headerIndex); if (m_debug) qCDebug(dcSnapd()) << "Header:" << m_header; QByteArray payload = data.right(data.length() - (headerIndex)); if (m_debug) qCDebug(dcSnapd()) << "Payload" << payload; // Check if this message is chuncked if (m_header.contains("chunked")) { if (m_debug) qCDebug(dcSnapd()) << "Chuncked message receiving"; m_chuncked = true; m_payload.append(payload); if (m_payload.endsWith("\r\n0\r\n\r\n")) { // Chuncked message finished processData(); } } else { // Not chucked m_payload = payload; processData(); } } else { m_payload.append(data); if (m_payload.endsWith("\r\n0\r\n\r\n")) { // Chuncked message finished processData(); } } }