File indexing completed on 2025-02-16 04:23:13

0001 /*
0002     SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "serversenteventsstream.h"
0007 #include "logging.h"
0008 
0009 #include <QIODevice>
0010 
0011 #include <algorithm>
0012 #include <cstring>
0013 
0014 using namespace KUnifiedPush;
0015 
0016 ServerSentEventsStream::ServerSentEventsStream(QObject *parent)
0017     : QObject(parent)
0018 {
0019 }
0020 
0021 ServerSentEventsStream::~ServerSentEventsStream() = default;
0022 
0023 void ServerSentEventsStream::read(QIODevice *device)
0024 {
0025     connect(device, &QIODevice::readyRead, this, [device, this]() {
0026         m_buffer.append(device->read(device->bytesAvailable()));
0027         processBuffer();
0028     });
0029 }
0030 
0031 static bool isLineBreak(char c)
0032 {
0033     return c == '\n' || c == '\r';
0034 }
0035 
0036 static QByteArray::ConstIterator findLineBreak(const QByteArray::const_iterator &begin, const QByteArray::const_iterator &end)
0037 {
0038     return std::find_if(begin, end, isLineBreak);
0039 }
0040 
0041 static QByteArray::const_iterator consumeLineBreak(const QByteArray::const_iterator &begin, const QByteArray::const_iterator &end)
0042 {
0043     auto it = begin;
0044     if (it == end) {
0045     } else if ((*it) == '\n') {
0046         ++it;
0047     } else if ((*it) == '\r') {
0048         ++it;
0049         if (it != end && (*it) == '\n') {
0050             ++it;
0051         }
0052     }
0053 
0054     return it;
0055 }
0056 
0057 static QByteArray::const_iterator findMessageEnd(const QByteArray::const_iterator &begin, const QByteArray::const_iterator &end)
0058 {
0059     for (auto it = findLineBreak(begin, end); it != end; it = findLineBreak(it, end)) {
0060         auto lookAhead = consumeLineBreak(it, end);
0061         if (lookAhead != end && isLineBreak(*lookAhead)) {
0062             return it;
0063         }
0064         it = lookAhead;
0065     }
0066 
0067     return end;
0068 }
0069 
0070 void ServerSentEventsStream::processBuffer()
0071 {
0072     qCDebug(Log) << m_buffer;
0073     auto msgEnd = findMessageEnd(m_buffer.begin(), m_buffer.end());
0074     if (msgEnd == m_buffer.end()) {
0075         qCDebug(Log) << "buffer incomplete, waiting for more";
0076         return;
0077     }
0078 
0079     SSEMessage msg;
0080     for (auto it = m_buffer.constBegin(); it != msgEnd;) {
0081         auto lineBegin = it;
0082         auto lineEnd = findLineBreak(lineBegin, msgEnd);
0083         it = consumeLineBreak(lineEnd, msgEnd);
0084         Q_ASSERT(lineBegin != it);
0085 
0086         auto colonIt = std::find(lineBegin, lineEnd, ':');
0087         auto valueBegin = colonIt;
0088         if (valueBegin != lineEnd) {
0089             ++valueBegin;
0090             if (valueBegin != lineEnd && (*valueBegin) == ' ') {
0091                 ++valueBegin;
0092             }
0093         }
0094 
0095         if (colonIt == lineBegin || valueBegin == lineEnd) {
0096             continue; // comment or value-less field
0097         }
0098 
0099         const auto fieldNameLen = std::distance(lineBegin, colonIt);
0100         if (fieldNameLen == 4 && std::strncmp(lineBegin, "data", fieldNameLen) == 0) {
0101             msg.data.append(valueBegin, std::distance(valueBegin, lineEnd));
0102         } else if (fieldNameLen == 5 && std::strncmp(lineBegin, "event", fieldNameLen) == 0) {
0103             msg.event = QByteArray(valueBegin, std::distance(valueBegin, lineEnd));
0104         }
0105     }
0106     Q_EMIT messageReceived(msg);
0107 
0108     msgEnd = consumeLineBreak(msgEnd, m_buffer.end());
0109     msgEnd = consumeLineBreak(msgEnd, m_buffer.end());
0110     if (msgEnd == m_buffer.end()) {
0111         m_buffer.clear();
0112     } else {
0113         const auto remainingLen = m_buffer.size() - std::distance(m_buffer.constBegin(), msgEnd);
0114         std::memmove(m_buffer.begin(), msgEnd, remainingLen);
0115         m_buffer.truncate(remainingLen);
0116         processBuffer();
0117     }
0118 }