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

0001 /*
0002     SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org>
0003     SPDX-FileCopyrightText: 2009 Andras Mantia <amantia@kde.org>
0004 
0005     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
0006     SPDX-FileContributor: Kevin Ottens <kevin@kdab.com>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "imapstreamparser.h"
0012 
0013 #include <QIODevice>
0014 #include <ctype.h>
0015 
0016 using namespace KIMAP;
0017 
0018 ImapStreamParser::ImapStreamParser(QIODevice *socket, bool serverModeEnabled)
0019     : m_position(0)
0020     , m_literalSize(0)
0021 {
0022     m_socket = socket;
0023     m_isServerModeEnabled = serverModeEnabled;
0024 }
0025 
0026 QString ImapStreamParser::readUtf8String()
0027 {
0028     QByteArray tmp;
0029     tmp = readString();
0030     QString result = QString::fromUtf8(tmp);
0031     return result;
0032 }
0033 
0034 QByteArray ImapStreamParser::readString()
0035 {
0036     QByteArray result;
0037     if (!waitForMoreData(m_data.isEmpty())) {
0038         throw ImapParserException("Unable to read more data");
0039     }
0040     stripLeadingSpaces();
0041     if (!waitForMoreData(m_position >= m_data.length())) {
0042         throw ImapParserException("Unable to read more data");
0043     }
0044 
0045     // literal string
0046     // TODO: error handling
0047     if (hasLiteral()) {
0048         while (!atLiteralEnd()) {
0049             result += readLiteralPart();
0050         }
0051         return result;
0052     }
0053 
0054     // quoted string
0055     return parseQuotedString();
0056 }
0057 
0058 bool ImapStreamParser::hasString()
0059 {
0060     if (!waitForMoreData(m_position >= m_data.length())) {
0061         throw ImapParserException("Unable to read more data");
0062     }
0063     int savedPos = m_position;
0064     stripLeadingSpaces();
0065     int pos = m_position;
0066     m_position = savedPos;
0067     const char dataChar = m_data.at(pos);
0068     if (dataChar == '{') {
0069         return true; // literal string
0070     } else if (dataChar == '"') {
0071         return true; // quoted string
0072     } else if (dataChar != ' ' && dataChar != '(' && dataChar != ')' && dataChar != '[' && dataChar != ']' && dataChar != '\n' && dataChar != '\r') {
0073         return true; // unquoted string
0074     }
0075 
0076     return false; // something else, not a string
0077 }
0078 
0079 bool ImapStreamParser::hasLiteral()
0080 {
0081     if (!waitForMoreData(m_position >= m_data.length())) {
0082         throw ImapParserException("Unable to read more data");
0083     }
0084     int savedPos = m_position;
0085     stripLeadingSpaces();
0086     if (m_data.at(m_position) == '{') {
0087         int end = -1;
0088         do {
0089             end = m_data.indexOf('}', m_position);
0090             if (!waitForMoreData(end == -1)) {
0091                 throw ImapParserException("Unable to read more data");
0092             }
0093         } while (end == -1);
0094         Q_ASSERT(end > m_position);
0095         m_literalSize = m_data.mid(m_position + 1, end - m_position - 1).toInt();
0096         // strip CRLF
0097         m_position = end + 1;
0098         // ensure that the CRLF is available
0099         if (!waitForMoreData(m_position + 1 >= m_data.length())) {
0100             throw ImapParserException("Unable to read more data");
0101         }
0102         if (m_position < m_data.length() && m_data.at(m_position) == '\r') {
0103             ++m_position;
0104         }
0105         if (m_position < m_data.length() && m_data.at(m_position) == '\n') {
0106             ++m_position;
0107         }
0108 
0109         // FIXME: Makes sense only on the server side?
0110         if (m_isServerModeEnabled && m_literalSize > 0) {
0111             sendContinuationResponse(m_literalSize);
0112         }
0113         return true;
0114     } else {
0115         m_position = savedPos;
0116         return false;
0117     }
0118 }
0119 
0120 bool ImapStreamParser::atLiteralEnd() const
0121 {
0122     return (m_literalSize == 0);
0123 }
0124 
0125 QByteArray ImapStreamParser::readLiteralPart()
0126 {
0127     static const qint64 maxLiteralPartSize = 4096;
0128     int size = qMin(maxLiteralPartSize, m_literalSize);
0129 
0130     if (!waitForMoreData(m_data.length() < m_position + size)) {
0131         throw ImapParserException("Unable to read more data");
0132     }
0133 
0134     if (m_data.length() < m_position + size) { // Still not enough data
0135         // Take what's already there
0136         size = m_data.length() - m_position;
0137     }
0138 
0139     QByteArray result = m_data.mid(m_position, size);
0140     m_position += size;
0141     m_literalSize -= size;
0142     Q_ASSERT(m_literalSize >= 0);
0143     trimBuffer();
0144 
0145     return result;
0146 }
0147 
0148 bool ImapStreamParser::hasList()
0149 {
0150     if (!waitForMoreData(m_position >= m_data.length())) {
0151         throw ImapParserException("Unable to read more data");
0152     }
0153     int savedPos = m_position;
0154     stripLeadingSpaces();
0155     int pos = m_position;
0156     m_position = savedPos;
0157     if (m_data.at(pos) == '(') {
0158         return true;
0159     }
0160     return false;
0161 }
0162 
0163 bool ImapStreamParser::atListEnd()
0164 {
0165     if (!waitForMoreData(m_position >= m_data.length())) {
0166         throw ImapParserException("Unable to read more data");
0167     }
0168     int savedPos = m_position;
0169     stripLeadingSpaces();
0170     int pos = m_position;
0171     m_position = savedPos;
0172     if (m_data.at(pos) == ')') {
0173         m_position = pos + 1;
0174         return true;
0175     }
0176     return false;
0177 }
0178 
0179 QList<QByteArray> ImapStreamParser::readParenthesizedList()
0180 {
0181     QList<QByteArray> result;
0182     if (!waitForMoreData(m_data.length() <= m_position)) {
0183         throw ImapParserException("Unable to read more data");
0184     }
0185 
0186     stripLeadingSpaces();
0187     if (m_data.at(m_position) != '(') {
0188         return result; // no list found
0189     }
0190 
0191     bool concatToLast = false;
0192     int count = 0;
0193     int sublistbegin = m_position;
0194     int i = m_position + 1;
0195     for (;;) {
0196         if (!waitForMoreData(m_data.length() <= i)) {
0197             m_position = i;
0198             throw ImapParserException("Unable to read more data");
0199         }
0200         if (m_data.at(i) == '(') {
0201             ++count;
0202             if (count == 1) {
0203                 sublistbegin = i;
0204             }
0205             ++i;
0206             continue;
0207         }
0208         if (m_data.at(i) == ')') {
0209             if (count <= 0) {
0210                 m_position = i + 1;
0211                 return result;
0212             }
0213             if (count == 1) {
0214                 result.append(m_data.mid(sublistbegin, i - sublistbegin + 1));
0215             }
0216             --count;
0217             ++i;
0218             continue;
0219         }
0220         if (m_data.at(i) == ' ') {
0221             ++i;
0222             continue;
0223         }
0224         if (m_data.at(i) == '"') {
0225             if (count > 0) {
0226                 m_position = i;
0227                 parseQuotedString();
0228                 i = m_position;
0229                 continue;
0230             }
0231         }
0232         if (m_data.at(i) == '[') {
0233             concatToLast = true;
0234             if (result.isEmpty()) {
0235                 result.append(QByteArray());
0236             }
0237             result.last() += '[';
0238             ++i;
0239             continue;
0240         }
0241         if (m_data.at(i) == ']') {
0242             concatToLast = false;
0243             result.last() += ']';
0244             ++i;
0245             continue;
0246         }
0247         if (count == 0) {
0248             m_position = i;
0249             QByteArray ba;
0250             if (hasLiteral()) {
0251                 while (!atLiteralEnd()) {
0252                     ba += readLiteralPart();
0253                 }
0254             } else {
0255                 ba = readString();
0256             }
0257 
0258             // We might sometime get some unwanted CRLF, but we're still not at the end
0259             // of the list, would make further string reads fail so eat the CRLFs.
0260             while ((m_position < m_data.size()) && (m_data.at(m_position) == '\r' || m_data.at(m_position) == '\n')) {
0261                 m_position++;
0262             }
0263 
0264             i = m_position - 1;
0265             if (concatToLast) {
0266                 result.last() += ba;
0267             } else {
0268                 result.append(ba);
0269             }
0270         }
0271         ++i;
0272     }
0273 
0274     throw ImapParserException("Something went very very wrong!");
0275 }
0276 
0277 bool ImapStreamParser::hasResponseCode()
0278 {
0279     if (!waitForMoreData(m_position >= m_data.length())) {
0280         throw ImapParserException("Unable to read more data");
0281     }
0282     int savedPos = m_position;
0283     stripLeadingSpaces();
0284     int pos = m_position;
0285     m_position = savedPos;
0286     if (m_data.at(pos) == '[') {
0287         m_position = pos + 1;
0288         return true;
0289     }
0290     return false;
0291 }
0292 
0293 bool ImapStreamParser::atResponseCodeEnd()
0294 {
0295     if (!waitForMoreData(m_position >= m_data.length())) {
0296         throw ImapParserException("Unable to read more data");
0297     }
0298     int savedPos = m_position;
0299     stripLeadingSpaces();
0300     int pos = m_position;
0301     m_position = savedPos;
0302     if (m_data.at(pos) == ']') {
0303         m_position = pos + 1;
0304         return true;
0305     }
0306     return false;
0307 }
0308 
0309 QByteArray ImapStreamParser::parseQuotedString()
0310 {
0311     QByteArray result;
0312     if (!waitForMoreData(m_data.length() == 0)) {
0313         throw ImapParserException("Unable to read more data");
0314     }
0315     stripLeadingSpaces();
0316     int end = m_position;
0317     result.clear();
0318     if (!waitForMoreData(m_position >= m_data.length())) {
0319         throw ImapParserException("Unable to read more data");
0320     }
0321     if (!waitForMoreData(m_position >= m_data.length())) {
0322         throw ImapParserException("Unable to read more data");
0323     }
0324 
0325     bool foundSlash = false;
0326     // quoted string
0327     if (m_data.at(m_position) == '"') {
0328         ++m_position;
0329         int i = m_position;
0330         for (;;) {
0331             if (!waitForMoreData(m_data.length() <= i)) {
0332                 m_position = i;
0333                 throw ImapParserException("Unable to read more data");
0334             }
0335             if (m_data.at(i) == '\\') {
0336                 i += 2;
0337                 foundSlash = true;
0338                 continue;
0339             }
0340             if (m_data.at(i) == '"') {
0341                 result = m_data.mid(m_position, i - m_position);
0342                 end = i + 1; // skip the '"'
0343                 break;
0344             }
0345             ++i;
0346         }
0347     }
0348 
0349     // unquoted string
0350     else {
0351         bool reachedInputEnd = true;
0352         int i = m_position;
0353         for (;;) {
0354             if (!waitForMoreData(m_data.length() <= i)) {
0355                 m_position = i;
0356                 throw ImapParserException("Unable to read more data");
0357             }
0358             if (m_data.at(i) == ' ' || m_data.at(i) == '(' || m_data.at(i) == ')' || m_data.at(i) == '[' || m_data.at(i) == ']' || m_data.at(i) == '\n'
0359                 || m_data.at(i) == '\r' || m_data.at(i) == '"') {
0360                 end = i;
0361                 reachedInputEnd = false;
0362                 break;
0363             }
0364             if (m_data.at(i) == '\\') {
0365                 foundSlash = true;
0366             }
0367             i++;
0368         }
0369         if (reachedInputEnd) { // FIXME: how can it get here?
0370             end = m_data.length();
0371         }
0372 
0373         result = m_data.mid(m_position, end - m_position);
0374     }
0375 
0376     // strip quotes
0377     if (foundSlash) {
0378         while (result.contains("\\\"")) {
0379             result.replace("\\\"", "\"");
0380         }
0381         while (result.contains("\\\\")) {
0382             result.replace("\\\\", "\\");
0383         }
0384     }
0385     m_position = end;
0386     return result;
0387 }
0388 
0389 qint64 ImapStreamParser::readNumber(bool *ok)
0390 {
0391     qint64 result;
0392     if (ok) {
0393         *ok = false;
0394     }
0395     if (!waitForMoreData(m_data.length() == 0)) {
0396         throw ImapParserException("Unable to read more data");
0397     }
0398     stripLeadingSpaces();
0399     if (!waitForMoreData(m_position >= m_data.length())) {
0400         throw ImapParserException("Unable to read more data");
0401     }
0402     if (m_position >= m_data.length()) {
0403         throw ImapParserException("Unable to read more data");
0404     }
0405     int i = m_position;
0406     for (;;) {
0407         if (!waitForMoreData(m_data.length() <= i)) {
0408             m_position = i;
0409             throw ImapParserException("Unable to read more data");
0410         }
0411         if (!isdigit(m_data.at(i))) {
0412             break;
0413         }
0414         ++i;
0415     }
0416     const QByteArray tmp = m_data.mid(m_position, i - m_position);
0417     result = tmp.toLongLong(ok);
0418     m_position = i;
0419     return result;
0420 }
0421 
0422 void ImapStreamParser::stripLeadingSpaces()
0423 {
0424     for (int i = m_position; i < m_data.length(); ++i) {
0425         if (m_data.at(i) != ' ') {
0426             m_position = i;
0427             return;
0428         }
0429     }
0430     m_position = m_data.length();
0431 }
0432 
0433 bool ImapStreamParser::waitForMoreData(bool wait)
0434 {
0435     if (wait) {
0436         if (m_socket->bytesAvailable() > 0 || m_socket->waitForReadyRead(30000)) {
0437             m_data.append(m_socket->readAll());
0438         } else {
0439             return false;
0440         }
0441     }
0442     return true;
0443 }
0444 
0445 void ImapStreamParser::setData(const QByteArray &data)
0446 {
0447     m_data = data;
0448 }
0449 
0450 QByteArray ImapStreamParser::readRemainingData()
0451 {
0452     return m_data.mid(m_position);
0453 }
0454 
0455 int ImapStreamParser::availableDataSize() const
0456 {
0457     return m_socket->bytesAvailable() + m_data.size() - m_position;
0458 }
0459 
0460 bool ImapStreamParser::atCommandEnd()
0461 {
0462     int savedPos = m_position;
0463     do {
0464         if (!waitForMoreData(m_position >= m_data.length())) {
0465             throw ImapParserException("Unable to read more data");
0466         }
0467         stripLeadingSpaces();
0468     } while (m_position >= m_data.size());
0469 
0470     if (m_data.at(m_position) == '\n' || m_data.at(m_position) == '\r') {
0471         if (m_data.at(m_position) == '\r') {
0472             ++m_position;
0473         }
0474         if (m_position < m_data.length() && m_data.at(m_position) == '\n') {
0475             ++m_position;
0476         }
0477 
0478         // We'd better empty m_data from time to time before it grows out of control
0479         trimBuffer();
0480 
0481         return true; // command end
0482     }
0483     m_position = savedPos;
0484     return false; // something else
0485 }
0486 
0487 QByteArray ImapStreamParser::readUntilCommandEnd()
0488 {
0489     QByteArray result;
0490     int i = m_position;
0491     int paranthesisBalance = 0;
0492     for (;;) {
0493         if (!waitForMoreData(m_data.length() <= i)) {
0494             m_position = i;
0495             throw ImapParserException("Unable to read more data");
0496         }
0497         if (m_data.at(i) == '{') {
0498             m_position = i - 1;
0499             hasLiteral(); // init literal size
0500             result.append(m_data.mid(i, m_position + 1));
0501             while (!atLiteralEnd()) {
0502                 result.append(readLiteralPart());
0503             }
0504             i = m_position;
0505         }
0506         if (m_data.at(i) == '(') {
0507             paranthesisBalance++;
0508         }
0509         if (m_data.at(i) == ')') {
0510             paranthesisBalance--;
0511         }
0512         if ((i == m_data.length() && paranthesisBalance == 0) || m_data.at(i) == '\n' || m_data.at(i) == '\r') {
0513             break; // command end
0514         }
0515         result.append(m_data.at(i));
0516         ++i;
0517     }
0518     m_position = i;
0519     atCommandEnd();
0520     return result;
0521 }
0522 
0523 void ImapStreamParser::sendContinuationResponse(qint64 size)
0524 {
0525     QByteArray block = "+ Ready for literal data (expecting " + QByteArray::number(size) + " bytes)\r\n";
0526     m_socket->write(block);
0527     m_socket->waitForBytesWritten(30000);
0528 }
0529 
0530 void ImapStreamParser::trimBuffer()
0531 {
0532     if (m_position < 4096) { // right() is expensive, so don't do it for every line
0533         return;
0534     }
0535     m_data = m_data.right(m_data.size() - m_position);
0536     m_position = 0;
0537 }