Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
c4df2da
RFC 5322 Folding: make wordEncoder::getNextChunk honour maxLength exa…
grammmike Sep 10, 2026
c918016
RFC 5322 Folding: keep encoded-words within 75 chars (#365)
grammmike Sep 10, 2026
6974697
RFC 5322 Folding: split encoded-words at 75 chars also without a line…
grammmike Sep 11, 2026
a144a00
RFC 5322 Folding: forget a previous encoded-word after unencoded outp…
grammmike Sep 11, 2026
1af643d
RFC 5322 Folding: no encoded leading space after a fold (#365)
grammmike Sep 14, 2026
15ddfa2
RFC 5322 Folding: fix the separator before a plain word after an enco…
grammmike Sep 14, 2026
94c18a6
RFC 5322 Folding: escape '"' and '\' when quoting a display name (#365)
grammmike Sep 14, 2026
acbf5ca
RFC 5322 Folding: keep display names quoted when they need folding (#…
grammmike Sep 15, 2026
61f6309
RFC 5322 Folding: avoid folding after a backslash or inside a white-s…
grammmike Sep 15, 2026
433458d
RFC 5322 Folding: fix the folds before a mailbox address (#365)
grammmike Sep 16, 2026
b6d7e83
RFC 5322 Folding: move whole addresses to the next line when folding …
grammmike Sep 16, 2026
148ddb5
RFC 5322 Folding: never encode or fold a printable ASCII local part (…
grammmike Sep 17, 2026
2513b24
RFC 5322 Folding: fold message-id lists in place of the separator (#365)
grammmike Sep 17, 2026
0272426
Parser: ignore '(' and '<' inside quoted strings when splitting lists…
grammmike Sep 17, 2026
fd99ad7
RFC 5322 Folding: no space in addition to a leading tab of an encoded…
grammmike Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions src/vmime/address.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,27 +112,39 @@ shared_ptr <address> address::parseNext(

case '"':

quoted = !quoted;
if (commentLevel == 0) {
quoted = !quoted;
}

break;

case '<':

inRouteAddr = true;
if (!quoted && commentLevel == 0) {
inRouteAddr = true;
}

break;

case '>':

inRouteAddr = false;
if (!quoted && commentLevel == 0) {
inRouteAddr = false;
}

break;

case '(':

++commentLevel;
if (!quoted) {
++commentLevel;
}

break;

case ')':

if (commentLevel > 0) {
if (!quoted && commentLevel > 0) {
--commentLevel;
}

Expand Down
43 changes: 33 additions & 10 deletions src/vmime/addressList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "vmime/exception.hpp"
#include "vmime/mailboxList.hpp"
#include "vmime/mailboxGroup.hpp"
#include "vmime/utility/outputStreamStringAdapter.hpp"


namespace vmime {
Expand Down Expand Up @@ -87,21 +88,43 @@ void addressList::generateImpl(

size_t pos = curLinePos;

generationContext tmpCtx(ctx);
tmpCtx.setMaxLineLength(tmpCtx.getMaxLineLength() - 2);
// Leave room for the comma after all addresses but the last one
generationContext commaCtx(ctx);

if (!m_list.empty()) {
if (ctx.getMaxLineLength() != lineLengthLimits::infinite &&
ctx.getMaxLineLength() > NEW_LINE_SEQUENCE_LENGTH + 1) {

for (std::vector <shared_ptr <address> >::const_iterator i = m_list.begin() ; ; ) {
commaCtx.setMaxLineLength(ctx.getMaxLineLength() - 1);
}

(*i)->generate(ctx, os, pos, &pos);
for (std::vector <shared_ptr <address> >::const_iterator i = m_list.begin() ;
i != m_list.end() ; ++i) {

if (++i == m_list.end()) {
break;
}
const generationContext& addrCtx = (i + 1 == m_list.end()) ? ctx : commaCtx;

if (i == m_list.begin()) {
(*i)->generate(addrCtx, os, pos, &pos);
continue;
}

string tmp;
utility::outputStreamStringAdapter tmpStream(tmp);
size_t tmpPos = 0;

(*i)->generate(addrCtx, tmpStream, pos + 2, &tmpPos);

// Unless the address fits entirely after ", ", it starts a new line,
// with the fold after the comma
if (pos > NEW_LINE_SEQUENCE_LENGTH &&
(tmp.find(CRLF) != string::npos || tmpPos > addrCtx.getMaxLineLength())) {

os << "," << NEW_LINE_SEQUENCE;
(*i)->generate(addrCtx, os, NEW_LINE_SEQUENCE_LENGTH, &pos);

} else {

os << ", ";
pos += 2;
os << ", " << tmp;
pos = tmpPos;
}
}

Expand Down
34 changes: 32 additions & 2 deletions src/vmime/emailAddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
//

#include "vmime/emailAddress.hpp"
#include "vmime/wordEncoder.hpp"

#include "vmime/platform.hpp"

Expand Down Expand Up @@ -587,9 +588,38 @@ void emailAddress::generateImpl(

} else {

// Local part
// Local part. It may not be folded, nor contain an encoded-word
// (RFC 2047 §5), unless it cannot be written otherwise.
vmime::utility::outputStreamStringAdapter os(localPart);
m_localName.generate(ctx, os, 0, NULL, text::QUOTE_IF_NEEDED, NULL);
const string& buffer = m_localName.getBuffer();

// Not is7bit(), which is also false for "=?" and a trailing '='.
// ESC (ISO-2022-JP), CR and LF are not printable.
bool printable = true;

for (size_t i = 0 ; printable && i < buffer.length() ; ++i) {
printable = parserHelpers::isPrint(buffer[i]);
}

if (printable) {

generationContext localCtx(ctx);
localCtx.setMaxLineLength(lineLengthLimits::infinite);

m_localName.generate(localCtx, os, 0, NULL,
text::FORCE_NO_ENCODING | text::QUOTE_IF_NEEDED, NULL);

} else {

wordEncoder enc(buffer, m_localName.getCharset());
const string lang = m_localName.getLanguage();

os << "=?" << m_localName.getCharset().getName()
<< (lang.empty() ? "" : "*" + lang)
<< (enc.getEncoding() == wordEncoder::ENCODING_B64 ? "?B?" : "?Q?")
<< enc.getNextChunk(lineLengthLimits::infinite)
<< "?=";
}

// Domain part as IDNA
domainPart = domainNameToIDNA(m_domainName.getConvertedText(vmime::charsets::UTF_8));
Expand Down
21 changes: 15 additions & 6 deletions src/vmime/mailbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,14 @@ void mailbox::generateImpl(

size_t pos = curLinePos;

// No display name is specified, only email address.
if (curLinePos + generatedEmail.length() > ctx.getMaxLineLength()) {
// No display name is specified, only email address. Fold before
// it only if it then fits, as it cannot be folded itself.
if (curLinePos + generatedEmail.length() > ctx.getMaxLineLength() &&
curLinePos > NEW_LINE_SEQUENCE_LENGTH &&
NEW_LINE_SEQUENCE_LENGTH + generatedEmail.length() <= ctx.getMaxLineLength()) {

os << NEW_LINE_SEQUENCE;
pos = NEW_LINE_SEQUENCE.length();
pos = NEW_LINE_SEQUENCE_LENGTH;
}

os << generatedEmail;
Expand Down Expand Up @@ -378,14 +381,20 @@ void mailbox::generateImpl(
text::QUOTE_IF_POSSIBLE | (forceEncode ? text::FORCE_ENCODING : 0)
);

// The fold, or a space, separates the name from the address
if (pos + generatedEmail.length() + 3 > ctx.getMaxLineLength()) {

os << NEW_LINE_SEQUENCE;
pos = NEW_LINE_SEQUENCE.length();
pos = NEW_LINE_SEQUENCE_LENGTH;

} else {

os << " ";
++pos;
}

os << " <" << generatedEmail << ">";
pos += 2 + generatedEmail.length() + 1;
os << "<" << generatedEmail << ">";
pos += 1 + generatedEmail.length() + 1;

if (newLinePos) {
*newLinePos = pos;
Expand Down
37 changes: 28 additions & 9 deletions src/vmime/mailboxGroup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "vmime/mailboxGroup.hpp"
#include "vmime/parserHelpers.hpp"
#include "vmime/exception.hpp"
#include "vmime/utility/outputStreamStringAdapter.hpp"


namespace vmime {
Expand Down Expand Up @@ -161,9 +162,6 @@ void mailboxGroup::generateImpl(

size_t pos = curLinePos;

generationContext tmpCtx(ctx);
tmpCtx.setMaxLineLength(ctx.getMaxLineLength() - 2);

m_name.encodeAndFold(
ctx, os, pos, &pos,
forceEncode ? text::FORCE_ENCODING : 0
Expand All @@ -172,21 +170,42 @@ void mailboxGroup::generateImpl(
os << ":";
++pos;

// Leave room for the ',' or ';' after each mailbox
generationContext mboxCtx(ctx);

if (ctx.getMaxLineLength() != lineLengthLimits::infinite &&
ctx.getMaxLineLength() > NEW_LINE_SEQUENCE_LENGTH + 1) {

mboxCtx.setMaxLineLength(ctx.getMaxLineLength() - 1);
}

for (std::vector <shared_ptr <mailbox> >::const_iterator it = m_list.begin() ;
it != m_list.end() ; ++it) {

if (it != m_list.begin()) {
os << ",";
++pos;
}

string tmp;
utility::outputStreamStringAdapter tmpStream(tmp);
size_t tmpPos = 0;

(*it)->generate(mboxCtx, tmpStream, pos + 1, &tmpPos);

os << ", ";
pos += 2;
// Unless the mailbox fits entirely after a space, it starts a new
// line, with the fold in place of the space
if (pos > NEW_LINE_SEQUENCE_LENGTH &&
(tmp.find(CRLF) != string::npos || tmpPos > mboxCtx.getMaxLineLength())) {

os << NEW_LINE_SEQUENCE;
(*it)->generate(mboxCtx, os, NEW_LINE_SEQUENCE_LENGTH, &pos);

} else {

os << " ";
++pos;
os << " " << tmp;
pos = tmpPos;
}

(*it)->generate(tmpCtx, os, pos, &pos);
}

os << ";";
Expand Down
31 changes: 23 additions & 8 deletions src/vmime/messageIdSequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include "vmime/messageIdSequence.hpp"
#include "vmime/exception.hpp"
#include "vmime/utility/outputStreamStringAdapter.hpp"

#include <algorithm>

Expand Down Expand Up @@ -123,19 +124,33 @@ void messageIdSequence::generateImpl(

if (!m_list.empty()) {

generationContext tmpCtx(ctx);
tmpCtx.setMaxLineLength(ctx.getMaxLineLength() - 2);
m_list.front()->generate(ctx, os, pos, &pos);

for (std::vector <shared_ptr <messageId> >::const_iterator it = m_list.begin() ; ; ) {
// The following ids are folded here, in place of the separator
generationContext idCtx(ctx);
idCtx.setWrapMessageId(false);

(*it)->generate(ctx, os, pos, &pos);
for (std::vector <shared_ptr <messageId> >::const_iterator it = m_list.begin() + 1 ;
it != m_list.end() ; ++it) {

if (++it == m_list.end()) {
break;
string id;
utility::outputStreamStringAdapter idStream(id);

(*it)->generate(idCtx, idStream, 0, NULL);

if (ctx.getWrapMessageId() && pos + 1 + id.length() > ctx.getMaxLineLength()) {

os << NEW_LINE_SEQUENCE;
pos = NEW_LINE_SEQUENCE_LENGTH;

} else {

os << " ";
++pos;
}

os << " ";
pos++;
os << id;
pos += id.length();
}
}

Expand Down
Loading
Loading