File indexing completed on 2024-05-12 05:17:23

0001 /*
0002     Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
0003     Copyright (c) 2009 Andras Mantia <amantia@kde.org>
0004     Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com>
0005 
0006     Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0007     Author: Kevin Ottens <kevin@kdab.com>
0008     Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
0009 
0010     This library is free software; you can redistribute it and/or modify it
0011     under the terms of the GNU Library General Public License as published by
0012     the Free Software Foundation; either version 2 of the License, or (at your
0013     option) any later version.
0014 
0015     This library is distributed in the hope that it will be useful, but WITHOUT
0016     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0017     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0018     License for more details.
0019 
0020     You should have received a copy of the GNU Library General Public License
0021     along with this library; see the file COPYING.LIB.  If not, write to the
0022     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0023     02110-1301, USA.
0024 */
0025 
0026 #include "imapstreamparser.h"
0027 
0028 #include <QIODevice>
0029 #include <QDebug>
0030 
0031 using namespace KIMAP2;
0032 
0033 ImapStreamParser::ImapStreamParser(QIODevice *socket, bool serverModeEnabled)
0034     : m_socket(socket),
0035     m_isServerModeEnabled(serverModeEnabled),
0036     m_processing(false),
0037     m_position(0),
0038     m_readPosition(0),
0039     m_literalSize(0),
0040     m_bufferSize(16000),
0041     m_currentState(InitState),
0042     m_listCounter(0),
0043     m_stringStartPos(0),
0044     m_readingLiteral(false),
0045     m_error(false),
0046     m_list(nullptr)
0047 {
0048     m_data1.resize(m_bufferSize);
0049     m_data2.resize(m_bufferSize);
0050     m_current = &m_data1;
0051     setupCallbacks();
0052 }
0053 
0054 QByteArray &ImapStreamParser::buffer()
0055 {
0056     return *m_current;
0057 }
0058 
0059 const QByteArray &ImapStreamParser::buffer() const
0060 {
0061     return *m_current;
0062 }
0063 
0064 char ImapStreamParser::at(int pos) const
0065 {
0066     return m_current->constData()[pos];
0067 }
0068 
0069 QByteArray ImapStreamParser::mid(int start, int len)  const
0070 {
0071     return buffer().mid(start, len);
0072 }
0073 
0074 QByteArray ImapStreamParser::midRef(int start, int len)  const
0075 {
0076     return QByteArray::fromRawData(buffer().constData() + start, len);
0077 }
0078 
0079 int ImapStreamParser::length() const
0080 {
0081     return m_readPosition;
0082 }
0083 
0084 int ImapStreamParser::readFromSocket()
0085 {
0086     if (m_readingLiteral && !m_isServerModeEnabled) {
0087         Q_ASSERT(m_currentState == LiteralStringState);
0088         Q_ASSERT(m_literalSize > 0);
0089         const auto amountToRead = qMin(m_socket->bytesAvailable(), m_literalSize);
0090         Q_ASSERT(amountToRead > 0);
0091         auto pos = m_literalData.size();
0092         m_literalData.resize(m_literalData.size() + amountToRead);
0093         const auto readBytes = m_socket->read(m_literalData.data() + pos, amountToRead);
0094         if (readBytes < 0) {
0095             qWarning() << "Failed to read data";
0096             return 0;
0097         }
0098         // qDebug() << "Read literal data: " << readBytes << m_literalSize;
0099         m_literalSize -= readBytes;
0100         Q_ASSERT(m_literalSize >= 0);
0101         return readBytes;
0102     } else {
0103         if (m_readPosition == m_bufferSize) {
0104             // qDebug() << "Buffer is full, trimming";
0105             trimBuffer();
0106         }
0107         const auto amountToRead = qMin(m_socket->bytesAvailable(), qint64(m_bufferSize - m_readPosition));
0108         Q_ASSERT(amountToRead > 0);
0109         const auto readBytes = m_socket->read(buffer().data() + m_readPosition, amountToRead);
0110         if (readBytes < 0) {
0111             qWarning() << "Failed to read data";
0112             return 0;
0113         }
0114         m_readPosition += readBytes;
0115         // qDebug() << "Buffer: " << buffer().mid(0, m_readPosition);
0116         // qDebug() << "Read data: " << readBytes;
0117         return readBytes;
0118     }
0119 }
0120 
0121 void ImapStreamParser::setupCallbacks()
0122 {
0123     onString([&](const char *data, const int size) {
0124         if (!m_message) {
0125             //We just assume that we always get a string first
0126             m_message.reset(new Message);
0127             m_currentPayload = &m_message->content;
0128         }
0129         if (m_list) {
0130             *m_list << QByteArray(data, size);
0131         } else {
0132             *m_currentPayload << Message::Part(QByteArray(data, size));
0133         }
0134     });
0135 
0136     onListStart([&]() {
0137         m_listCounter++;
0138         if (m_listCounter > 1) {
0139             //Parse sublists as string
0140             setState(SublistString);
0141             m_stringStartPos = m_position;
0142         } else {
0143             if (!m_list) {
0144                 m_list = new QList<QByteArray>;
0145             }
0146         }
0147     });
0148 
0149     onListEnd([&]() {
0150         if (m_listCounter <= 0) {
0151             qWarning() << "Brackets are off";
0152             m_error = true;
0153             return;
0154         }
0155         m_listCounter--;
0156         if (m_listCounter == 0) {
0157             Q_ASSERT(m_currentPayload);
0158             Q_ASSERT(m_list);
0159             *m_currentPayload << Message::Part(*m_list);
0160             delete m_list;
0161             m_list = nullptr;
0162         }
0163     });
0164 
0165     onResponseCodeStart([&]() {
0166         m_currentPayload = &m_message->responseCode;
0167     });
0168 
0169     onResponseCodeEnd([&]() {
0170         m_currentPayload = &m_message->content;
0171     });
0172 
0173     onLiteralStart([&](const int size) {
0174         m_literalData.clear();
0175         m_literalData.reserve(size);
0176     });
0177 
0178     onLiteralPart([&](const char *data, const int size) {
0179         m_literalData.append(QByteArray::fromRawData(data, size));
0180     });
0181 
0182     onLiteralEnd([&]() {
0183         string(m_literalData.constData(), m_literalData.size());
0184     });
0185 
0186     onLineEnd([&]() {
0187         if (m_list || m_listCounter != 0) {
0188             qWarning() << "List parsing in progress: " << m_listCounter;
0189             m_error = true;
0190         }
0191         if (m_literalSize || m_readingLiteral) {
0192             qWarning() << "Literal parsing in progress: " << m_literalSize;
0193             m_error = true;
0194         }
0195         Q_ASSERT(responseReceived);
0196         if (m_message) {
0197             responseReceived(*m_message);
0198             m_message.reset(nullptr);
0199         }
0200         m_currentPayload = nullptr;
0201     });
0202 }
0203 
0204 void ImapStreamParser::setState(States state)
0205 {
0206     m_lastState = m_currentState;
0207     m_currentState = state;
0208 }
0209 
0210 void ImapStreamParser::forwardToState(States state)
0211 {
0212     m_currentState = state;
0213 }
0214 
0215 void ImapStreamParser::resetState()
0216 {
0217     m_currentState = m_lastState;
0218 }
0219 
0220 void ImapStreamParser::processBuffer()
0221 {
0222     if (m_error) {
0223         qWarning() << "An error occurred";
0224         return;
0225     }
0226     if (m_currentState == LiteralStringState && m_literalSize == 0 && m_readingLiteral) {
0227         literalEnd();
0228         resetState();
0229         m_readingLiteral = false;
0230     }
0231 
0232     while (m_position < m_readPosition) {
0233         Q_ASSERT(m_position < length());
0234         const char c = buffer()[m_position];
0235         // qDebug() << "Checking :" << c << m_position << m_readPosition << m_currentState << m_listCounter;
0236         switch (m_currentState) {
0237             case InitState:
0238                 if (c == '(') {
0239                     listStart();
0240                 } else if (c == ')') {
0241                     listEnd();
0242                 } else if (c == '[') {
0243                     if (m_listCounter >= 1) {
0244                         //Inside lists angle brackets are parsed as strings
0245                         setState(AngleBracketStringState);
0246                         m_stringStartPos = m_position;
0247                     } else {
0248                         responseCodeStart();
0249                     }
0250                 } else if (c == ']') {
0251                     responseCodeEnd();
0252                 } else if (c == ' ') {
0253                     //Skip whitespace
0254                     setState(WhitespaceState);
0255                 } else if (c == '\r') {
0256                     setState(CRLFState);
0257                 } else if (c == '{') {
0258                     setState(LiteralStringState);
0259                     m_stringStartPos = m_position + 1;
0260                 } else if (c == '\"') {
0261                     setState(QuotedStringState);
0262                     m_stringStartPos = m_position + 1;
0263                 } else {
0264                     setState(StringState);
0265                     m_stringStartPos = m_position;
0266                 }
0267                 break;
0268             case QuotedStringState:
0269                 if (c == '\"' && buffer().at(m_position - 1) != '\\') {
0270                     //Unescaped quote
0271                     resetState();
0272                     const auto endPos = m_position;
0273                     string(buffer().constData() + m_stringStartPos, endPos - m_stringStartPos);
0274                     m_stringStartPos = 0;
0275                 }
0276                 break;
0277             case LiteralStringState:
0278                 if (c == '}') {
0279                     m_literalSize = strtol(buffer().constData() + m_stringStartPos, nullptr, 10);
0280                     // qDebug() << "Found literal size: " << m_literalSize;
0281                     literalStart(m_literalSize);
0282                     m_readingLiteral = false;
0283                     m_stringStartPos = 0;
0284                     break;
0285                 }
0286                 if (!m_readingLiteral) {
0287                     //Skip CRLF after literal size
0288                     if (c == '\n') {
0289                         m_readingLiteral = true;
0290                         if (m_isServerModeEnabled && m_literalSize > 0) {
0291                             sendContinuationResponse(m_literalSize);
0292                         }
0293                     }
0294                 } else {
0295                     Q_ASSERT(m_position < length());
0296                     if (m_literalSize) {
0297                         int size = m_literalSize;
0298                         if (length() < m_position + size) {
0299                             //If the literal is not complete we take what is available
0300                             size = length() - m_position;
0301                         }
0302                         literalPart(buffer().constData() + m_position, size);
0303                         m_position += size;
0304                         m_literalSize -= size;
0305                     }
0306                     if (m_literalSize <= 0) {
0307                         Q_ASSERT(m_literalSize == 0);
0308                         literalEnd();
0309                         resetState();
0310                         m_readingLiteral = false;
0311                     }
0312                     continue;
0313                 }
0314                 break;
0315             case StringState:
0316                 if (c == ' ' ||
0317                     c == ')' || //End of list
0318                     c == '(' || //New list
0319                     //FIXME because we want to concat in sublists.
0320                     // c == '[' ||
0321                     c == ']' ||
0322                     c == '\r' || //CRLF
0323                     c == '\"') {
0324                     resetState();
0325                     string(buffer().constData() + m_stringStartPos, m_position - m_stringStartPos);
0326                     m_stringStartPos = 0;
0327                     continue;
0328                 }
0329                 //Inside lists we want to parse the angle brackets as part of the string.
0330                 if (c == '[') {
0331                     if (m_listCounter >= 1) {
0332                         // qDebug() << "Switching to angle bracket state";
0333                         forwardToState(AngleBracketStringState);
0334                         break;
0335                     }
0336                 }
0337                 break;
0338             case AngleBracketStringState:
0339                 if (c == ']') {
0340                     resetState();
0341                     string(buffer().constData() + m_stringStartPos, m_position - m_stringStartPos + 1);
0342                     m_stringStartPos = 0;
0343                 }
0344                 break;
0345             case SublistString:
0346                 if (c == '(') {
0347                     m_listCounter++;
0348                 } else if (c == ')') {
0349                     m_listCounter--;
0350                     if (m_listCounter <= 1) {
0351                         resetState();
0352                         string(buffer().constData() + m_stringStartPos, m_position - m_stringStartPos + 1);
0353                         m_stringStartPos = 0;
0354                     }
0355                 }
0356                 break;
0357             case WhitespaceState:
0358                 if (c != ' ') {
0359                     //Skip whitespace
0360                     resetState();
0361                     continue;
0362                 }
0363                 break;
0364             case CRLFState:
0365                 if (c == '\n') {
0366                     lineEnd();
0367                     resetState();
0368                 } else {
0369                     //Skip over the \r that isn't part of the CRLF
0370                     resetState();
0371                     continue;
0372                 }
0373                 break;
0374         }
0375         m_position++;
0376     }
0377 }
0378 
0379 void ImapStreamParser::parseStream()
0380 {
0381     if (m_processing) {
0382         return;
0383     }
0384     if (m_error) {
0385         qWarning() << "An error occurred";
0386         return;
0387     }
0388     m_processing = true;
0389     while (m_socket->bytesAvailable()) {
0390         if (readFromSocket() <= 0) {
0391             //If we're not making progress we could loop forever,
0392             //and given that we check beforehand if there is data,
0393             //this should never happen.
0394             qWarning() << "Read nothing from the socket.";
0395             m_error = true;
0396             Q_ASSERT(false);
0397             return;
0398         };
0399         processBuffer();
0400     }
0401     m_processing = false;
0402 }
0403 
0404 void ImapStreamParser::trimBuffer()
0405 {
0406     int offset = m_position;
0407     if (m_stringStartPos) {
0408         offset = qMin(m_stringStartPos, m_position);
0409     }
0410 
0411     auto remainderSize = m_readPosition - offset;
0412     Q_ASSERT( remainderSize >= 0);
0413     QByteArray *otherBuffer;
0414     if (m_current == &m_data1) {
0415         otherBuffer = &m_data2;
0416     } else {
0417         otherBuffer = &m_data1;
0418     }
0419     if (remainderSize) {
0420         otherBuffer->replace(0, remainderSize, buffer().constData() + offset, remainderSize);
0421     }
0422     m_current = otherBuffer;
0423     m_readPosition = remainderSize;
0424     m_position -= offset;
0425     if (m_stringStartPos) {
0426         m_stringStartPos -= offset;
0427     }
0428     // qDebug() << "Buffer after trim: " << mid(0, m_readPosition);
0429 }
0430 
0431 int ImapStreamParser::availableDataSize() const
0432 {
0433     return m_socket->bytesAvailable() + length() - m_position;
0434 }
0435 
0436 QByteArray ImapStreamParser::readUntilCommandEnd()
0437 {
0438     QByteArray result;
0439     auto startPos = m_position;
0440     onLineEnd([&result, this, startPos]() {
0441         result = mid(startPos, m_position - startPos - 1);
0442     });
0443     Q_FOREVER {
0444         if (!m_socket->bytesAvailable()) {
0445             if (!m_socket->waitForReadyRead(10000)) {
0446                 qWarning() << "No data available";
0447                 return result;
0448             }
0449         }
0450         parseStream();
0451         if (!result.isEmpty() && m_currentState == InitState) {
0452             // qDebug() << "Got a result: " << m_readingLiteral;
0453             // result.append(m_literalData);
0454             break;
0455         }
0456     }
0457     qDebug() << "Read until command end: " << result;
0458     return result;
0459 }
0460 
0461 void ImapStreamParser::sendContinuationResponse(qint64 size)
0462 {
0463     QByteArray block = "+ Ready for literal data (expecting " +
0464                        QByteArray::number(size) + " bytes)\r\n";
0465     m_socket->write(block);
0466     m_socket->waitForBytesWritten(30000);
0467 }
0468 
0469 void ImapStreamParser::onResponseReceived(std::function<void(const Message &)> f)
0470 {
0471     responseReceived = f;
0472 }
0473 
0474 bool ImapStreamParser::error() const
0475 {
0476     return m_error;
0477 }
0478 
0479 QByteArray ImapStreamParser::currentBuffer() const
0480 {
0481     return mid(0, m_readPosition);
0482 }