diff --git a/src/kmime_header_parsing.cpp b/src/kmime_header_parsing.cpp index 8e18f4d..821499f 100644 --- a/src/kmime_header_parsing.cpp +++ b/src/kmime_header_parsing.cpp @@ -1,2105 +1,2124 @@ /* -*- c++ -*- kmime_header_parsing.cpp KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 Marc Mutz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmime_header_parsing.h" #include "kmime_headerfactory_p.h" #include "kmime_headers.h" #include "kmime_util.h" #include "kmime_util_p.h" #include "kmime_codecs.h" #include "kmime_dateformatter.h" #include "kmime_warning.h" #include #include #include #include #include #include #include // for isdigit #include using namespace KMime; using namespace KMime::Types; namespace KMime { namespace HeaderParsing { // parse the encoded-word (scursor points to after the initial '=') bool parseEncodedWord(const char *&scursor, const char *const send, QString &result, QByteArray &language, QByteArray &usedCS, const QByteArray &defaultCS, bool forceCS) { // make sure the caller already did a bit of the work. assert(*(scursor - 1) == '='); // // STEP 1: // scan for the charset/language portion of the encoded-word // char ch = *scursor++; if (ch != '?') { // qDebug() << "first"; //KMIME_WARN_PREMATURE_END_OF( EncodedWord ); return false; } // remember start of charset (ie. just after the initial "=?") and // language (just after the first '*') fields: const char *charsetStart = scursor; const char *languageStart = nullptr; // find delimiting '?' (and the '*' separating charset and language // tags, if any): for (; scursor != send ; scursor++) { if (*scursor == '?') { break; } else if (*scursor == '*' && languageStart == nullptr) { languageStart = scursor + 1; } } // not found? can't be an encoded-word! if (scursor == send || *scursor != '?') { // qDebug() << "second"; KMIME_WARN_PREMATURE_END_OF(EncodedWord); return false; } // extract the language information, if any (if languageStart is 0, // language will be null, too): QByteArray maybeLanguage(languageStart, scursor - languageStart); // extract charset information (keep in mind: the size given to the // ctor is one off due to the \0 terminator): QByteArray maybeCharset(charsetStart, (languageStart ? languageStart - 1 : scursor) - charsetStart); // // STEP 2: // scan for the encoding portion of the encoded-word // // remember start of encoding (just _after_ the second '?'): scursor++; const char *encodingStart = scursor; // find next '?' (ending the encoding tag): for (; scursor != send ; scursor++) { if (*scursor == '?') { break; } } // not found? Can't be an encoded-word! if (scursor == send || *scursor != '?') { // qDebug() << "third"; KMIME_WARN_PREMATURE_END_OF(EncodedWord); return false; } // extract the encoding information: QByteArray maybeEncoding(encodingStart, scursor - encodingStart); // qDebug() << "parseEncodedWord: found charset == \"" << maybeCharset // << "\"; language == \"" << maybeLanguage // << "\"; encoding == \"" << maybeEncoding << "\""; // // STEP 3: // scan for encoded-text portion of encoded-word // // remember start of encoded-text (just after the third '?'): scursor++; const char *encodedTextStart = scursor; // find the '?=' sequence (ending the encoded-text): for (; scursor != send ; scursor++) { if (*scursor == '?') { if (scursor + 1 != send) { if (*(scursor + 1) != '=') { // We expect a '=' after the '?', but we got something else; ignore KMIME_WARN << "Stray '?' in q-encoded word, ignoring this."; continue; } else { // yep, found a '?=' sequence scursor += 2; break; } } else { // The '?' is the last char, but we need a '=' after it! KMIME_WARN_PREMATURE_END_OF(EncodedWord); return false; } } } if (*(scursor - 2) != '?' || *(scursor - 1) != '=' || scursor < encodedTextStart + 2) { KMIME_WARN_PREMATURE_END_OF(EncodedWord); return false; } // set end sentinel for encoded-text: const char *const encodedTextEnd = scursor - 2; // // STEP 4: // setup decoders for the transfer encoding and the charset // // try if there's a codec for the encoding found: KCodecs::Codec *codec = KCodecs::Codec::codecForName(maybeEncoding); if (!codec) { KMIME_WARN_UNKNOWN(Encoding, maybeEncoding); return false; } // get an instance of a corresponding decoder: KCodecs::Decoder *dec = codec->makeDecoder(); assert(dec); // try if there's a (text)codec for the charset found: bool matchOK = false; QTextCodec *textCodec = nullptr; if (forceCS || maybeCharset.isEmpty()) { textCodec = KCharsets::charsets()->codecForName(QLatin1String(defaultCS), matchOK); usedCS = cachedCharset(defaultCS); } else { textCodec = KCharsets::charsets()->codecForName(QLatin1String(maybeCharset), matchOK); if (!matchOK) { //no suitable codec found => use default charset textCodec = KCharsets::charsets()->codecForName(QLatin1String(defaultCS), matchOK); usedCS = cachedCharset(defaultCS); } else { usedCS = cachedCharset(maybeCharset); } } if (!matchOK || !textCodec) { KMIME_WARN_UNKNOWN(Charset, maybeCharset); delete dec; return false; }; // qDebug() << "mimeName(): \"" << textCodec->name() << "\""; // allocate a temporary buffer to store the 8bit text: int encodedTextLength = encodedTextEnd - encodedTextStart; QByteArray buffer; buffer.resize(codec->maxDecodedSizeFor(encodedTextLength)); char *bbegin = buffer.data(); char *bend = bbegin + buffer.length(); // // STEP 5: // do the actual decoding // if (!dec->decode(encodedTextStart, encodedTextEnd, bbegin, bend)) { KMIME_WARN << codec->name() << "codec lies about its maxDecodedSizeFor(" << encodedTextLength << ")\nresult may be truncated"; } result = textCodec->toUnicode(buffer.data(), bbegin - buffer.data()); // qDebug() << "result now: \"" << result << "\""; // cleanup: delete dec; language = maybeLanguage; return true; } static inline void eatWhiteSpace(const char *&scursor, const char *const send) { while (scursor != send && (*scursor == ' ' || *scursor == '\n' || *scursor == '\t' || *scursor == '\r')) { scursor++; } } bool parseAtom(const char*&scursor, const char *const send, QString &result, bool allow8Bit) { QPair maybeResult; if (parseAtom(scursor, send, maybeResult, allow8Bit)) { result = QString::fromLatin1(maybeResult.first, maybeResult.second); return true; } return false; } bool parseAtom(const char*&scursor, const char *const send, QPair &result, bool allow8Bit) { bool success = false; const char *start = scursor; while (scursor != send) { signed char ch = *scursor++; if (ch > 0 && isAText(ch)) { // AText: OK success = true; } else if (allow8Bit && ch < 0) { // 8bit char: not OK, but be tolerant. KMIME_WARN_8BIT(ch); success = true; } else { // CTL or special - marking the end of the atom: // re-set sursor to point to the offending // char and return: scursor--; break; } } result.first = start; result.second = scursor - start; return success; } // FIXME: Remove this and the other parseToken() method. add a new one where "result" is a // QByteArray. bool parseToken(const char*&scursor, const char *const send, QString &result, ParseTokenFlags flags) { QPair maybeResult; if (parseToken(scursor, send, maybeResult, flags)) { result = QString::fromLatin1(maybeResult.first, maybeResult.second); return true; } return false; } bool parseToken(const char*&scursor, const char *const send, QPair &result, ParseTokenFlags flags) { bool success = false; const char *start = scursor; while (scursor != send) { signed char ch = *scursor++; if (ch > 0 && isTText(ch)) { // TText: OK success = true; } else if ((flags & ParseTokenAllow8Bit) && ch < 0) { // 8bit char: not OK, but be tolerant. KMIME_WARN_8BIT(ch); success = true; } else if ((flags & ParseTokenRelaxedTText) && ch == '/') { success = true; } else { // CTL or tspecial - marking the end of the atom: // re-set sursor to point to the offending // char and return: scursor--; break; } } result.first = start; result.second = scursor - start; return success; } #define READ_ch_OR_FAIL if ( scursor == send ) { \ KMIME_WARN_PREMATURE_END_OF( GenericQuotedString ); \ return false; \ } else { \ ch = *scursor++; \ } // known issues: // // - doesn't handle quoted CRLF // FIXME: Why is result a QString? This should be a QByteArray, since at this level, we don't // know about encodings yet! bool parseGenericQuotedString(const char *&scursor, const char *const send, QString &result, bool isCRLF, const char openChar, const char closeChar) { // We are in a quoted-string or domain-literal or comment and the // cursor points to the first char after the openChar. // We will apply unfolding and quoted-pair removal. // We return when we either encounter the end or unescaped openChar // or closeChar. assert(*(scursor - 1) == openChar || *(scursor - 1) == closeChar); while (scursor != send) { char ch = *scursor++; if (ch == closeChar || ch == openChar) { // end of quoted-string or another opening char: // let caller decide what to do. return true; } switch (ch) { case '\\': // quoted-pair // misses "\" CRLF LWSP-char handling, see rfc822, 3.4.5 READ_ch_OR_FAIL; KMIME_WARN_IF_8BIT(ch); result += QLatin1Char(ch); break; case '\r': // ### // The case of lonely '\r' is easy to solve, as they're // not part of Unix Line-ending conventions. // But I see a problem if we are given Unix-native // line-ending-mails, where we cannot determine anymore // whether a given '\n' was part of a CRLF or was occurring // on it's own. READ_ch_OR_FAIL; if (ch != '\n') { // CR on it's own... KMIME_WARN_LONE(CR); result += QLatin1Char('\r'); scursor--; // points to after the '\r' again } else { // CRLF encountered. // lookahead: check for folding READ_ch_OR_FAIL; if (ch == ' ' || ch == '\t') { // correct folding; // position cursor behind the CRLF WSP (unfolding) // and add the WSP to the result result += QLatin1Char(ch); } else { // this is the "shouldn't happen"-case. There is a CRLF // inside a quoted-string without it being part of FWS. // We take it verbatim. KMIME_WARN_NON_FOLDING(CRLF); result += QLatin1String("\r\n"); // the cursor is decremented again, so's we need not // duplicate the whole switch here. "ch" could've been // everything (incl. openChar or closeChar). scursor--; } } break; case '\n': // Note: CRLF has been handled above already! // ### LF needs special treatment, depending on whether isCRLF // is true (we can be sure a lonely '\n' was meant this way) or // false ('\n' alone could have meant LF or CRLF in the original // message. This parser assumes CRLF iff the LF is followed by // either WSP (folding) or NULL (premature end of quoted-string; // Should be fixed, since NULL is allowed as per rfc822). READ_ch_OR_FAIL; if (!isCRLF && (ch == ' ' || ch == '\t')) { // folding // correct folding result += QLatin1Char(ch); } else { // non-folding KMIME_WARN_LONE(LF); result += QLatin1Char('\n'); // pos is decremented, so's we need not duplicate the whole // switch here. ch could've been everything (incl. <">, "\"). scursor--; } break; case '=': { // ### Work around broken clients that send encoded words in quoted-strings // For example, older KMail versions. if (scursor == send) { break; } const char *oldscursor = scursor; QString tmp; QByteArray lang, charset; if (*scursor++ == '?') { --scursor; if (parseEncodedWord(scursor, send, tmp, lang, charset)) { result += tmp; break; } else { scursor = oldscursor; } } else { scursor = oldscursor; } // fall through Q_FALLTHROUGH(); } default: KMIME_WARN_IF_8BIT(ch); result += QLatin1Char(ch); } } return false; } // known issues: // // - doesn't handle encoded-word inside comments. bool parseComment(const char *&scursor, const char *const send, QString &result, bool isCRLF, bool reallySave) { int commentNestingDepth = 1; const char *afterLastClosingParenPos = nullptr; QString maybeCmnt; const char *oldscursor = scursor; assert(*(scursor - 1) == '('); while (commentNestingDepth) { QString cmntPart; if (parseGenericQuotedString(scursor, send, cmntPart, isCRLF, '(', ')')) { assert(*(scursor - 1) == ')' || *(scursor - 1) == '('); // see the kdoc for above function for the possible conditions // we have to check: switch (*(scursor - 1)) { case ')': if (reallySave) { // add the chunk that's now surely inside the comment. result += maybeCmnt; result += cmntPart; if (commentNestingDepth > 1) { // don't add the outermost ')'... result += QLatin1Char(')'); } maybeCmnt.clear(); } afterLastClosingParenPos = scursor; --commentNestingDepth; break; case '(': if (reallySave) { // don't add to "result" yet, because we might find that we // are already outside the (broken) comment... maybeCmnt += cmntPart; maybeCmnt += QLatin1Char('('); } ++commentNestingDepth; break; default: assert(0); } // switch } else { // !parseGenericQuotedString, ie. premature end if (afterLastClosingParenPos) { scursor = afterLastClosingParenPos; } else { scursor = oldscursor; } return false; } } // while return true; } // known issues: none. bool parsePhrase(const char *&scursor, const char *const send, QString &result, bool isCRLF) { enum { None, Phrase, Atom, EncodedWord, QuotedString } found = None; QString tmp; QByteArray lang, charset; const char *successfullyParsed = nullptr; // only used by the encoded-word branch const char *oldscursor; // used to suppress whitespace between adjacent encoded-words // (rfc2047, 6.2): bool lastWasEncodedWord = false; while (scursor != send) { char ch = *scursor++; switch (ch) { case '.': // broken, but allow for intorop's sake if (found == None) { --scursor; return false; } else { if (scursor != send && (*scursor == ' ' || *scursor == '\t')) { result += QLatin1String(". "); } else { result += QLatin1Char('.'); } successfullyParsed = scursor; } break; case '"': // quoted-string tmp.clear(); if (parseGenericQuotedString(scursor, send, tmp, isCRLF, '"', '"')) { successfullyParsed = scursor; assert(*(scursor - 1) == '"'); switch (found) { case None: found = QuotedString; break; case Phrase: case Atom: case EncodedWord: case QuotedString: found = Phrase; result += QLatin1Char(' '); // rfc822, 3.4.4 break; default: assert(0); } lastWasEncodedWord = false; result += tmp; } else { // premature end of quoted string. // What to do? Return leading '"' as special? Return as quoted-string? // We do the latter if we already found something, else signal failure. if (found == None) { return false; } else { result += QLatin1Char(' '); // rfc822, 3.4.4 result += tmp; return true; } } break; case '(': // comment // parse it, but ignore content: tmp.clear(); if (parseComment(scursor, send, tmp, isCRLF, false /*don't bother with the content*/)) { successfullyParsed = scursor; lastWasEncodedWord = false; // strictly interpreting rfc2047, 6.2 } else { if (found == None) { return false; } else { scursor = successfullyParsed; return true; } } break; case '=': // encoded-word tmp.clear(); oldscursor = scursor; lang.clear(); charset.clear(); if (parseEncodedWord(scursor, send, tmp, lang, charset)) { successfullyParsed = scursor; switch (found) { case None: found = EncodedWord; break; case Phrase: case EncodedWord: case Atom: case QuotedString: if (!lastWasEncodedWord) { result += QLatin1Char(' '); // rfc822, 3.4.4 } found = Phrase; break; default: assert(0); } lastWasEncodedWord = true; result += tmp; break; } else { // parse as atom: scursor = oldscursor; } Q_FALLTHROUGH(); // fall though... default: //atom tmp.clear(); scursor--; if (parseAtom(scursor, send, tmp, true /* allow 8bit */)) { successfullyParsed = scursor; switch (found) { case None: found = Atom; break; case Phrase: case Atom: case EncodedWord: case QuotedString: found = Phrase; result += QLatin1Char(' '); // rfc822, 3.4.4 break; default: assert(0); } lastWasEncodedWord = false; result += tmp; } else { if (found == None) { return false; } else { scursor = successfullyParsed; return true; } } } eatWhiteSpace(scursor, send); } return found != None; } // FIXME: This should probably by QByteArray &result instead? bool parseDotAtom(const char *&scursor, const char *const send, QString &result, bool isCRLF) { eatCFWS(scursor, send, isCRLF); // always points to just after the last atom parsed: const char *successfullyParsed; QString tmp; if (!parseAtom(scursor, send, tmp, false /* no 8bit */)) { return false; } result += tmp; successfullyParsed = scursor; while (scursor != send) { // end of header or no '.' -> return if (scursor == send || *scursor != '.') { return true; } scursor++; // eat '.' if (scursor == send || !isAText(*scursor)) { // end of header or no AText, but this time following a '.'!: // reset cursor to just after last successfully parsed char and // return: scursor = successfullyParsed; return true; } // try to parse the next atom: QString maybeAtom; if (!parseAtom(scursor, send, maybeAtom, false /*no 8bit*/)) { scursor = successfullyParsed; return true; } result += QLatin1Char('.'); result += maybeAtom; successfullyParsed = scursor; } scursor = successfullyParsed; return true; } void eatCFWS(const char *&scursor, const char *const send, bool isCRLF) { QString dummy; while (scursor != send) { const char *oldscursor = scursor; char ch = *scursor++; switch (ch) { case ' ': case '\t': // whitespace case '\r': case '\n': // folding continue; case '(': // comment if (parseComment(scursor, send, dummy, isCRLF, false /*don't save*/)) { continue; } scursor = oldscursor; return; default: scursor = oldscursor; return; } } } bool parseDomain(const char *&scursor, const char *const send, QString &result, bool isCRLF) { eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // domain := dot-atom / domain-literal / atom *("." atom) // // equivalent to: // domain = dot-atom / domain-literal, // since parseDotAtom does allow CFWS between atoms and dots if (*scursor == '[') { // domain-literal: QString maybeDomainLiteral; // eat '[': scursor++; while (parseGenericQuotedString(scursor, send, maybeDomainLiteral, isCRLF, '[', ']')) { if (scursor == send) { // end of header: check for closing ']': if (*(scursor - 1) == ']') { // OK, last char was ']': result = maybeDomainLiteral; return true; } else { // not OK, domain-literal wasn't closed: return false; } } // we hit openChar in parseGenericQuotedString. // include it in maybeDomainLiteral and keep on parsing: if (*(scursor - 1) == '[') { maybeDomainLiteral += QLatin1Char('['); continue; } // OK, real end of domain-literal: result = maybeDomainLiteral; return true; } } else { // dot-atom: QString maybeDotAtom; if (parseDotAtom(scursor, send, maybeDotAtom, isCRLF)) { result = maybeDotAtom; // Domain may end with '.', if so preserve it' if (scursor != send && *scursor == '.') { result += QLatin1Char('.'); scursor++; } return true; } } return false; } bool parseObsRoute(const char *&scursor, const char *const send, QStringList &result, bool isCRLF, bool save) { while (scursor != send) { eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // empty entry: if (*scursor == ',') { scursor++; if (save) { result.append(QString()); } continue; } // empty entry ending the list: if (*scursor == ':') { scursor++; if (save) { result.append(QString()); } return true; } // each non-empty entry must begin with '@': if (*scursor != '@') { return false; } else { scursor++; } QString maybeDomain; if (!parseDomain(scursor, send, maybeDomain, isCRLF)) { return false; } if (save) { result.append(maybeDomain); } // eat the following (optional) comma: eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } if (*scursor == ':') { scursor++; return true; } if (*scursor == ',') { scursor++; } } return false; } bool parseAddrSpec(const char *&scursor, const char *const send, AddrSpec &result, bool isCRLF) { // // STEP 1: // local-part := dot-atom / quoted-string / word *("." word) // // this is equivalent to: // local-part := word *("." word) QString maybeLocalPart; QString tmp; while (scursor != send) { // first, eat any whitespace eatCFWS(scursor, send, isCRLF); char ch = *scursor++; switch (ch) { case '.': // dot maybeLocalPart += QLatin1Char('.'); break; case '@': goto SAW_AT_SIGN; break; case '"': // quoted-string tmp.clear(); if (parseGenericQuotedString(scursor, send, tmp, isCRLF, '"', '"')) { maybeLocalPart += tmp; } else { return false; } break; default: // atom scursor--; // re-set scursor to point to ch again tmp.clear(); if (parseAtom(scursor, send, tmp, false /* no 8bit */)) { maybeLocalPart += tmp; } else { return false; // parseAtom can only fail if the first char is non-atext. } break; } } return false; // // STEP 2: // domain // SAW_AT_SIGN: assert(*(scursor - 1) == '@'); QString maybeDomain; if (!parseDomain(scursor, send, maybeDomain, isCRLF)) { return false; } result.localPart = maybeLocalPart; result.domain = maybeDomain; return true; } bool parseAngleAddr(const char *&scursor, const char *const send, AddrSpec &result, bool isCRLF) { // first, we need an opening angle bracket: eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != '<') { return false; } scursor++; // eat '<' eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } if (*scursor == '@' || *scursor == ',') { // obs-route: parse, but ignore: KMIME_WARN << "obsolete source route found! ignoring."; QStringList dummy; if (!parseObsRoute(scursor, send, dummy, isCRLF, false /* don't save */)) { return false; } // angle-addr isn't complete until after the '>': if (scursor == send) { return false; } } // parse addr-spec: AddrSpec maybeAddrSpec; if (!parseAddrSpec(scursor, send, maybeAddrSpec, isCRLF)) { return false; } eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != '>') { return false; } scursor++; result = maybeAddrSpec; return true; } static QString stripQuotes(const QString &input) { const QLatin1Char quotes('"'); if (input.startsWith(quotes) && input.endsWith(quotes)) { QString stripped(input.mid(1, input.size() - 2)); return stripped; } else { return input; } } bool parseMailbox(const char *&scursor, const char *const send, Mailbox &result, bool isCRLF) { eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } AddrSpec maybeAddrSpec; QString maybeDisplayName; // first, try if it's a vanilla addr-spec: const char *oldscursor = scursor; if (parseAddrSpec(scursor, send, maybeAddrSpec, isCRLF)) { result.setAddress(maybeAddrSpec); // check for the obsolete form of display-name (as comment): eatWhiteSpace(scursor, send); if (scursor != send && *scursor == '(') { scursor++; if (!parseComment(scursor, send, maybeDisplayName, isCRLF, true /*keep*/)) { return false; } } result.setName(stripQuotes(maybeDisplayName)); return true; } scursor = oldscursor; // second, see if there's a display-name: if (!parsePhrase(scursor, send, maybeDisplayName, isCRLF)) { // failed: reset cursor, note absent display-name maybeDisplayName.clear(); scursor = oldscursor; } else { // succeeded: eat CFWS eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } } // third, parse the angle-addr: if (!parseAngleAddr(scursor, send, maybeAddrSpec, isCRLF)) { return false; } if (maybeDisplayName.isNull()) { // check for the obsolete form of display-name (as comment): eatWhiteSpace(scursor, send); if (scursor != send && *scursor == '(') { scursor++; if (!parseComment(scursor, send, maybeDisplayName, isCRLF, true /*keep*/)) { return false; } } } result.setName(stripQuotes(maybeDisplayName)); result.setAddress(maybeAddrSpec); return true; } bool parseGroup(const char *&scursor, const char *const send, Address &result, bool isCRLF) { // group := display-name ":" [ mailbox-list / CFWS ] ";" [CFWS] // // equivalent to: // group := display-name ":" [ obs-mbox-list ] ";" eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // get display-name: QString maybeDisplayName; if (!parsePhrase(scursor, send, maybeDisplayName, isCRLF)) { return false; } // get ":": eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != ':') { return false; } // KDE5 TODO: Don't expose displayName as public, but rather add setter for it that // automatically calls removeBidiControlChars result.displayName = removeBidiControlChars(maybeDisplayName); // get obs-mbox-list (may contain empty entries): scursor++; while (scursor != send) { eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // empty entry: if (*scursor == ',') { scursor++; continue; } // empty entry ending the list: if (*scursor == ';') { scursor++; return true; } Mailbox maybeMailbox; if (!parseMailbox(scursor, send, maybeMailbox, isCRLF)) { return false; } result.mailboxList.append(maybeMailbox); eatCFWS(scursor, send, isCRLF); // premature end: if (scursor == send) { return false; } // regular end of the list: if (*scursor == ';') { scursor++; return true; } // eat regular list entry separator: if (*scursor == ',') { scursor++; } } return false; } bool parseAddress(const char *&scursor, const char *const send, Address &result, bool isCRLF) { // address := mailbox / group eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // first try if it's a single mailbox: Mailbox maybeMailbox; const char *oldscursor = scursor; if (parseMailbox(scursor, send, maybeMailbox, isCRLF)) { // yes, it is: result.displayName.clear(); result.mailboxList.append(maybeMailbox); return true; } scursor = oldscursor; Address maybeAddress; // no, it's not a single mailbox. Try if it's a group: if (!parseGroup(scursor, send, maybeAddress, isCRLF)) { return false; } result = maybeAddress; return true; } bool parseAddressList(const char *&scursor, const char *const send, AddressList &result, bool isCRLF) { while (scursor != send) { eatCFWS(scursor, send, isCRLF); // end of header: this is OK. if (scursor == send) { return true; } // empty entry: ignore: if (*scursor == ',') { scursor++; continue; } // broken clients might use ';' as list delimiter, accept that as well if (*scursor == ';') { scursor++; continue; } // parse one entry Address maybeAddress; if (!parseAddress(scursor, send, maybeAddress, isCRLF)) { return false; } result.append(maybeAddress); eatCFWS(scursor, send, isCRLF); // end of header: this is OK. if (scursor == send) { return true; } // comma separating entries: eat it. if (*scursor == ',') { scursor++; } } return true; } // FIXME: Get rid of the very ugly "QStringOrQPair" thing. At this level, we are supposed to work // on byte arrays, not strings! The result parameter should be a simple // QPair, which is the attribute name and the value. bool parseParameter(const char *&scursor, const char *const send, QPair &result, bool isCRLF) { // parameter = regular-parameter / extended-parameter // regular-parameter = regular-parameter-name "=" value // extended-parameter = // value = token / quoted-string // // note that rfc2231 handling is out of the scope of this function. // Therefore we return the attribute as QString and the value as // (start,length) tupel if we see that the value is encoded // (trailing asterisk), for parseParameterList to decode... eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // // parse the parameter name: // // FIXME: maybeAttribute should be a QByteArray QString maybeAttribute; if (!parseToken(scursor, send, maybeAttribute, ParseTokenNoFlag)) { return false; } eatCFWS(scursor, send, isCRLF); // premature end: not OK (haven't seen '=' yet). if (scursor == send || *scursor != '=') { return false; } scursor++; // eat '=' eatCFWS(scursor, send, isCRLF); if (scursor == send) { // don't choke on attribute=, meaning the value was omitted: if (maybeAttribute.endsWith(QLatin1Char('*'))) { KMIME_WARN << "attribute ends with \"*\", but value is empty!" "Chopping away \"*\"."; maybeAttribute.truncate(maybeAttribute.length() - 1); } result = qMakePair(maybeAttribute.toLower(), QStringOrQPair()); return true; } const char *oldscursor = scursor; // // parse the parameter value: // QStringOrQPair maybeValue; if (*scursor == '"') { // value is a quoted-string: scursor++; if (maybeAttribute.endsWith(QLatin1Char('*'))) { // attributes ending with "*" designate extended-parameters, // which cannot have quoted-strings as values. So we remove the // trailing "*" to not confuse upper layers. KMIME_WARN << "attribute ends with \"*\", but value is a quoted-string!" "Chopping away \"*\"."; maybeAttribute.truncate(maybeAttribute.length() - 1); } if (!parseGenericQuotedString(scursor, send, maybeValue.qstring, isCRLF)) { scursor = oldscursor; result = qMakePair(maybeAttribute.toLower(), QStringOrQPair()); return false; // this case needs further processing by upper layers!! } } else { // value is a token: if (!parseToken(scursor, send, maybeValue.qpair, ParseTokenRelaxedTText)) { scursor = oldscursor; result = qMakePair(maybeAttribute.toLower(), QStringOrQPair()); return false; // this case needs further processing by upper layers!! } } result = qMakePair(maybeAttribute.toLower(), maybeValue); return true; } // FIXME: Get rid of QStringOrQPair: Use a simply QMap for "result" // instead! bool parseRawParameterList(const char *&scursor, const char *const send, QMap &result, bool isCRLF) { // we use parseParameter() consecutively to obtain a map of raw // attributes to raw values. "Raw" here means that we don't do // rfc2231 decoding and concatenation. This is left to // parseParameterList(), which will call this function. // // The main reason for making this chunk of code a separate // (private) method is that we can deal with broken parameters // _here_ and leave the rfc2231 handling solely to // parseParameterList(), which will still be enough work. while (scursor != send) { eatCFWS(scursor, send, isCRLF); // empty entry ending the list: OK. if (scursor == send) { return true; } // empty list entry: ignore. if (*scursor == ';') { scursor++; continue; } QPair maybeParameter; if (!parseParameter(scursor, send, maybeParameter, isCRLF)) { // we need to do a bit of work if the attribute is not // NULL. These are the cases marked with "needs further // processing" in parseParameter(). Specifically, parsing of the // token or the quoted-string, which should represent the value, // failed. We take the easy way out and simply search for the // next ';' to start parsing again. (Another option would be to // take the text between '=' and ';' as value) if (maybeParameter.first.isNull()) { return false; } while (scursor != send) { if (*scursor++ == ';') { goto IS_SEMICOLON; } } // scursor == send case: end of list. return true; IS_SEMICOLON: // *scursor == ';' case: parse next entry. continue; } // successful parsing brings us here: result.insert(maybeParameter.first, maybeParameter.second); eatCFWS(scursor, send, isCRLF); // end of header: ends list. if (scursor == send) { return true; } // regular separator: eat it. if (*scursor == ';') { scursor++; } } return true; } static void decodeRFC2231Value(KCodecs::Codec *&rfc2231Codec, QTextCodec *&textcodec, bool isContinuation, QString &value, QPair &source, QByteArray &charset) { // // parse the raw value into (charset,language,text): // const char *decBegin = source.first; const char *decCursor = decBegin; const char *decEnd = decCursor + source.second; if (!isContinuation) { // find the first single quote while (decCursor != decEnd) { if (*decCursor == '\'') { break; } else { decCursor++; } } if (decCursor == decEnd) { // there wasn't a single single quote at all! // take the whole value to be in latin-1: KMIME_WARN << "No charset in extended-initial-value." "Assuming \"iso-8859-1\"."; value += QString::fromLatin1(decBegin, source.second); return; } charset = QByteArray(decBegin, decCursor - decBegin); const char *oldDecCursor = ++decCursor; // find the second single quote (we ignore the language tag): while (decCursor != decEnd) { if (*decCursor == '\'') { break; } else { decCursor++; } } if (decCursor == decEnd) { KMIME_WARN << "No language in extended-initial-value." "Trying to recover."; decCursor = oldDecCursor; } else { decCursor++; } // decCursor now points to the start of the // "extended-other-values": // // get the decoders: // bool matchOK = false; textcodec = KCharsets::charsets()->codecForName(QLatin1String(charset), matchOK); if (!matchOK) { textcodec = nullptr; KMIME_WARN_UNKNOWN(Charset, charset); } } if (!rfc2231Codec) { rfc2231Codec = KCodecs::Codec::codecForName("x-kmime-rfc2231"); assert(rfc2231Codec); } if (!textcodec) { value += QString::fromLatin1(decCursor, decEnd - decCursor); return; } KCodecs::Decoder *dec = rfc2231Codec->makeDecoder(); assert(dec); // // do the decoding: // QByteArray buffer; buffer.resize(rfc2231Codec->maxDecodedSizeFor(decEnd - decCursor)); QByteArray::Iterator bit = buffer.begin(); QByteArray::ConstIterator bend = buffer.end(); if (!dec->decode(decCursor, decEnd, bit, bend)) { KMIME_WARN << rfc2231Codec->name() << "codec lies about its maxDecodedSizeFor()" << endl << "result may be truncated"; } value += textcodec->toUnicode(buffer.begin(), bit - buffer.begin()); // qDebug() << "value now: \"" << value << "\""; // cleanup: delete dec; } // known issues: // - permutes rfc2231 continuations when the total number of parts // exceeds 10 (other-sections then becomes *xy, ie. two digits) bool parseParameterListWithCharset(const char *&scursor, const char *const send, QMap &result, QByteArray &charset, bool isCRLF) { // parse the list into raw attribute-value pairs: QMap rawParameterList; if (!parseRawParameterList(scursor, send, rawParameterList, isCRLF)) { return false; } if (rawParameterList.isEmpty()) { return true; } // decode rfc 2231 continuations and alternate charset encoding: // NOTE: this code assumes that what QMapIterator delivers is sorted // by the key! KCodecs::Codec *rfc2231Codec = nullptr; QTextCodec *textcodec = nullptr; QString attribute; QString value; enum Mode { NoMode = 0x0, Continued = 0x1, Encoded = 0x2 }; enum EncodingMode { NoEncoding, RFC2047, RFC2231 }; QMap::Iterator it, end = rawParameterList.end(); for (it = rawParameterList.begin() ; it != end ; ++it) { if (attribute.isNull() || !it.key().startsWith(attribute)) { // // new attribute: // // store the last attribute/value pair in the result map now: if (!attribute.isNull()) { result.insert(attribute, value); } // and extract the information from the new raw attribute: value.clear(); attribute = it.key(); int mode = NoMode; EncodingMode encodingMode = NoEncoding; // is the value rfc2331-encoded? if (attribute.endsWith(QLatin1Char('*'))) { attribute.truncate(attribute.length() - 1); mode |= Encoded; encodingMode = RFC2231; } // is the value rfc2047-encoded? if (!(*it).qstring.isNull() && (*it).qstring.contains(QLatin1String("=?"))) { mode |= Encoded; encodingMode = RFC2047; } // is the value continued? if (attribute.endsWith(QLatin1String("*0"))) { attribute.truncate(attribute.length() - 2); mode |= Continued; } // // decode if necessary: // if (mode & Encoded) { if (encodingMode == RFC2231) { decodeRFC2231Value(rfc2231Codec, textcodec, false, /* isn't continuation */ value, (*it).qpair, charset); } else if (encodingMode == RFC2047) { value += KCodecs::decodeRFC2047String((*it).qstring.toLatin1(), &charset); } } else { // not encoded. if ((*it).qpair.first) { value += QString::fromLatin1((*it).qpair.first, (*it).qpair.second); } else { value += (*it).qstring; } } // // shortcut-processing when the value isn't encoded: // if (!(mode & Continued)) { // save result already: result.insert(attribute, value); // force begin of a new attribute: attribute.clear(); } } else { // it.key().startsWith( attribute ) // // continuation // // ignore the section and trust QMap to have sorted the keys: if (it.key().endsWith(QLatin1Char('*'))) { // encoded decodeRFC2231Value(rfc2231Codec, textcodec, true, /* is continuation */ value, (*it).qpair, charset); } else { // not encoded if ((*it).qpair.first) { value += QString::fromLatin1((*it).qpair.first, (*it).qpair.second); } else { value += (*it).qstring; } } } } // write last attr/value pair: if (!attribute.isNull()) { result.insert(attribute, value); } return true; } bool parseParameterList(const char *&scursor, const char *const send, QMap &result, bool isCRLF) { QByteArray charset; return parseParameterListWithCharset(scursor, send, result, charset, isCRLF); } static const char stdDayNames[][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const int stdDayNamesLen = sizeof stdDayNames / sizeof *stdDayNames; static bool parseDayName(const char *&scursor, const char *const send) { // check bounds: if (send - scursor < 3) { return false; } for (int i = 0 ; i < stdDayNamesLen ; ++i) { if (qstrnicmp(scursor, stdDayNames[i], 3) == 0) { scursor += 3; // qDebug() << "found" << stdDayNames[i]; return true; } } return false; } static const char stdMonthNames[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const int stdMonthNamesLen = sizeof stdMonthNames / sizeof *stdMonthNames; static bool parseMonthName(const char *&scursor, const char *const send, int &result) { // check bounds: if (send - scursor < 3) { return false; } for (result = 0 ; result < stdMonthNamesLen ; ++result) { if (qstrnicmp(scursor, stdMonthNames[result], 3) == 0) { scursor += 3; return true; } } // not found: return false; } static const struct { const char tzName[5]; long int secsEastOfGMT; } timeZones[] = { // rfc 822 timezones: { "GMT", 0 }, { "UT", 0 }, { "EDT", -4 * 3600 }, { "EST", -5 * 3600 }, { "MST", -5 * 3600 }, { "CST", -6 * 3600 }, { "MDT", -6 * 3600 }, { "MST", -7 * 3600 }, { "PDT", -7 * 3600 }, { "PST", -8 * 3600 }, // common, non-rfc-822 zones: { "CET", 1 * 3600 }, { "MET", 1 * 3600 }, { "UTC", 0 }, { "CEST", 2 * 3600 }, { "BST", 1 * 3600 }, // rfc 822 military timezones: { "Z", 0 }, { "A", -1 * 3600 }, { "B", -2 * 3600 }, { "C", -3 * 3600 }, { "D", -4 * 3600 }, { "E", -5 * 3600 }, { "F", -6 * 3600 }, { "G", -7 * 3600 }, { "H", -8 * 3600 }, { "I", -9 * 3600 }, // J is not used! { "K", -10 * 3600 }, { "L", -11 * 3600 }, { "M", -12 * 3600 }, { "N", 1 * 3600 }, { "O", 2 * 3600 }, { "P", 3 * 3600 }, { "Q", 4 * 3600 }, { "R", 5 * 3600 }, { "S", 6 * 3600 }, { "T", 7 * 3600 }, { "U", 8 * 3600 }, { "V", 9 * 3600 }, { "W", 10 * 3600 }, { "X", 11 * 3600 }, { "Y", 12 * 3600 }, }; static const int timeZonesLen = sizeof timeZones / sizeof *timeZones; static bool parseAlphaNumericTimeZone(const char *&scursor, const char *const send, long int &secsEastOfGMT, bool &timeZoneKnown) { // allow the timezone to be wrapped in quotes; bug 260761 if (scursor < send && *scursor == '"') { scursor++; if (scursor == send) { return false; } } QPair maybeTimeZone(nullptr, 0); if (!parseToken(scursor, send, maybeTimeZone, ParseTokenNoFlag)) { return false; } for (int i = 0 ; i < timeZonesLen ; ++i) { if (qstrnicmp(timeZones[i].tzName, maybeTimeZone.first, maybeTimeZone.second) == 0) { scursor += maybeTimeZone.second; secsEastOfGMT = timeZones[i].secsEastOfGMT; timeZoneKnown = true; if (scursor < send && *scursor == '"') { scursor++; } return true; } } // don't choke just because we don't happen to know the time zone KMIME_WARN_UNKNOWN(time zone, QByteArray(maybeTimeZone.first, maybeTimeZone.second)); secsEastOfGMT = 0; timeZoneKnown = false; return true; } // parse a number and return the number of digits parsed: int parseDigits(const char *&scursor, const char *const send, int &result) { result = 0; int digits = 0; for (; scursor != send && isdigit(*scursor) ; scursor++, digits++) { result *= 10; result += int(*scursor - '0'); } return digits; } static bool parseTimeOfDay(const char *&scursor, const char *const send, int &hour, int &min, int &sec, bool isCRLF = false) { // time-of-day := 2DIGIT [CFWS] ":" [CFWS] 2DIGIT [ [CFWS] ":" 2DIGIT ] // // 2DIGIT representing "hour": // if (!parseDigits(scursor, send, hour)) { return false; } eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != ':') { return false; } scursor++; // eat ':' eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // // 2DIGIT representing "minute": // if (!parseDigits(scursor, send, min)) { return false; } eatCFWS(scursor, send, isCRLF); if (scursor == send) { return true; // seconds are optional } // // let's see if we have a 2DIGIT representing "second": // if (*scursor == ':') { // yepp, there are seconds: scursor++; // eat ':' eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } if (!parseDigits(scursor, send, sec)) { return false; } } else { sec = 0; } return true; } bool parseTime(const char *&scursor, const char *send, int &hour, int &min, int &sec, long int &secsEastOfGMT, bool &timeZoneKnown, bool isCRLF) { // time := time-of-day CFWS ( zone / obs-zone ) // // obs-zone := "UT" / "GMT" / // "EST" / "EDT" / ; -0500 / -0400 // "CST" / "CDT" / ; -0600 / -0500 // "MST" / "MDT" / ; -0700 / -0600 // "PST" / "PDT" / ; -0800 / -0700 // "A"-"I" / "a"-"i" / // "K"-"Z" / "k"-"z" eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } if (!parseTimeOfDay(scursor, send, hour, min, sec, isCRLF)) { return false; } eatCFWS(scursor, send, isCRLF); // there might be no timezone but a year following if ((scursor == send) || isdigit(*scursor)) { timeZoneKnown = false; secsEastOfGMT = 0; return true; // allow missing timezone } timeZoneKnown = true; if (*scursor == '+' || *scursor == '-') { // remember and eat '-'/'+': const char sign = *scursor++; // numerical timezone: int maybeTimeZone; const int tzDigits = parseDigits(scursor, send, maybeTimeZone); if (tzDigits != 4) { // Allow timezones in 02:00 format if (tzDigits == 2 && scursor != send && *scursor == ':') { scursor++; int maybeTimeZone2; if (parseDigits(scursor, send, maybeTimeZone2) != 2) { return false; } maybeTimeZone = maybeTimeZone * 100 + maybeTimeZone2; } else { return false; } } secsEastOfGMT = 60 * (maybeTimeZone / 100 * 60 + maybeTimeZone % 100); if (sign == '-') { secsEastOfGMT *= -1; if (secsEastOfGMT == 0) { timeZoneKnown = false; // -0000 means indetermined tz } } } else { // maybe alphanumeric timezone: if (!parseAlphaNumericTimeZone(scursor, send, secsEastOfGMT, timeZoneKnown)) { return false; } } return true; } bool parseDateTime(const char *&scursor, const char *const send, QDateTime &result, bool isCRLF) { // Parsing date-time; strict mode: // // date-time := [ [CFWS] day-name [CFWS] "," ] ; wday // (expanded) [CFWS] 1*2DIGIT CFWS month-name CFWS 2*DIGIT [CFWS] ; date // time // // day-name := "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun" // month-name := "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" / // "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec" result = QDateTime(); QDateTime maybeDateTime; eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // // let's see if there's a day-of-week: // if (parseDayName(scursor, send)) { eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // day-name should be followed by ',' but we treat it as optional: if (*scursor == ',') { scursor++; // eat ',' eatCFWS(scursor, send, isCRLF); } } int maybeMonth = -1; bool asctimeFormat = false; // ANSI-C asctime() format is: Wed Jun 30 21:49:08 1993 if (!isdigit(*scursor) && parseMonthName(scursor, send, maybeMonth)) { asctimeFormat = true; eatCFWS(scursor, send, isCRLF); } // // 1*2DIGIT representing "day" (of month): // int maybeDay; if (!parseDigits(scursor, send, maybeDay)) { return false; } eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // ignore ","; bug 54098 if (*scursor == ',') { scursor++; } // // month-name: // if (!asctimeFormat && !parseMonthName(scursor, send, maybeMonth)) { return false; } if (scursor == send) { return false; } assert(maybeMonth >= 0); assert(maybeMonth <= 11); ++maybeMonth; // 0-11 -> 1-12 eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // check for "year HH:MM:SS" or only "HH:MM:SS" (or "H:MM:SS") bool timeAfterYear = true; if ((send - scursor > 3) && ((scursor[1] == ':') || (scursor[2] == ':'))) { timeAfterYear = false; // first read time, then year } // // 2*DIGIT representing "year": // int maybeYear = 0; if (timeAfterYear && !parseDigits(scursor, send, maybeYear)) { return false; } eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // // time // int maybeHour, maybeMinute, maybeSecond; long int secsEastOfGMT; bool timeZoneKnown = true; if (!parseTime(scursor, send, maybeHour, maybeMinute, maybeSecond, secsEastOfGMT, timeZoneKnown, isCRLF)) { return false; } // in asctime() the year follows the time if (!timeAfterYear) { eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } if (!parseDigits(scursor, send, maybeYear)) { return false; } } // RFC 2822 4.3 processing: if (maybeYear < 50) { maybeYear += 2000; } else if (maybeYear < 1000) { maybeYear += 1900; } // else keep as is if (maybeYear < 1900) { return false; // rfc2822, 3.3 } maybeDateTime.setDate(QDate(maybeYear, maybeMonth, maybeDay)); maybeDateTime.setTime(QTime(maybeHour, maybeMinute, maybeSecond)); if (!maybeDateTime.isValid()) { return false; } result = QDateTime(maybeDateTime.date(), maybeDateTime.time(), Qt::OffsetFromUTC, secsEastOfGMT); //result = QDateTime( maybeDateTime, QDateTime::Spec( QDateTime::OffsetFromUTC, secsEastOfGMT ) ); if (!result.isValid()) { return false; } return true; } -Headers::Base *extractFirstHeader(QByteArray &head) +namespace { + +Headers::Base *extractHeader(const QByteArray &head, const int headerStart, int &endOfFieldBody) { + Headers::Base *header = {}; + + int startOfFieldBody = head.indexOf(':', headerStart); + if (startOfFieldBody < 0) { + return nullptr; + } + + const QByteArray rawType = QByteArray::fromRawData(head.constData() + headerStart, startOfFieldBody - headerStart); + + startOfFieldBody++; //skip the ':' + if (startOfFieldBody < head.size() - 1 && head[startOfFieldBody] == ' ') { // skip the space after the ':', if there's any + startOfFieldBody++; + } + bool folded = false; - Headers::Base *header = nullptr; + endOfFieldBody = findHeaderLineEnd(head, startOfFieldBody, &folded); + // rawFieldBody references actual data from 'head' + QByteArray rawFieldBody = QByteArray::fromRawData(head.constData() + startOfFieldBody, endOfFieldBody - startOfFieldBody); + if (folded) { + rawFieldBody = unfoldHeader(rawFieldBody); + } - int startOfFieldBody = head.indexOf(':'); + // We might get an invalid mail without a field name, don't crash on that. + if (!rawType.isEmpty()) { + header = HeaderFactory::createHeader(rawType); + } + if (!header) { + //qWarning() << "Returning Generic header of type" << rawType; + header = new Headers::Generic(rawType.constData(), rawType.size()); + } + header->from7BitString(rawFieldBody); - if (startOfFieldBody > -1) { //there is another header - // Split the original data - head[startOfFieldBody] = '\0'; - // rawType references the actual data from 'head' - QByteArray rawType = QByteArray::fromRawData(head.constData(), startOfFieldBody); + return header; +} - startOfFieldBody++; //skip the ':' - if (head[startOfFieldBody] == ' ') { // skip the space after the ':', if there - startOfFieldBody++; - } - int endOfFieldBody = findHeaderLineEnd(head, startOfFieldBody, &folded); - // rawFieldBody references actual data from 'heaed' - QByteArray rawFieldBody = QByteArray::fromRawData(head.constData() + startOfFieldBody, endOfFieldBody - startOfFieldBody); - if (folded) { - rawFieldBody = unfoldHeader(rawFieldBody); - } - // We might get an invalid mail without a field name, don't crash on that. - if (!rawType.isEmpty()) { - header = HeaderFactory::createHeader(rawType); - } - if (!header) { - //qWarning() << "Returning Generic header of type" << rawType; - header = new Headers::Generic(rawType.constData()); - } - header->from7BitString(rawFieldBody); +} +Headers::Base *extractFirstHeader(QByteArray &head) +{ + int endOfFieldBody = 0; + auto header = extractHeader(head, 0, endOfFieldBody); + if (header) { head.remove(0, endOfFieldBody + 1); } else { head.clear(); } return header; } void extractHeaderAndBody(const QByteArray &content, QByteArray &header, QByteArray &body) { header.clear(); body.clear(); // empty header if (content.startsWith('\n')) { body = content.right(content.length() - 1); return; } int pos = content.indexOf("\n\n", 0); if (pos > -1) { header = content.left(++pos); //header *must* end with "\n" !! body = content.mid(pos + 1); if (body.startsWith("\n")) { body = "\n" + body; } } else { header = content; } } QVector parseHeaders(const QByteArray &head) { QVector ret; - Headers::Base *h; - QByteArray copy = head; - while ((h = extractFirstHeader(copy))) { - ret << h; + int cursor = 0; + while (cursor < head.size()) { + const int headerStart = cursor; + int endOfFieldBody; + if (auto header = extractHeader(head, headerStart, endOfFieldBody)) { + ret << header; + cursor = endOfFieldBody + 1; + } else { + break; + } } return ret; } } // namespace HeaderParsing } // namespace KMime diff --git a/src/kmime_headerfactory.cpp b/src/kmime_headerfactory.cpp index a7282ac..6952520 100644 --- a/src/kmime_headerfactory.cpp +++ b/src/kmime_headerfactory.cpp @@ -1,122 +1,122 @@ /* kmime_header_factory.cpp KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling MIME data and defines the HeaderFactory class. @brief Defines the HeaderFactory class. @authors Constantin Berzan \ */ #include "kmime_headerfactory_p.h" #include "kmime_headers.h" using namespace KMime; using namespace KMime::Headers; #define mk_header(hdr) \ - if (qstricmp(type.constData(), hdr ::staticType()) == 0) \ + if (qstrnicmp(type.constData(), hdr ::staticType(), type.size()) == 0) \ return new hdr Headers::Base *HeaderFactory::createHeader(const QByteArray &type) { Q_ASSERT(!type.isEmpty()); switch (type.at(0)) { case 'b': case 'B': mk_header(Bcc); break; case 'c': case 'C': mk_header(Cc); mk_header(ContentDescription); mk_header(ContentDisposition); mk_header(ContentID); mk_header(ContentLocation); mk_header(ContentTransferEncoding); mk_header(ContentType); mk_header(Control); break; case 'd': case 'D': mk_header(Date); break; case 'f': case 'F': mk_header(FollowUpTo); mk_header(From); break; case 'i': case 'I': mk_header(InReplyTo); break; case 'k': case 'K': mk_header(Keywords); break; case 'l': case 'L': mk_header(Lines); break; case 'm': case 'M': mk_header(MailCopiesTo); mk_header(MessageID); mk_header(MIMEVersion); break; case 'n': case 'N': mk_header(Newsgroups); break; case 'o': case 'O': mk_header(Organization); break; case 'r': case 'R': mk_header(References); mk_header(ReplyTo); mk_header(ReturnPath); break; case 's': case 'S': mk_header(Sender); mk_header(Subject); mk_header(Supersedes); break; case 't': case 'T': mk_header(To); break; case 'u': case 'U': mk_header(UserAgent); break; } return nullptr; } #undef mk_header diff --git a/src/kmime_headers.cpp b/src/kmime_headers.cpp index b4340a5..783785d 100644 --- a/src/kmime_headers.cpp +++ b/src/kmime_headers.cpp @@ -1,2148 +1,2149 @@ /* -*- c++ -*- kmime_headers.cpp KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the various header classes: - header's base class defining the common interface - generic base classes for different types of fields - incompatible, Structured-based field classes - compatible, Unstructured-based field classes @brief Defines the various headers classes. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #include "kmime_headers.h" #include "kmime_headers_p.h" #include "kmime_util.h" #include "kmime_util_p.h" #include "kmime_codecs.h" #include "kmime_content.h" #include "kmime_header_parsing.h" #include "kmime_headerfactory_p.h" #include "kmime_warning.h" #include #include #include #include #include #include #include // macro to generate a default constructor implementation #define kmime_mk_trivial_ctor( subclass, baseclass ) \ subclass::subclass() : baseclass() \ { \ } \ \ subclass::~subclass() {} // end kmime_mk_trivial_ctor #define kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \ subclass::subclass() : baseclass( new subclass##Private ) \ { \ } \ \ subclass::~subclass() { \ Q_D(subclass); \ delete d; \ d_ptr = 0; \ } // end kmime_mk_trivial_ctor_with_dptr #define kmime_mk_trivial_ctor_with_name( subclass, baseclass, name ) \ kmime_mk_trivial_ctor( subclass, baseclass ) \ \ const char *subclass::type() const \ { \ return staticType(); \ } \ const char *subclass::staticType() { return #name; } #define kmime_mk_trivial_ctor_with_name_and_dptr( subclass, baseclass, name ) \ kmime_mk_trivial_ctor_with_dptr( subclass, baseclass ) \ const char *subclass::type() const { return staticType(); } \ const char *subclass::staticType() { return #name; } #define kmime_mk_dptr_ctor( subclass, baseclass ) \ subclass::subclass( subclass##Private *d ) : baseclass( d ) {} using namespace KMime; using namespace KMime::Headers; using namespace KMime::Types; using namespace KMime::HeaderParsing; namespace KMime { namespace Headers { //--------------------------------------- Base::Base() : d_ptr(new BasePrivate) { } Base::Base(BasePrivate *dd) : d_ptr(dd) { } Base::~Base() { delete d_ptr; d_ptr = nullptr; } QByteArray Base::rfc2047Charset() const { if (d_ptr->encCS.isEmpty()) { return Content::defaultCharset(); } else { return d_ptr->encCS; } } void Base::setRFC2047Charset(const QByteArray &cs) { d_ptr->encCS = cachedCharset(cs); } const char *Base::type() const { return ""; } bool Base::is(const char *t) const { return qstricmp(t, type()) == 0; } bool Base::isMimeHeader() const { return qstrnicmp(type(), "Content-", 8) == 0; } QByteArray Base::typeIntro() const { return QByteArray(type()) + ": "; } //-------------------------------------- namespace Generics { //------------------------------ //@cond PRIVATE kmime_mk_dptr_ctor(Unstructured, Base) //@endcond Unstructured::Unstructured() : Base(new UnstructuredPrivate) { } Unstructured::~Unstructured() { Q_D(Unstructured); delete d; d_ptr = nullptr; } void Unstructured::from7BitString(const QByteArray &s) { Q_D(Unstructured); d->decoded = KCodecs::decodeRFC2047String(s, &d->encCS, Content::defaultCharset()); } QByteArray Unstructured::as7BitString(bool withHeaderType) const { const Q_D(Unstructured); QByteArray result; if (withHeaderType) { result = typeIntro(); } result += encodeRFC2047String(d->decoded, d->encCS) ; return result; } void Unstructured::fromUnicodeString(const QString &s, const QByteArray &b) { Q_D(Unstructured); d->decoded = s; d->encCS = cachedCharset(b); } QString Unstructured::asUnicodeString() const { return d_func()->decoded; } void Unstructured::clear() { Q_D(Unstructured); d->decoded.truncate(0); } bool Unstructured::isEmpty() const { return d_func()->decoded.isEmpty(); } //------------------------------ //------------------------------ Structured::Structured() : Base(new StructuredPrivate) { } kmime_mk_dptr_ctor(Structured, Base) Structured::~Structured() { Q_D(Structured); delete d; d_ptr = nullptr; } void Structured::from7BitString(const QByteArray &s) { Q_D(Structured); if (d->encCS.isEmpty()) { d->encCS = Content::defaultCharset(); } const char *cursor = s.constData(); parse(cursor, cursor + s.length()); } QString Structured::asUnicodeString() const { return QString::fromLatin1(as7BitString(false)); } void Structured::fromUnicodeString(const QString &s, const QByteArray &b) { Q_D(Structured); d->encCS = cachedCharset(b); from7BitString(s.toLatin1()); } //------------------------------ //-----
------------------------- Address::Address() : Structured(new AddressPrivate) { } kmime_mk_dptr_ctor(Address, Structured) Address:: ~Address() { } // helper method used in AddressList and MailboxList static bool stringToMailbox(const QByteArray &address, const QString &displayName, Types::Mailbox &mbox) { Types::AddrSpec addrSpec; mbox.setName(displayName); const char *cursor = address.constData(); if (!parseAngleAddr(cursor, cursor + address.length(), addrSpec)) { if (!parseAddrSpec(cursor, cursor + address.length(), addrSpec)) { qWarning() << "stringToMailbox: Invalid address"; return false; } } mbox.setAddress(addrSpec); return true; } //-----
------------------------- //------------------------------ kmime_mk_trivial_ctor_with_dptr(MailboxList, Address) kmime_mk_dptr_ctor(MailboxList, Address) QByteArray MailboxList::as7BitString(bool withHeaderType) const { const Q_D(MailboxList); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv = typeIntro(); } foreach (const Types::Mailbox &mbox, d->mailboxList) { rv += mbox.as7BitString(d->encCS); rv += ", "; } rv.resize(rv.length() - 2); return rv; } void MailboxList::fromUnicodeString(const QString &s, const QByteArray &b) { Q_D(MailboxList); d->encCS = cachedCharset(b); from7BitString(encodeRFC2047Sentence(s, b)); } QString MailboxList::asUnicodeString() const { Q_D(const MailboxList); return Mailbox::listToUnicodeString(d->mailboxList); } void MailboxList::clear() { Q_D(MailboxList); d->mailboxList.clear(); } bool MailboxList::isEmpty() const { return d_func()->mailboxList.isEmpty(); } void MailboxList::addAddress(const Types::Mailbox &mbox) { Q_D(MailboxList); d->mailboxList.append(mbox); } void MailboxList::addAddress(const QByteArray &address, const QString &displayName) { Q_D(MailboxList); Types::Mailbox mbox; if (stringToMailbox(address, displayName, mbox)) { d->mailboxList.append(mbox); } } QVector MailboxList::addresses() const { QVector rv; rv.reserve(d_func()->mailboxList.count()); foreach (const Types::Mailbox &mbox, d_func()->mailboxList) { rv.append(mbox.address()); } return rv; } QStringList MailboxList::displayNames() const { Q_D(const MailboxList); QStringList rv; rv.reserve(d->mailboxList.count()); foreach (const Types::Mailbox &mbox, d->mailboxList) { if (mbox.hasName()) rv.append(mbox.name()); else rv.append(QString::fromLatin1(mbox.address())); } return rv; } QString MailboxList::displayString() const { Q_D(const MailboxList); if (d->mailboxList.size() == 1) { // fast-path to avoid temporary QStringList in the common case of just one From address const auto& mbox = d->mailboxList.at(0); if (mbox.hasName()) return mbox.name(); else return QString::fromLatin1(mbox.address()); } return displayNames().join(QStringLiteral(", ")); } Types::Mailbox::List MailboxList::mailboxes() const { return d_func()->mailboxList; } bool MailboxList::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(MailboxList); // examples: // from := "From:" mailbox-list CRLF // sender := "Sender:" mailbox CRLF // parse an address-list: QVector maybeAddressList; if (!parseAddressList(scursor, send, maybeAddressList, isCRLF)) { return false; } d->mailboxList.clear(); d->mailboxList.reserve(maybeAddressList.count()); // extract the mailboxes and complain if there are groups: foreach (const auto &it, maybeAddressList) { if (!(it).displayName.isEmpty()) { KMIME_WARN << "mailbox groups in header disallowing them! Name: \"" << (it).displayName << "\"" << endl; } d->mailboxList += (it).mailboxList; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(SingleMailbox, MailboxList) //@endcond bool SingleMailbox::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(MailboxList); if (!MailboxList::parse(scursor, send, isCRLF)) { return false; } if (d->mailboxList.count() > 1) { KMIME_WARN << "multiple mailboxes in header allowing only a single one!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(AddressList, Address) kmime_mk_dptr_ctor(AddressList, Address) //@endcond QByteArray AddressList::as7BitString(bool withHeaderType) const { const Q_D(AddressList); if (d->addressList.isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv = typeIntro(); } foreach (const Types::Address &addr, d->addressList) { foreach (const Types::Mailbox &mbox, addr.mailboxList) { rv += mbox.as7BitString(d->encCS); rv += ", "; } } rv.resize(rv.length() - 2); return rv; } void AddressList::fromUnicodeString(const QString &s, const QByteArray &b) { Q_D(AddressList); d->encCS = cachedCharset(b); from7BitString(encodeRFC2047Sentence(s, b)); } QString AddressList::asUnicodeString() const { Q_D(const AddressList); QStringList rv; foreach (const Types::Address &addr, d->addressList) { rv.reserve(rv.size() + addr.mailboxList.size()); foreach (const Types::Mailbox &mbox, addr.mailboxList) { rv.append(mbox.prettyAddress()); } } return rv.join(QStringLiteral(", ")); } void AddressList::clear() { Q_D(AddressList); d->addressList.clear(); } bool AddressList::isEmpty() const { return d_func()->addressList.isEmpty(); } void AddressList::addAddress(const Types::Mailbox &mbox) { Q_D(AddressList); Types::Address addr; addr.mailboxList.append(mbox); d->addressList.append(addr); } void AddressList::addAddress(const QByteArray &address, const QString &displayName) { Q_D(AddressList); Types::Address addr; Types::Mailbox mbox; if (stringToMailbox(address, displayName, mbox)) { addr.mailboxList.append(mbox); d->addressList.append(addr); } } QVector AddressList::addresses() const { QVector rv; foreach (const Types::Address &addr, d_func()->addressList) { foreach (const Types::Mailbox &mbox, addr.mailboxList) { rv.append(mbox.address()); } } return rv; } QStringList AddressList::displayNames() const { Q_D(const AddressList); QStringList rv; foreach (const Types::Address &addr, d->addressList) { foreach (const Types::Mailbox &mbox, addr.mailboxList) { if (mbox.hasName()) rv.append(mbox.name()); else rv.append(QString::fromLatin1(mbox.address())); } } return rv; } QString AddressList::displayString() const { // optimize for single entry and avoid creation of the QStringList in that case? return displayNames().join(QStringLiteral(", ")); } Types::Mailbox::List AddressList::mailboxes() const { Types::Mailbox::List rv; foreach (const Types::Address &addr, d_func()->addressList) { foreach (const Types::Mailbox &mbox, addr.mailboxList) { rv.append(mbox); } } return rv; } bool AddressList::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(AddressList); QVector maybeAddressList; if (!parseAddressList(scursor, send, maybeAddressList, isCRLF)) { return false; } d->addressList = maybeAddressList; return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(Token, Structured) kmime_mk_dptr_ctor(Token, Structured) //@endcond QByteArray Token::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } if (withHeaderType) { return typeIntro() + d_func()->token; } return d_func()->token; } void Token::clear() { Q_D(Token); d->token.clear(); } bool Token::isEmpty() const { return d_func()->token.isEmpty(); } QByteArray Token::token() const { return d_func()->token; } void Token::setToken(const QByteArray &t) { Q_D(Token); d->token = t; } bool Token::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Token); clear(); eatCFWS(scursor, send, isCRLF); // must not be empty: if (scursor == send) { return false; } QPair maybeToken; if (!parseToken(scursor, send, maybeToken, ParseTokenNoFlag)) { return false; } d->token = QByteArray(maybeToken.first, maybeToken.second); // complain if trailing garbage is found: eatCFWS(scursor, send, isCRLF); if (scursor != send) { KMIME_WARN << "trailing garbage after token in header allowing " "only a single token!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(PhraseList, Structured) //@endcond QByteArray PhraseList::as7BitString(bool withHeaderType) const { const Q_D(PhraseList); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv = typeIntro(); } for (int i = 0; i < d->phraseList.count(); ++i) { // FIXME: only encode when needed, quote when needed, etc. rv += encodeRFC2047String(d->phraseList[i], d->encCS, false, false); if (i != d->phraseList.count() - 1) { rv += ", "; } } return rv; } QString PhraseList::asUnicodeString() const { return d_func()->phraseList.join(QStringLiteral(", ")); } void PhraseList::clear() { Q_D(PhraseList); d->phraseList.clear(); } bool PhraseList::isEmpty() const { return d_func()->phraseList.isEmpty(); } QStringList PhraseList::phrases() const { return d_func()->phraseList; } bool PhraseList::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(PhraseList); d->phraseList.clear(); while (scursor != send) { eatCFWS(scursor, send, isCRLF); // empty entry ending the list: OK. if (scursor == send) { return true; } // empty entry: ignore. if (*scursor == ',') { scursor++; continue; } QString maybePhrase; if (!parsePhrase(scursor, send, maybePhrase, isCRLF)) { return false; } d->phraseList.append(maybePhrase); eatCFWS(scursor, send, isCRLF); // non-empty entry ending the list: OK. if (scursor == send) { return true; } // comma separating the phrases: eat. if (*scursor == ',') { scursor++; } } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(DotAtom, Structured) //@endcond QByteArray DotAtom::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } rv += d_func()->dotAtom.toLatin1(); // FIXME: encoding? return rv; } QString DotAtom::asUnicodeString() const { return d_func()->dotAtom; } void DotAtom::clear() { Q_D(DotAtom); d->dotAtom.clear(); } bool DotAtom::isEmpty() const { return d_func()->dotAtom.isEmpty(); } bool DotAtom::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(DotAtom); QString maybeDotAtom; if (!parseDotAtom(scursor, send, maybeDotAtom, isCRLF)) { return false; } d->dotAtom = maybeDotAtom; eatCFWS(scursor, send, isCRLF); if (scursor != send) { KMIME_WARN << "trailing garbage after dot-atom in header allowing " "only a single dot-atom!" << endl; } return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(Parametrized, Structured) kmime_mk_dptr_ctor(Parametrized, Structured) //@endcond QByteArray Parametrized::as7BitString(bool withHeaderType) const { const Q_D(Parametrized); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } bool first = true; for (QMap::ConstIterator it = d->parameterHash.constBegin(); it != d->parameterHash.constEnd(); ++it) { if (!first) { rv += "; "; } else { first = false; } if (isUsAscii(it.value())) { rv += it.key().toLatin1() + '='; QByteArray tmp = it.value().toLatin1(); addQuotes(tmp, true); // force quoting, eg. for whitespaces in parameter value rv += tmp; } else { if (useOutlookAttachmentEncoding()) { rv += it.key().toLatin1() + '='; qDebug() << "doing:" << it.value() << QLatin1String(d->encCS); rv += "\"" + encodeRFC2047String(it.value(), d->encCS) + "\""; } else { rv += it.key().toLatin1() + "*="; rv += encodeRFC2231String(it.value(), d->encCS); } } } return rv; } QString Parametrized::parameter(const QString &key) const { return d_func()->parameterHash.value(key.toLower()); } bool Parametrized::hasParameter(const QString &key) const { return d_func()->parameterHash.contains(key.toLower()); } void Parametrized::setParameter(const QString &key, const QString &value) { Q_D(Parametrized); d->parameterHash.insert(key.toLower(), value); } bool Parametrized::isEmpty() const { return d_func()->parameterHash.isEmpty(); } void Parametrized::clear() { Q_D(Parametrized); d->parameterHash.clear(); } bool Parametrized::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Parametrized); d->parameterHash.clear(); QByteArray charset; if (!parseParameterListWithCharset(scursor, send, d->parameterHash, charset, isCRLF)) { return false; } d->encCS = charset; return true; } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(Ident, Address) kmime_mk_dptr_ctor(Ident, Address) //@endcond QByteArray Ident::as7BitString(bool withHeaderType) const { const Q_D(Ident); if (d->msgIdList.isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv = typeIntro(); } foreach (const Types::AddrSpec &addr, d->msgIdList) { if (!addr.isEmpty()) { const QString asString = addr.asString(); rv += '<'; if (!asString.isEmpty()) { rv += asString.toLatin1(); // FIXME: change parsing to use QByteArrays } rv += "> "; } } if (!rv.isEmpty()) { rv.resize(rv.length() - 1); } return rv; } void Ident::clear() { Q_D(Ident); d->msgIdList.clear(); d->cachedIdentifier.clear(); } bool Ident::isEmpty() const { return d_func()->msgIdList.isEmpty(); } bool Ident::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Ident); // msg-id := "<" id-left "@" id-right ">" // id-left := dot-atom-text / no-fold-quote / local-part // id-right := dot-atom-text / no-fold-literal / domain // // equivalent to: // msg-id := angle-addr d->msgIdList.clear(); d->cachedIdentifier.clear(); while (scursor != send) { eatCFWS(scursor, send, isCRLF); // empty entry ending the list: OK. if (scursor == send) { return true; } // empty entry: ignore. if (*scursor == ',') { scursor++; continue; } AddrSpec maybeMsgId; if (!parseAngleAddr(scursor, send, maybeMsgId, isCRLF)) { return false; } d->msgIdList.append(maybeMsgId); eatCFWS(scursor, send, isCRLF); // header end ending the list: OK. if (scursor == send) { return true; } // regular item separator: eat it. if (*scursor == ',') { scursor++; } } return true; } QVector Ident::identifiers() const { QVector rv; foreach (const Types::AddrSpec &addr, d_func()->msgIdList) { if (!addr.isEmpty()) { const QString asString = addr.asString(); if (!asString.isEmpty()) { rv.append(asString.toLatin1()); // FIXME: change parsing to use QByteArrays } } } return rv; } void Ident::fromIdent(const Ident* ident) { d_func()->encCS = ident->d_func()->encCS; d_func()->msgIdList = ident->d_func()->msgIdList; d_func()->cachedIdentifier = ident->d_func()->cachedIdentifier; } void Ident::appendIdentifier(const QByteArray &id) { Q_D(Ident); QByteArray tmp = id; if (!tmp.startsWith('<')) { tmp.prepend('<'); } if (!tmp.endsWith('>')) { tmp.append('>'); } AddrSpec msgId; const char *cursor = tmp.constData(); if (parseAngleAddr(cursor, cursor + tmp.length(), msgId)) { d->msgIdList.append(msgId); } else { qWarning() << "Unable to parse address spec!"; } } //------------------------------ //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_dptr(SingleIdent, Ident) kmime_mk_dptr_ctor(SingleIdent, Ident) //@endcond QByteArray SingleIdent::identifier() const { if (d_func()->msgIdList.isEmpty()) { return QByteArray(); } if (d_func()->cachedIdentifier.isEmpty()) { const Types::AddrSpec &addr = d_func()->msgIdList.first(); if (!addr.isEmpty()) { const QString asString = addr.asString(); if (!asString.isEmpty()) { d_func()->cachedIdentifier = asString.toLatin1();// FIXME: change parsing to use QByteArrays } } } return d_func()->cachedIdentifier; } void SingleIdent::setIdentifier(const QByteArray &id) { Q_D(SingleIdent); d->msgIdList.clear(); d->cachedIdentifier.clear(); appendIdentifier(id); } bool SingleIdent::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(SingleIdent); if (!Ident::parse(scursor, send, isCRLF)) { return false; } if (d->msgIdList.count() > 1) { KMIME_WARN << "more than one msg-id in header " << "allowing only a single one!" << endl; } return true; } //------------------------------ } // namespace Generics //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(ReturnPath, Generics::Address, Return-Path) //@endcond QByteArray ReturnPath::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } rv += '<' + d_func()->mailbox.as7BitString(d_func()->encCS) + '>'; return rv; } void ReturnPath::clear() { Q_D(ReturnPath); d->mailbox.setAddress(Types::AddrSpec()); d->mailbox.setName(QString()); } bool ReturnPath::isEmpty() const { const Q_D(ReturnPath); return !d->mailbox.hasAddress() && !d->mailbox.hasName(); } bool ReturnPath::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ReturnPath); eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } const char *oldscursor = scursor; Mailbox maybeMailbox; if (!parseMailbox(scursor, send, maybeMailbox, isCRLF)) { // mailbox parsing failed, but check for empty brackets: scursor = oldscursor; if (*scursor != '<') { return false; } scursor++; eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != '>') { return false; } scursor++; // prepare a Null mailbox: AddrSpec emptyAddrSpec; maybeMailbox.setName(QString()); maybeMailbox.setAddress(emptyAddrSpec); } else { // check that there was no display-name: if (maybeMailbox.hasName()) { KMIME_WARN << "display-name \"" << maybeMailbox.name() << "\" in Return-Path!" << endl; } } d->mailbox = maybeMailbox; // see if that was all: eatCFWS(scursor, send, isCRLF); // and warn if it wasn't: if (scursor != send) { KMIME_WARN << "trailing garbage after angle-addr in Return-Path!" << endl; } return true; } //------------------------------ //------------------------------------ // NOTE: Do *not* register Generic with HeaderFactory, since its type() is changeable. Generic::Generic() : Generics::Unstructured(new GenericPrivate) { } -Generic::Generic(const char *t) : Generics::Unstructured(new GenericPrivate) +Generic::Generic(const char *t, int len) : Generics::Unstructured(new GenericPrivate) { - setType(t); + setType(t, len); } Generic::~Generic() { Q_D(Generic); delete d; d_ptr = nullptr; } void Generic::clear() { Q_D(Generic); delete[] d->type; d->type = nullptr; Unstructured::clear(); } bool Generic::isEmpty() const { return d_func()->type == nullptr || Unstructured::isEmpty(); } const char *Generic::type() const { return d_func()->type; } -void Generic::setType(const char *type) +void Generic::setType(const char *type, int len) { Q_D(Generic); if (d->type) { delete[] d->type; } if (type) { - d->type = new char[strlen(type) + 1]; - strcpy(d->type, type); + const int l = (len < 0 ? strlen(type) : len) + 1; + d->type = new char[l]; + qstrncpy(d->type, type, l); } else { d->type = nullptr; } } //------------------------------------ //---------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name(MessageID, Generics::SingleIdent, Message-ID) //@endcond void MessageID::generate(const QByteArray &fqdn) { setIdentifier('<' + uniqueString() + '@' + fqdn + '>'); } //--------------------------------- //------------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(Control, Generics::Structured, Control) //@endcond QByteArray Control::as7BitString(bool withHeaderType) const { const Q_D(Control); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } rv += d->name; if (!d->parameter.isEmpty()) { rv += ' ' + d->parameter; } return rv; } void Control::clear() { Q_D(Control); d->name.clear(); d->parameter.clear(); } bool Control::isEmpty() const { return d_func()->name.isEmpty(); } QByteArray Control::controlType() const { return d_func()->name; } QByteArray Control::parameter() const { return d_func()->parameter; } bool Control::isCancel() const { return d_func()->name.toLower() == "cancel"; } void Control::setCancel(const QByteArray &msgid) { Q_D(Control); d->name = "cancel"; d->parameter = msgid; } bool Control::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Control); clear(); eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } const char *start = scursor; while (scursor != send && !isspace(*scursor)) { ++scursor; } d->name = QByteArray(start, scursor - start); eatCFWS(scursor, send, isCRLF); d->parameter = QByteArray(scursor, send - scursor); return true; } //----------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(MailCopiesTo, Generics::AddressList, Mail-Copies-To) //@endcond QByteArray MailCopiesTo::as7BitString(bool withHeaderType) const { QByteArray rv; if (withHeaderType) { rv += typeIntro(); } if (!AddressList::isEmpty()) { rv += AddressList::as7BitString(false); } else { if (d_func()->alwaysCopy) { rv += "poster"; } else if (d_func()->neverCopy) { rv += "nobody"; } } return rv; } QString MailCopiesTo::asUnicodeString() const { if (!AddressList::isEmpty()) { return AddressList::asUnicodeString(); } if (d_func()->alwaysCopy) { return QStringLiteral("poster"); } if (d_func()->neverCopy) { return QStringLiteral("nobody"); } return QString(); } void MailCopiesTo::clear() { Q_D(MailCopiesTo); AddressList::clear(); d->alwaysCopy = false; d->neverCopy = false; } bool MailCopiesTo::isEmpty() const { return AddressList::isEmpty() && !(d_func()->alwaysCopy || d_func()->neverCopy); } bool MailCopiesTo::alwaysCopy() const { return !AddressList::isEmpty() || d_func()->alwaysCopy; } void MailCopiesTo::setAlwaysCopy() { Q_D(MailCopiesTo); clear(); d->alwaysCopy = true; } bool MailCopiesTo::neverCopy() const { return d_func()->neverCopy; } void MailCopiesTo::setNeverCopy() { Q_D(MailCopiesTo); clear(); d->neverCopy = true; } bool MailCopiesTo::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(MailCopiesTo); clear(); if (send - scursor == 5) { if (qstrnicmp("never", scursor, 5) == 0) { d->neverCopy = true; return true; } } if (send - scursor == 6) { if (qstrnicmp("always", scursor, 6) == 0 || qstrnicmp("poster", scursor, 6) == 0) { d->alwaysCopy = true; return true; } if (qstrnicmp("nobody", scursor, 6) == 0) { d->neverCopy = true; return true; } } return AddressList::parse(scursor, send, isCRLF); } //------------------------------ //--------------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(Date, Generics::Structured, Date) //@endcond QByteArray Date::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } //QT5 fix port to QDateTime Qt::RFC2822Date is not enough we need to fix it. We need to use QLocale("C") + add "ddd, "; //rv += d_func()->dateTime.toString( Qt::RFC2822Date ).toLatin1(); rv += QLocale::c().toString(d_func()->dateTime, QStringLiteral("ddd, ")).toLatin1(); rv += d_func()->dateTime.toString(Qt::RFC2822Date).toLatin1(); return rv; } void Date::clear() { Q_D(Date); d->dateTime = QDateTime(); } bool Date::isEmpty() const { return d_func()->dateTime.isNull() || !d_func()->dateTime.isValid(); } QDateTime Date::dateTime() const { return d_func()->dateTime; } void Date::setDateTime(const QDateTime & dt) { Q_D(Date); d->dateTime = dt; } int Date::ageInDays() const { QDate today = QDate::currentDate(); return dateTime().date().daysTo(today); } bool Date::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Date); return parseDateTime(scursor, send, d->dateTime, isCRLF); } //-------------------------------------- //--------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(Newsgroups, Generics::Structured, Newsgroups) kmime_mk_trivial_ctor_with_name(FollowUpTo, Newsgroups, Followup-To) //@endcond QByteArray Newsgroups::as7BitString(bool withHeaderType) const { const Q_D(Newsgroups); if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } for (int i = 0; i < d->groups.count(); ++i) { rv += d->groups[ i ]; if (i != d->groups.count() - 1) { rv += ','; } } return rv; } void Newsgroups::fromUnicodeString(const QString & s, const QByteArray & b) { Q_UNUSED(b); Q_D(Newsgroups); from7BitString(s.toUtf8()); d->encCS = cachedCharset("UTF-8"); } QString Newsgroups::asUnicodeString() const { return QString::fromUtf8(as7BitString(false)); } void Newsgroups::clear() { Q_D(Newsgroups); d->groups.clear(); } bool Newsgroups::isEmpty() const { return d_func()->groups.isEmpty(); } QVector Newsgroups::groups() const { return d_func()->groups; } void Newsgroups::setGroups(const QVector &groups) { Q_D(Newsgroups); d->groups = groups; } bool Newsgroups::isCrossposted() const { return d_func()->groups.count() >= 2; } bool Newsgroups::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Newsgroups); clear(); forever { eatCFWS(scursor, send, isCRLF); if (scursor != send && *scursor == ',') { ++scursor; } eatCFWS(scursor, send, isCRLF); if (scursor == send) { return true; } const char *start = scursor; while (scursor != send && !isspace(*scursor) && *scursor != ',') { ++scursor; } QByteArray group(start, scursor - start); d->groups.append(group); } return true; } //-------------------------------- //-------------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(Lines, Generics::Structured, Lines) //@endcond QByteArray Lines::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray num; num.setNum(d_func()->lines); if (withHeaderType) { return typeIntro() + num; } return num; } QString Lines::asUnicodeString() const { if (isEmpty()) { return QString(); } return QString::number(d_func()->lines); } void Lines::clear() { Q_D(Lines); d->lines = -1; } bool Lines::isEmpty() const { return d_func()->lines == -1; } int Lines::numberOfLines() const { return d_func()->lines; } void Lines::setNumberOfLines(int lines) { Q_D(Lines); d->lines = lines; } bool Lines::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(Lines); eatCFWS(scursor, send, isCRLF); if (parseDigits(scursor, send, d->lines) == 0) { clear(); return false; } return true; } //------------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(ContentType, Generics::Parametrized, Content-Type) //@endcond bool ContentType::isEmpty() const { return d_func()->mimeType.isEmpty(); } void ContentType::clear() { Q_D(ContentType); d->category = CCsingle; d->mimeType.clear(); Parametrized::clear(); } QByteArray ContentType::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } rv += mimeType(); if (!Parametrized::isEmpty()) { rv += "; " + Parametrized::as7BitString(false); } return rv; } QByteArray ContentType::mimeType() const { Q_D(const ContentType); return d->mimeType; } QByteArray ContentType::mediaType() const { Q_D(const ContentType); const int pos = d->mimeType.indexOf('/'); if (pos < 0) { return d->mimeType; } else { return d->mimeType.left(pos); } } QByteArray ContentType::subType() const { Q_D(const ContentType); const int pos = d->mimeType.indexOf('/'); if (pos < 0) { return QByteArray(); } else { return d->mimeType.mid(pos + 1); } } void ContentType::setMimeType(const QByteArray & mimeType) { Q_D(ContentType); d->mimeType = mimeType; Parametrized::clear(); if (isMultipart()) { d->category = CCcontainer; } else { d->category = CCsingle; } } bool ContentType::isMediatype(const char *mediatype) const { Q_D(const ContentType); const int len = strlen(mediatype); return qstrnicmp(d->mimeType.constData(), mediatype, len) == 0 && (d->mimeType.at(len) == '/' || d->mimeType.size() == len); } bool ContentType::isSubtype(const char *subtype) const { Q_D(const ContentType); const int pos = d->mimeType.indexOf('/'); if (pos < 0) { return false; } const int len = strlen(subtype); return qstrnicmp(d->mimeType.constData() + pos + 1, subtype, len) == 0 && d->mimeType.size() == pos + len + 1; } bool ContentType::isMimeType(const char* mimeType) const { Q_D(const ContentType); return qstricmp(d->mimeType.constData(), mimeType) == 0; } bool ContentType::isText() const { return (isMediatype("text") || isEmpty()); } bool ContentType::isPlainText() const { return (qstricmp(d_func()->mimeType.constData(), "text/plain") == 0 || isEmpty()); } bool ContentType::isHTMLText() const { return qstricmp(d_func()->mimeType.constData(), "text/html") == 0; } bool ContentType::isImage() const { return isMediatype("image"); } bool ContentType::isMultipart() const { return isMediatype("multipart"); } bool ContentType::isPartial() const { return qstricmp(d_func()->mimeType.constData(), "message/partial") == 0; } QByteArray ContentType::charset() const { QByteArray ret = parameter(QStringLiteral("charset")).toLatin1(); if (ret.isEmpty()) { //return the default-charset if necessary ret = Content::defaultCharset(); } return ret; } void ContentType::setCharset(const QByteArray & s) { setParameter(QStringLiteral("charset"), QString::fromLatin1(s)); } QByteArray ContentType::boundary() const { return parameter(QStringLiteral("boundary")).toLatin1(); } void ContentType::setBoundary(const QByteArray & s) { setParameter(QStringLiteral("boundary"), QString::fromLatin1(s)); } QString ContentType::name() const { return parameter(QStringLiteral("name")); } void ContentType::setName(const QString & s, const QByteArray & cs) { Q_D(ContentType); d->encCS = cs; setParameter(QStringLiteral("name"), s); } QByteArray ContentType::id() const { return parameter(QStringLiteral("id")).toLatin1(); } void ContentType::setId(const QByteArray & s) { setParameter(QStringLiteral("id"), QString::fromLatin1(s)); } int ContentType::partialNumber() const { QByteArray p = parameter(QStringLiteral("number")).toLatin1(); if (!p.isEmpty()) { return p.toInt(); } else { return -1; } } int ContentType::partialCount() const { QByteArray p = parameter(QStringLiteral("total")).toLatin1(); if (!p.isEmpty()) { return p.toInt(); } else { return -1; } } contentCategory ContentType::category() const { return d_func()->category; } void ContentType::setCategory(contentCategory c) { Q_D(ContentType); d->category = c; } void ContentType::setPartialParams(int total, int number) { setParameter(QStringLiteral("number"), QString::number(number)); setParameter(QStringLiteral("total"), QString::number(total)); } bool ContentType::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ContentType); // content-type: type "/" subtype *(";" parameter) clear(); eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; // empty header } // type QPair maybeMimeType; if (!parseToken(scursor, send, maybeMimeType, ParseTokenNoFlag)) { return false; } // subtype eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != '/') { return false; } scursor++; eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } QPair maybeSubType; if (!parseToken(scursor, send, maybeSubType, ParseTokenNoFlag)) { return false; } d->mimeType.reserve(maybeMimeType.second + maybeSubType.second + 1); d->mimeType = QByteArray(maybeMimeType.first, maybeMimeType.second).toLower() + '/' + QByteArray(maybeSubType.first, maybeSubType.second).toLower(); // parameter list eatCFWS(scursor, send, isCRLF); if (scursor == send) { goto success; // no parameters } if (*scursor != ';') { return false; } scursor++; if (!Parametrized::parse(scursor, send, isCRLF)) { return false; } // adjust category success: if (isMultipart()) { d->category = CCcontainer; } else { d->category = CCsingle; } return true; } //------------------------------ //--------------------------- kmime_mk_trivial_ctor_with_name_and_dptr(ContentID, SingleIdent, Content-ID) kmime_mk_dptr_ctor(ContentID, SingleIdent) bool ContentID::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ContentID); // Content-id := "<" contentid ">" // contentid := now whitespaces const char *origscursor = scursor; if (!SingleIdent::parse(scursor, send, isCRLF)) { scursor = origscursor; d->msgIdList.clear(); d->cachedIdentifier.clear(); while (scursor != send) { eatCFWS(scursor, send, isCRLF); // empty entry ending the list: OK. if (scursor == send) { return true; } // empty entry: ignore. if (*scursor == ',') { scursor++; continue; } AddrSpec maybeContentId; // Almost parseAngleAddr if (scursor == send || *scursor != '<') { return false; } scursor++; // eat '<' eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } // Save chars untill '>'' QString result; if (!parseDotAtom(scursor, send, result, false)) { return false; } eatCFWS(scursor, send, isCRLF); if (scursor == send || *scursor != '>') { return false; } scursor++; // /Almost parseAngleAddr maybeContentId.localPart = result; d->msgIdList.append(maybeContentId); eatCFWS(scursor, send, isCRLF); // header end ending the list: OK. if (scursor == send) { return true; } // regular item separator: eat it. if (*scursor == ',') { scursor++; } } return true; } else { return true; } } //--------------------------- //--------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(ContentTransferEncoding, Generics::Token, Content-Transfer-Encoding) //@endcond typedef struct { const char *s; int e; } encTableType; static const encTableType encTable[] = { { "7Bit", CE7Bit }, { "8Bit", CE8Bit }, { "quoted-printable", CEquPr }, { "base64", CEbase64 }, { "x-uuencode", CEuuenc }, { "binary", CEbinary }, { nullptr, 0} }; void ContentTransferEncoding::clear() { Q_D(ContentTransferEncoding); d->decoded = true; d->cte = CE7Bit; Token::clear(); } contentEncoding ContentTransferEncoding::encoding() const { return d_func()->cte; } void ContentTransferEncoding::setEncoding(contentEncoding e) { Q_D(ContentTransferEncoding); d->cte = e; for (int i = 0; encTable[i].s != nullptr; ++i) { if (d->cte == encTable[i].e) { setToken(encTable[i].s); break; } } } bool ContentTransferEncoding::isDecoded() const { return d_func()->decoded; } void ContentTransferEncoding::setDecoded(bool decoded) { Q_D(ContentTransferEncoding); d->decoded = decoded; } bool ContentTransferEncoding::needToEncode() const { const Q_D(ContentTransferEncoding); return d->decoded && (d->cte == CEquPr || d->cte == CEbase64); } bool ContentTransferEncoding::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ContentTransferEncoding); clear(); if (!Token::parse(scursor, send, isCRLF)) { return false; } // TODO: error handling in case of an unknown encoding? for (int i = 0; encTable[i].s != nullptr; ++i) { if (qstricmp(token().constData(), encTable[i].s) == 0) { d->cte = (contentEncoding)encTable[i].e; break; } } d->decoded = (d->cte == CE7Bit || d->cte == CE8Bit); return true; } //-------------------------------- //------------------------------- //@cond PRIVATE kmime_mk_trivial_ctor_with_name_and_dptr(ContentDisposition, Generics::Parametrized, Content-Disposition) //@endcond QByteArray ContentDisposition::as7BitString(bool withHeaderType) const { if (isEmpty()) { return QByteArray(); } QByteArray rv; if (withHeaderType) { rv += typeIntro(); } if (d_func()->disposition == CDattachment) { rv += "attachment"; } else if (d_func()->disposition == CDinline) { rv += "inline"; } else { return QByteArray(); } if (!Parametrized::isEmpty()) { rv += "; " + Parametrized::as7BitString(false); } return rv; } bool ContentDisposition::isEmpty() const { return d_func()->disposition == CDInvalid; } void ContentDisposition::clear() { Q_D(ContentDisposition); d->disposition = CDInvalid; Parametrized::clear(); } contentDisposition ContentDisposition::disposition() const { return d_func()->disposition; } void ContentDisposition::setDisposition(contentDisposition disp) { Q_D(ContentDisposition); d->disposition = disp; } QString KMime::Headers::ContentDisposition::filename() const { return parameter(QStringLiteral("filename")); } void ContentDisposition::setFilename(const QString & filename) { setParameter(QStringLiteral("filename"), filename); } bool ContentDisposition::parse(const char *&scursor, const char *const send, bool isCRLF) { Q_D(ContentDisposition); clear(); // token QByteArray token; eatCFWS(scursor, send, isCRLF); if (scursor == send) { return false; } QPair maybeToken; if (!parseToken(scursor, send, maybeToken, ParseTokenNoFlag)) { return false; } token = QByteArray(maybeToken.first, maybeToken.second).toLower(); if (token == "inline") { d->disposition = CDinline; } else if (token == "attachment") { d->disposition = CDattachment; } else { return false; } // parameter list eatCFWS(scursor, send, isCRLF); if (scursor == send) { return true; // no parameters } if (*scursor != ';') { return false; } scursor++; return Parametrized::parse(scursor, send, isCRLF); } //------------------------------ //@cond PRIVATE kmime_mk_trivial_ctor_with_name(Subject, Generics::Unstructured, Subject) //@endcond Base *createHeader(const QByteArray & type) { return HeaderFactory::createHeader(type); } //@cond PRIVATE kmime_mk_trivial_ctor_with_name(ContentDescription, Generics::Unstructured, Content-Description) kmime_mk_trivial_ctor_with_name(ContentLocation, Generics::Unstructured, Content-Location) kmime_mk_trivial_ctor_with_name(From, Generics::MailboxList, From) kmime_mk_trivial_ctor_with_name(Sender, Generics::SingleMailbox, Sender) kmime_mk_trivial_ctor_with_name(To, Generics::AddressList, To) kmime_mk_trivial_ctor_with_name(Cc, Generics::AddressList, Cc) kmime_mk_trivial_ctor_with_name(Bcc, Generics::AddressList, Bcc) kmime_mk_trivial_ctor_with_name(ReplyTo, Generics::AddressList, Reply-To) kmime_mk_trivial_ctor_with_name(Keywords, Generics::PhraseList, Keywords) kmime_mk_trivial_ctor_with_name(MIMEVersion, Generics::DotAtom, MIME-Version) kmime_mk_trivial_ctor_with_name(Supersedes, Generics::SingleIdent, Supersedes) kmime_mk_trivial_ctor_with_name(InReplyTo, Generics::Ident, In-Reply-To) kmime_mk_trivial_ctor_with_name(References, Generics::Ident, References) kmime_mk_trivial_ctor_with_name(Organization, Generics::Unstructured, Organization) kmime_mk_trivial_ctor_with_name(UserAgent, Generics::Unstructured, User-Agent) //@endcond } // namespace Headers } // namespace KMime diff --git a/src/kmime_headers.h b/src/kmime_headers.h index 0d4d7f2..b0c1de7 100644 --- a/src/kmime_headers.h +++ b/src/kmime_headers.h @@ -1,1461 +1,1461 @@ /* -*- c++ -*- kmime_headers.h KMime, the KDE Internet mail/usenet news message library. Copyright (c) 2001-2002 the KMime authors. See file AUTHORS for details Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling @ref MIME data and defines the various header classes: - header's base class defining the common interface - generic base classes for different types of fields - incompatible, Structured-based field classes - compatible, Unstructured-based field classes @brief Defines the various headers classes. @authors the KMime authors (see AUTHORS file), Volker Krause \ */ #ifndef __KMIME_HEADERS_H__ #define __KMIME_HEADERS_H__ #include "kmime_export.h" #include "kmime_header_parsing.h" #include #include #include #include #include #include #include namespace KMime { class Content; namespace Headers { class BasePrivate; enum contentCategory { CCsingle, CCcontainer, CCmixedPart, CCalternativePart }; /** Various possible values for the "Content-Transfer-Encoding" header. */ enum contentEncoding { CE7Bit, ///< 7bit CE8Bit, ///< 8bit CEquPr, ///< quoted-printable CEbase64, ///< base64 CEuuenc, ///< uuencode CEbinary ///< binary }; /** Various possible values for the "Content-Disposition" header. */ enum contentDisposition { CDInvalid, ///< Default, invalid value CDinline, ///< inline CDattachment, ///< attachment CDparallel ///< parallel (invalid, do not use) }; //@cond PRIVATE // internal macro to generate default constructors #define kmime_mk_trivial_ctor( subclass ) \ public: \ subclass(); \ ~subclass(); #define kmime_mk_dptr_ctor( subclass ) \ protected: \ explicit subclass( subclass##Private *d ); #define kmime_mk_trivial_ctor_with_name( subclass ) \ kmime_mk_trivial_ctor( subclass ) \ const char *type() const override; \ static const char *staticType(); //@endcond // // // HEADER'S BASE CLASS. DEFINES THE COMMON INTERFACE // // /** Baseclass of all header-classes. It represents a header-field as described in RFC-822. */ class KMIME_EXPORT Base { public: /** A vector of headers. */ typedef QVector List; /** Creates an empty header. */ Base(); /** Destructor. */ virtual ~Base(); /** Parses the given string. Take care of RFC2047-encoded strings. @param s The encoded header data. */ virtual void from7BitString(const QByteArray &s) = 0; /** Returns the encoded header. @param withHeaderType Specifies whether the header-type should be included. */ virtual QByteArray as7BitString(bool withHeaderType = true) const = 0; /** Returns the charset that is used for RFC2047-encoding. */ QByteArray rfc2047Charset() const; /** Sets the charset for RFC2047-encoding. @param cs The new charset used for RFC2047 encoding. */ void setRFC2047Charset(const QByteArray &cs); /** Parses the given string and set the charset. @param s The header data as unicode string. @param b The charset preferred for encoding. */ virtual void fromUnicodeString(const QString &s, const QByteArray &b) = 0; /** Returns the decoded content of the header without the header-type. @note The return value of this method should only be used when showing an address to the user. It is not guaranteed that fromUnicodeString( asUnicodeString(), ... ) will return the original string. */ virtual QString asUnicodeString() const = 0; /** Deletes. */ virtual void clear() = 0; /** Checks if this header contains any data. */ virtual bool isEmpty() const = 0; /** Returns the type of this header (e.g. "From"). */ virtual const char *type() const; /** Checks if this header is of type @p t. */ bool is(const char *t) const; /** Checks if this header is a MIME header. */ bool isMimeHeader() const; protected: /** Helper method, returns the header prefix including ":". */ QByteArray typeIntro() const; //@cond PRIVATE BasePrivate *d_ptr; kmime_mk_dptr_ctor(Base) //@endcond private: Q_DECLARE_PRIVATE(Base) Q_DISABLE_COPY(Base) }; // // // GENERIC BASE CLASSES FOR DIFFERENT TYPES OF FIELDS // // namespace Generics { class UnstructuredPrivate; /** Abstract base class for unstructured header fields (e.g. "Subject", "Comment", "Content-description"). Features: Decodes the header according to RFC2047, incl. RFC2231 extensions to encoded-words. Subclasses need only re-implement @p const @p char* @p type(). */ // known issues: // - uses old decodeRFC2047String function, instead of our own... class KMIME_EXPORT Unstructured : public Base { //@cond PRIVATE kmime_mk_dptr_ctor(Unstructured) //@endcond public: Unstructured(); ~Unstructured(); void from7BitString(const QByteArray &s) override; QByteArray as7BitString(bool withHeaderType = true) const override; void fromUnicodeString(const QString &s, const QByteArray &b) override; QString asUnicodeString() const override; void clear() override; bool isEmpty() const override; private: Q_DECLARE_PRIVATE(Unstructured) }; class StructuredPrivate; /** @brief Base class for structured header fields. This is the base class for all structured header fields. It contains parsing methods for all basic token types found in rfc2822. @section Parsing At the basic level, there are tokens & tspecials (rfc2045), atoms & specials, quoted-strings, domain-literals (all rfc822) and encoded-words (rfc2047). As a special token, we have the comment. It is one of the basic tokens defined in rfc822, but it's parsing relies in part on the basic token parsers (e.g. comments may contain encoded-words). Also, most upper-level parsers (notably those for phrase and dot-atom) choose to ignore any comment when parsing. Then there are the real composite tokens, which are made up of one or more of the basic tokens (and semantically invisible comments): phrases (rfc822 with rfc2047) and dot-atoms (rfc2822). This finishes the list of supported token types. Subclasses will provide support for more higher-level tokens, where necessary, using these parsers. @author Marc Mutz */ class KMIME_EXPORT Structured : public Base { public: Structured(); ~Structured(); void from7BitString(const QByteArray &s) override; QString asUnicodeString() const override; void fromUnicodeString(const QString &s, const QByteArray &b) override; protected: /** This method parses the raw header and needs to be implemented in every sub-class. @param scursor Pointer to the start of the data still to parse. @param send Pointer to the end of the data. @param isCRLF true if input string is terminated with a CRLF. */ virtual bool parse(const char *&scursor, const char *const send, bool isCRLF = false) = 0; //@cond PRIVATE kmime_mk_dptr_ctor(Structured) //@endcond private: Q_DECLARE_PRIVATE(Structured) }; class AddressPrivate; /** Base class for all address related headers. */ class KMIME_EXPORT Address : public Structured { public: Address(); ~Address(); protected: //@cond PRIVATE kmime_mk_dptr_ctor(Address) //@endcond private: Q_DECLARE_PRIVATE(Address) }; class MailboxListPrivate; /** Base class for headers that deal with (possibly multiple) addresses, but don't allow groups. @see RFC 2822, section 3.4 */ class KMIME_EXPORT MailboxList : public Address { //@cond PRIVATE kmime_mk_trivial_ctor(MailboxList) kmime_mk_dptr_ctor(MailboxList) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void fromUnicodeString(const QString &s, const QByteArray &b) override; QString asUnicodeString() const override; void clear() override; bool isEmpty() const override; /** Adds an address to this header. @param mbox A Mailbox object specifying the address. */ void addAddress(const Types::Mailbox &mbox); /** Adds an address to this header. @param address The actual email address, with or without angle brackets. @param displayName An optional name associated with the address. */ void addAddress(const QByteArray &address, const QString &displayName = QString()); /** Returns a list of all addresses in this header, regardless of groups. */ QVector addresses() const; /** Returns a list of all display names associated with the addresses in this header. The address is added for addresses that do not have a display name. */ QStringList displayNames() const; /** Returns a single string for user-facing display of this mailbox list. This is equivalent to displayNames().join(", "). @since 5.14 */ QString displayString() const; /** Returns a list of mailboxes listed in this header. */ Types::Mailbox::List mailboxes() const; protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(MailboxList) }; class SingleMailboxPrivate; /** Base class for headers that deal with exactly one mailbox (e.g. Sender). */ class KMIME_EXPORT SingleMailbox : public MailboxList { //@cond PRIVATE kmime_mk_trivial_ctor(SingleMailbox) //@endcond protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(SingleMailbox) }; class AddressListPrivate; /** Base class for headers that deal with (possibly multiple) addresses, allowing groups. Note: Groups are parsed but not represented in the API yet. All addresses in groups are listed as if they would not be part of a group. @todo Add API for groups? @see RFC 2822, section 3.4 */ class KMIME_EXPORT AddressList : public Address { //@cond PRIVATE kmime_mk_trivial_ctor(AddressList) kmime_mk_dptr_ctor(AddressList) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void fromUnicodeString(const QString &s, const QByteArray &b) override; QString asUnicodeString() const override; void clear() override; bool isEmpty() const override; /** Adds an address to this header. @param mbox A Mailbox object specifying the address. */ void addAddress(const Types::Mailbox &mbox); /** Adds an address to this header. @param address The actual email address, with or without angle brackets. @param displayName An optional name associated with the address. */ void addAddress(const QByteArray &address, const QString &displayName = QString()); /** Returns a list of all addresses in this header, regardless of groups. */ QVector addresses() const; /** Returns a list of all display names associated with the addresses in this header. The address is added for addresses that don't have a display name. */ QStringList displayNames() const; /** Returns a single string for user-facing display of this address list. This is equivalent to displayNames().join(", "). @since 5.14 */ QString displayString() const; /** Returns a list of mailboxes listed in this header. */ Types::Mailbox::List mailboxes() const; protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(AddressList) }; class IdentPrivate; /** Base class for headers which deal with a list of msg-id's. @see RFC 2822, section 3.6.4 */ class KMIME_EXPORT Ident : public Address { //@cond PRIVATE kmime_mk_trivial_ctor(Ident) kmime_mk_dptr_ctor(Ident) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void clear() override; bool isEmpty() const override; /** Initialize this identifier Copy the data from */ void fromIdent(const Ident* ident); /** Returns the list of identifiers contained in this header. Note: - Identifiers are not enclosed in angle-brackets. - Identifiers are listed in the same order as in the header. */ QVector identifiers() const; /** Appends a new identifier to this header. @param id The identifier to append, with or without angle-brackets. */ void appendIdentifier(const QByteArray &id); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(Ident) }; class SingleIdentPrivate; /** Base class for headers which deal with a single msg-id. @see RFC 2822, section 3.6.4 */ class KMIME_EXPORT SingleIdent : public Ident { //@cond PRIVATE kmime_mk_trivial_ctor(SingleIdent) kmime_mk_dptr_ctor(SingleIdent) //@endcond public: /** Returns the identifier contained in this header. Note: The identifiers is not enclosed in angle-brackets. */ QByteArray identifier() const; /** Sets the identifier. @param id The new identifier with or without angle-brackets. */ void setIdentifier(const QByteArray &id); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(SingleIdent) }; class TokenPrivate; /** Base class for headers which deal with a single atom. */ class KMIME_EXPORT Token : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor(Token) kmime_mk_dptr_ctor(Token) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void clear() override; bool isEmpty() const override; /** Returns the token. */ QByteArray token() const; /** Sets the token to @p t, */ void setToken(const QByteArray &t); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(Token) }; class PhraseListPrivate; /** Base class for headers containing a list of phrases. */ class KMIME_EXPORT PhraseList : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor(PhraseList) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; QString asUnicodeString() const override; void clear() override; bool isEmpty() const override; /** Returns the list of phrases contained in this header. */ QStringList phrases() const; protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(PhraseList) }; class DotAtomPrivate; /** Base class for headers containing a dot atom. */ class KMIME_EXPORT DotAtom : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor(DotAtom) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; QString asUnicodeString() const override; void clear() override; bool isEmpty() const override; protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(DotAtom) }; class ParametrizedPrivate; /** Base class for headers containing a parameter list such as "Content-Type". */ class KMIME_EXPORT Parametrized : public Structured { //@cond PRIVATE kmime_mk_trivial_ctor(Parametrized) kmime_mk_dptr_ctor(Parametrized) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; bool isEmpty() const override; void clear() override; //FIXME: Shouldn't the parameter keys be QByteArray and not QStrings? Only the values can be // non-ascii! /** Returns the value of the specified parameter. @param key The parameter name. */ QString parameter(const QString &key) const; /** @param key the key of the parameter to check for @return true if a parameter with the given @p key exists. @since 4.5 */ bool hasParameter(const QString &key) const; /** Sets the parameter @p key to @p value. @param key The parameter name. @param value The new value for @p key. */ void setParameter(const QString &key, const QString &value); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(Parametrized) }; } // namespace Generics // // // INCOMPATIBLE, GSTRUCTURED-BASED FIELDS: // // class ReturnPathPrivate; /** Represents the Return-Path header field. @see RFC 2822, section 3.6.7 */ class KMIME_EXPORT ReturnPath : public Generics::Address { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(ReturnPath) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void clear() override; bool isEmpty() const override; protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(ReturnPath) }; // Address et al.: // rfc(2)822 headers: /** Represent a "From" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT From : public Generics::MailboxList { kmime_mk_trivial_ctor_with_name(From) }; /** Represents a "Sender" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT Sender : public Generics::SingleMailbox { kmime_mk_trivial_ctor_with_name(Sender) }; /** Represents a "To" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT To : public Generics::AddressList { kmime_mk_trivial_ctor_with_name(To) }; /** Represents a "Cc" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT Cc : public Generics::AddressList { kmime_mk_trivial_ctor_with_name(Cc) }; /** Represents a "Bcc" header. @see RFC 2822, section 3.6.3. */ class KMIME_EXPORT Bcc : public Generics::AddressList { kmime_mk_trivial_ctor_with_name(Bcc) }; /** Represents a "ReplyTo" header. @see RFC 2822, section 3.6.2. */ class KMIME_EXPORT ReplyTo : public Generics::AddressList { kmime_mk_trivial_ctor_with_name(ReplyTo) }; class MailCopiesToPrivate; /** Represents a "Mail-Copies-To" header. @see http://www.newsreaders.com/misc/mail-copies-to.html */ class KMIME_EXPORT MailCopiesTo : public Generics::AddressList { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(MailCopiesTo) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; QString asUnicodeString() const override; void clear() override; bool isEmpty() const override; /** Returns true if a mail copy was explicitly requested. */ bool alwaysCopy() const; /** Sets the header to "poster". */ void setAlwaysCopy(); /** Returns true if a mail copy was explicitly denied. */ bool neverCopy() const; /** Sets the header to "never". */ void setNeverCopy(); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(MailCopiesTo) }; class ContentTransferEncodingPrivate; /** Represents a "Content-Transfer-Encoding" header. @see RFC 2045, section 6. */ class KMIME_EXPORT ContentTransferEncoding : public Generics::Token { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(ContentTransferEncoding) //@endcond public: void clear() override; /** Returns the encoding specified in this header. */ contentEncoding encoding() const; /** Sets the encoding to @p e. */ void setEncoding(contentEncoding e); /** Returns whether the Content containing this header is already decoded. */ bool isDecoded() const; /** Set whether the Content containing this header is already decoded. For instance, if you fill your Content with already-encoded base64 data, you will want to setDecoded( false ). @param decoded if @c true the content is already decoded */ void setDecoded(bool isDecoded = true); /** Returns whether the Content containing this header needs to be encoded (i.e., if decoded() is true and encoding() is base64 or quoted-printable). */ bool needToEncode() const; protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(ContentTransferEncoding) }; /** Represents a "Keywords" header. @see RFC 2822, section 3.6.5. */ class KMIME_EXPORT Keywords : public Generics::PhraseList { kmime_mk_trivial_ctor_with_name(Keywords) }; // DotAtom: /** Represents a "MIME-Version" header. @see RFC 2045, section 4. */ class KMIME_EXPORT MIMEVersion : public Generics::DotAtom { kmime_mk_trivial_ctor_with_name(MIMEVersion) }; // Ident: /** Represents a "Message-ID" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT MessageID : public Generics::SingleIdent { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(MessageID) //@endcond public: /** Generate a message identifer. @param fqdn A fully qualified domain name. */ void generate(const QByteArray &fqdn); }; class ContentIDPrivate; /** Represents a "Content-ID" header. */ class KMIME_EXPORT ContentID : public Generics::SingleIdent { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(ContentID) kmime_mk_dptr_ctor(ContentID) //@endcond protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(ContentID) }; /** Represents a "Supersedes" header. */ class KMIME_EXPORT Supersedes : public Generics::SingleIdent { kmime_mk_trivial_ctor_with_name(Supersedes) }; /** Represents a "In-Reply-To" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT InReplyTo : public Generics::Ident { kmime_mk_trivial_ctor_with_name(InReplyTo) }; /** Represents a "References" header. @see RFC 2822, section 3.6.4. */ class KMIME_EXPORT References : public Generics::Ident { kmime_mk_trivial_ctor_with_name(References) }; class ContentTypePrivate; /** Represents a "Content-Type" header. @see RFC 2045, section 5. */ class KMIME_EXPORT ContentType : public Generics::Parametrized { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(ContentType) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void clear() override; bool isEmpty() const override; /** Returns the mimetype. */ QByteArray mimeType() const; /** Returns the media type (first part of the mimetype). */ QByteArray mediaType() const; /** Returns the mime sub-type (second part of the mimetype). */ QByteArray subType() const; /** Sets the mimetype and clears already existing parameters. @param mimeType The new mimetype. */ void setMimeType(const QByteArray &mimeType); /** Tests if the media type equals @p mediatype. */ bool isMediatype(const char *mediatype) const; /** Tests if the mime sub-type equals @p subtype. */ bool isSubtype(const char *subtype) const; /** Tests if the mime type is @p mimeType. */ bool isMimeType(const char *mimeType) const; /** Returns true if the associated MIME entity is a text. */ bool isText() const; /** Returns true if the associated MIME entity is a plain text. */ bool isPlainText() const; /** Returns true if the associated MIME entity is a HTML file. */ bool isHTMLText() const; /** Returns true if the associated MIME entity is an image. */ bool isImage() const; /** Returns true if the associated MIME entity is a mulitpart container. */ bool isMultipart() const; /** Returns true if the associated MIME entity contains partial data. @see partialNumber(), partialCount() */ bool isPartial() const; /** Returns the charset for the associated MIME entity. */ QByteArray charset() const; /** Sets the charset. */ void setCharset(const QByteArray &s); /** Returns the boundary (for mulitpart containers). */ QByteArray boundary() const; /** Sets the mulitpart container boundary. */ void setBoundary(const QByteArray &s); /** Returns the name of the associated MIME entity. */ QString name() const; /** Sets the name to @p s using charset @p cs. */ void setName(const QString &s, const QByteArray &cs); /** Returns the identifier of the associated MIME entity. */ QByteArray id() const; /** Sets the identifier. */ void setId(const QByteArray &s); /** Returns the position of this part in a multi-part set. @see isPartial(), partialCount() */ int partialNumber() const; /** Returns the total number of parts in a multi-part set. @see isPartial(), partialNumber() */ int partialCount() const; /** Sets parameters of a partial MIME entity. @param total The total number of entities in the multi-part set. @param number The number of this entity in a multi-part set. */ void setPartialParams(int total, int number); // TODO: document contentCategory category() const; void setCategory(contentCategory c); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(ContentType) }; class ContentDispositionPrivate; /** Represents a "Content-Disposition" header. @see RFC 2183 */ class KMIME_EXPORT ContentDisposition : public Generics::Parametrized { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(ContentDisposition) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; bool isEmpty() const override; void clear() override; /** Returns the content disposition. */ contentDisposition disposition() const; /** Sets the content disposition. @param disp The new content disposition. */ void setDisposition(contentDisposition disp); /** Returns the suggested filename for the associated MIME part. This is just a convenience function, it is equivalent to calling parameter( "filename" ); */ QString filename() const; /** Sets the suggested filename for the associated MIME part. This is just a convenience function, it is equivalent to calling setParameter( "filename", filename ); @param filename The filename. */ void setFilename(const QString &filename); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(ContentDisposition) }; // // // COMPATIBLE GUNSTRUCTURED-BASED FIELDS: // // class GenericPrivate; /** Represents an arbitrary header, that can contain any header-field. Adds a type over Unstructured. @see Unstructured */ class KMIME_EXPORT Generic : public Generics::Unstructured { public: Generic(); - Generic(const char *t); + Generic(const char *t, int len = -1); ~Generic(); void clear() override; bool isEmpty() const override; const char *type() const override; - void setType(const char *type); + void setType(const char *type, int len = -1); private: Q_DECLARE_PRIVATE(Generic) }; /** Represents a "Subject" header. @see RFC 2822, section 3.6.5. */ class KMIME_EXPORT Subject : public Generics::Unstructured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(Subject) //@endcond }; /** Represents a "Organization" header. */ class KMIME_EXPORT Organization : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name(Organization) }; /** Represents a "Content-Description" header. */ class KMIME_EXPORT ContentDescription : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name(ContentDescription) }; /** Represents a "Content-Location" header. @since 4.2 */ class KMIME_EXPORT ContentLocation : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name(ContentLocation) }; class ControlPrivate; /** Represents a "Control" header. @see RFC 1036, section 3. */ class KMIME_EXPORT Control : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(Control) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void clear() override; bool isEmpty() const override; /** Returns the control message type. */ QByteArray controlType() const; /** Returns the control message parameter. */ QByteArray parameter() const; /** Returns true if this is a cancel control message. @see RFC 1036, section 3.1. */ bool isCancel() const; /** Changes this header into a cancel control message for the given message-id. @param msgid The message-id of the article that should be canceled. */ void setCancel(const QByteArray &msgid); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(Control) }; class DatePrivate; /** Represents a "Date" header. @see RFC 2822, section 3.3. */ class KMIME_EXPORT Date : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(Date) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void clear() override; bool isEmpty() const override; /** Returns the date contained in this header. */ QDateTime dateTime() const; /** Sets the date. */ void setDateTime(const QDateTime &dt); /** Returns the age of the message. */ int ageInDays() const; protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(Date) }; class NewsgroupsPrivate; /** Represents a "Newsgroups" header. @see RFC 1036, section 2.1.3. */ class KMIME_EXPORT Newsgroups : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(Newsgroups) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; void fromUnicodeString(const QString &s, const QByteArray &b) override; QString asUnicodeString() const override; void clear() override; bool isEmpty() const override; /** Returns the list of newsgroups. */ QVector groups() const; /** Sets the newsgroup list. */ void setGroups(const QVector &groups); /** Returns true if this message has been cross-posted, i.e. if it has been posted to multiple groups. */ bool isCrossposted() const; protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(Newsgroups) }; /** Represents a "Followup-To" header. @see RFC 1036, section 2.2.3. */ class KMIME_EXPORT FollowUpTo : public Newsgroups { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(FollowUpTo) //@endcond }; class LinesPrivate; /** Represents a "Lines" header. @see RFC 1036, section 2.2.12. */ class KMIME_EXPORT Lines : public Generics::Structured { //@cond PRIVATE kmime_mk_trivial_ctor_with_name(Lines) //@endcond public: QByteArray as7BitString(bool withHeaderType = true) const override; QString asUnicodeString() const override; void clear() override; bool isEmpty() const override; /** Returns the number of lines, undefined if isEmpty() returns true. */ int numberOfLines() const; /** Sets the number of lines to @p lines. */ void setNumberOfLines(int lines); protected: bool parse(const char *&scursor, const char *const send, bool isCRLF = false) override; private: Q_DECLARE_PRIVATE(Lines) }; /** Represents a "User-Agent" header. */ class KMIME_EXPORT UserAgent : public Generics::Unstructured { kmime_mk_trivial_ctor_with_name(UserAgent) }; /** Creates a header based on @param type. If @param type is a known header type, * the right object type will be created, otherwise a null pointer is returned. */ KMIME_EXPORT Base *createHeader(const QByteArray &type); } //namespace Headers } //namespace KMime // undefine code generation macros again #undef kmime_mk_trivial_ctor #undef kmime_mk_dptr_ctor #undef kmime_mk_trivial_ctor_with_name Q_DECLARE_METATYPE(KMime::Headers::To*) Q_DECLARE_METATYPE(KMime::Headers::Cc*) Q_DECLARE_METATYPE(KMime::Headers::Bcc*) #endif // __KMIME_HEADERS_H__