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 }