Commit 7c7c918b authored by Christian Schoenebeck's avatar Christian Schoenebeck Committed by Sandro Knauß
Browse files

Add KMime::foldHeader()

Breaks a header over multiple lines if needed by inserting "folding
white space" (FWS) at appropriate positions according to RFC 5322
section 2.1.1. "Line Length Limits".

Complexity of algorithm: O(n)
parent 6874e727
......@@ -223,6 +223,93 @@ QByteArray unfoldHeader(const QByteArray &header)
return unfoldHeader(header.constData(), header.size());
}
// state machine used by foldHeader()
struct HeaderContext {
unsigned int isEscapePair : 1;
unsigned int isQuotedStr : 1;
HeaderContext() {
isEscapePair = isQuotedStr = 0;
}
void push(char c) {
if (c == '\"' && !isEscapePair) {
++isQuotedStr;
} else if (c == '\\' || isEscapePair) {
++isEscapePair;
}
}
};
QByteArray foldHeader(const QByteArray &header)
{
// RFC 5322 section 2.1.1. "Line Length Limits" says:
//
// "Each line of characters MUST be no more than 998 characters, and
// SHOULD be no more than 78 characters, excluding the CRLF."
const int maxLen = 78;
if (header.length() <= maxLen) {
return header;
}
// fast forward to header body
int pos = header.indexOf(':');
if (pos < 0 || ++pos >= header.length()) {
return header;
}
// prepare for mutating header
QByteArray hdr = header;
// There are positions that are eligible for inserting FWS but discouraged
// (e.g. existing white space within a quoted string), and there are
// positions which are recommended for inserting FWS (e.g. after comma
// separator of an address list).
int eligible = pos;
int recommended = pos;
// reflects start position of "current line" in byte array
int start = 0;
HeaderContext ctx;
for (; true; ++pos) {
if (pos - start > maxLen && eligible) {
// Fold line preferably at recommended position, at eligible position
// otherwise.
const int fws = recommended ?: eligible;
hdr.insert(fws, '\n');
// We started a new line, so reset.
if (eligible <= fws) {
eligible = 0;
} else {
++eligible; // LF
}
recommended = 0;
start = fws + 1/* LF */;
continue;
}
if (pos >= hdr.length()) {
break;
}
// Any white space character position is eligible for folding, except of
// escape pair (i.e. BSP WSP must not be folded).
if (hdr[pos] == ' ' && !ctx.isEscapePair && hdr[pos - 1] != '\n') {
eligible = pos;
if ((hdr[pos - 1] == ',' || hdr[pos - 1] == ';') && !ctx.isQuotedStr) {
recommended = pos;
}
}
ctx.push(hdr[pos]);
}
return hdr;
}
int findHeaderLineEnd(const QByteArray &src, int &dataBegin, bool *folded)
{
int end = dataBegin;
......
......@@ -79,6 +79,12 @@ KMIME_EXPORT extern QByteArray multiPartBoundary();
KMIME_EXPORT extern QByteArray unfoldHeader(const QByteArray &header);
KMIME_EXPORT extern QByteArray unfoldHeader(const char *header, size_t headerSize);
/**
Folds the given header if necessary.
@param header The header to fold.
*/
KMIME_EXPORT extern QByteArray foldHeader(const QByteArray &header);
/**
Tries to extract the header with name @p name from the string
@p src, unfolding it if necessary.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment