Merge pull request #5001 from k9mail/no_encoding_in_MimeHeader

No encoding in MimeHeader
This commit is contained in:
cketti 2020-10-13 03:54:13 +02:00 committed by GitHub
commit 1835b502b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 432 additions and 34 deletions

View file

@ -20,8 +20,8 @@ class AutocryptTransferMessageCreator(private val stringProvider: AutocryptStrin
val subjectText = stringProvider.transferMessageSubject() val subjectText = stringProvider.transferMessageSubject()
val messageText = stringProvider.transferMessageBody() val messageText = stringProvider.transferMessageBody()
val textBodyPart = MimeBodyPart(TextBody(messageText)) val textBodyPart = MimeBodyPart.create(TextBody(messageText))
val dataBodyPart = MimeBodyPart(BinaryMemoryBody(data, "7bit")) val dataBodyPart = MimeBodyPart.create(BinaryMemoryBody(data, "7bit"))
dataBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "application/autocrypt-setup") dataBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "application/autocrypt-setup")
dataBodyPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, "attachment; filename=\"autocrypt-setup-message\"") dataBodyPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, "attachment; filename=\"autocrypt-setup-message\"")
@ -29,7 +29,7 @@ class AutocryptTransferMessageCreator(private val stringProvider: AutocryptStrin
messageBody.addBodyPart(textBodyPart) messageBody.addBodyPart(textBodyPart)
messageBody.addBodyPart(dataBodyPart) messageBody.addBodyPart(dataBodyPart)
val message = MimeMessage() val message = MimeMessage.create()
MimeMessageHelper.setBody(message, messageBody) MimeMessageHelper.setBody(message, messageBody)
val nowDate = Date() val nowDate = Date()

View file

@ -26,6 +26,7 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MessageIdGenerator; import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeBodyPart; import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeHeaderEncoder;
import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMessageHelper; import com.fsck.k9.mail.internet.MimeMessageHelper;
import com.fsck.k9.mail.internet.MimeMultipart; import com.fsck.k9.mail.internet.MimeMultipart;
@ -83,7 +84,7 @@ public abstract class MessageBuilder {
protected MimeMessage build() throws MessagingException { protected MimeMessage build() throws MessagingException {
//FIXME: check arguments //FIXME: check arguments
MimeMessage message = new MimeMessage(); MimeMessage message = MimeMessage.create();
buildHeader(message); buildHeader(message);
buildBody(message); buildBody(message);
@ -108,7 +109,8 @@ public abstract class MessageBuilder {
} }
if (!K9.isHideUserAgent()) { if (!K9.isHideUserAgent()) {
message.setHeader("User-Agent", resourceProvider.userAgent()); String encodedUserAgent = MimeHeaderEncoder.encode("User-Agent", resourceProvider.userAgent());
message.setHeader("User-Agent", encodedUserAgent);
} }
final String replyTo = identity.getReplyTo(); final String replyTo = identity.getReplyTo();
@ -166,8 +168,8 @@ public abstract class MessageBuilder {
composedMimeMessage.setSubType("alternative"); composedMimeMessage.setSubType("alternative");
// Let the receiver select either the text or the HTML part. // Let the receiver select either the text or the HTML part.
bodyPlain = buildText(isDraft, SimpleMessageFormat.TEXT); bodyPlain = buildText(isDraft, SimpleMessageFormat.TEXT);
composedMimeMessage.addBodyPart(new MimeBodyPart(bodyPlain, "text/plain")); composedMimeMessage.addBodyPart(MimeBodyPart.create(bodyPlain, "text/plain"));
composedMimeMessage.addBodyPart(new MimeBodyPart(body, "text/html")); composedMimeMessage.addBodyPart(MimeBodyPart.create(body, "text/html"));
if (hasAttachments) { if (hasAttachments) {
// If we're HTML and have attachments, we have a MimeMultipart container to hold the // If we're HTML and have attachments, we have a MimeMultipart container to hold the
@ -175,7 +177,7 @@ public abstract class MessageBuilder {
// (composedMimeMessage) with the user's composed messages, and subsequent parts for // (composedMimeMessage) with the user's composed messages, and subsequent parts for
// the attachments. // the attachments.
MimeMultipart mp = createMimeMultipart(); MimeMultipart mp = createMimeMultipart();
mp.addBodyPart(new MimeBodyPart(composedMimeMessage)); mp.addBodyPart(MimeBodyPart.create(composedMimeMessage));
addAttachmentsToMessage(mp); addAttachmentsToMessage(mp);
MimeMessageHelper.setBody(message, mp); MimeMessageHelper.setBody(message, mp);
} else { } else {
@ -186,7 +188,7 @@ public abstract class MessageBuilder {
// Text-only message. // Text-only message.
if (hasAttachments) { if (hasAttachments) {
MimeMultipart mp = createMimeMultipart(); MimeMultipart mp = createMimeMultipart();
mp.addBodyPart(new MimeBodyPart(body, "text/plain")); mp.addBodyPart(MimeBodyPart.create(body, "text/plain"));
addAttachmentsToMessage(mp); addAttachmentsToMessage(mp);
MimeMessageHelper.setBody(message, mp); MimeMessageHelper.setBody(message, mp);
} else { } else {
@ -231,7 +233,7 @@ public abstract class MessageBuilder {
} }
Body body = new TempFileBody(attachment.getFileName()); Body body = new TempFileBody(attachment.getFileName());
MimeBodyPart bp = new MimeBodyPart(body); MimeBodyPart bp = MimeBodyPart.create(body);
addContentType(bp, attachment.getContentType(), attachment.getName()); addContentType(bp, attachment.getContentType(), attachment.getName());
addContentDisposition(bp, attachment.getName(), attachment.getSize()); addContentDisposition(bp, attachment.getName(), attachment.getSize());

View file

@ -27,6 +27,7 @@ import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream; import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.internet.BinaryTempFileBody; import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.Headers;
import com.fsck.k9.mail.internet.MessageIdGenerator; import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeBodyPart; import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeHeader;
@ -219,7 +220,7 @@ public class PgpMessageBuilder extends MessageBuilder {
messageContentBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE, messageContentBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
messageContentBodyPart.getContentType() + "; protected-headers=\"v1\""); messageContentBodyPart.getContentType() + "; protected-headers=\"v1\"");
messageContentBodyPart.setHeader(MimeHeader.SUBJECT, subjects[0]); messageContentBodyPart.setHeader(MimeHeader.SUBJECT, subjects[0]);
currentProcessedMimeMessage.setHeader(MimeHeader.SUBJECT, resourceProvider.encryptedSubject()); currentProcessedMimeMessage.setSubject(resourceProvider.encryptedSubject());
} }
} }
@ -394,7 +395,7 @@ public class PgpMessageBuilder extends MessageBuilder {
multipartSigned.setSubType("signed"); multipartSigned.setSubType("signed");
multipartSigned.addBodyPart(signedBodyPart); multipartSigned.addBodyPart(signedBodyPart);
multipartSigned.addBodyPart( multipartSigned.addBodyPart(
new MimeBodyPart(new BinaryMemoryBody(signedData, MimeUtil.ENC_7BIT), MimeBodyPart.create(new BinaryMemoryBody(signedData, MimeUtil.ENC_7BIT),
"application/pgp-signature; name=\"signature.asc\"")); "application/pgp-signature; name=\"signature.asc\""));
MimeMessageHelper.setBody(currentProcessedMimeMessage, multipartSigned); MimeMessageHelper.setBody(currentProcessedMimeMessage, multipartSigned);
@ -413,8 +414,8 @@ public class PgpMessageBuilder extends MessageBuilder {
private void mimeBuildEncryptedMessage(@NonNull Body encryptedBodyPart) throws MessagingException { private void mimeBuildEncryptedMessage(@NonNull Body encryptedBodyPart) throws MessagingException {
MimeMultipart multipartEncrypted = createMimeMultipart(); MimeMultipart multipartEncrypted = createMimeMultipart();
multipartEncrypted.setSubType("encrypted"); multipartEncrypted.setSubType("encrypted");
multipartEncrypted.addBodyPart(new MimeBodyPart(new TextBody("Version: 1"), "application/pgp-encrypted")); multipartEncrypted.addBodyPart(MimeBodyPart.create(new TextBody("Version: 1"), "application/pgp-encrypted"));
MimeBodyPart encryptedPart = new MimeBodyPart(encryptedBodyPart, "application/octet-stream; name=\"encrypted.asc\""); MimeBodyPart encryptedPart = MimeBodyPart.create(encryptedBodyPart, "application/octet-stream; name=\"encrypted.asc\"");
encryptedPart.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, "inline; filename=\"encrypted.asc\""); encryptedPart.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, "inline; filename=\"encrypted.asc\"");
multipartEncrypted.addBodyPart(encryptedPart); multipartEncrypted.addBodyPart(encryptedPart);
MimeMessageHelper.setBody(currentProcessedMimeMessage, multipartEncrypted); MimeMessageHelper.setBody(currentProcessedMimeMessage, multipartEncrypted);

View file

@ -27,6 +27,20 @@ public class MimeBodyPart extends BodyPart {
private final MimeHeader mHeader; private final MimeHeader mHeader;
private Body mBody; private Body mBody;
/**
* Creates an instance that will check the header field syntax when adding headers.
*/
public static MimeBodyPart create(Body body) throws MessagingException {
return new MimeBodyPart(body, null, true);
}
/**
* Creates an instance that will check the header field syntax when adding headers.
*/
public static MimeBodyPart create(Body body, String contentType) throws MessagingException {
return new MimeBodyPart(body, contentType, true);
}
public MimeBodyPart() throws MessagingException { public MimeBodyPart() throws MessagingException {
this(null); this(null);
} }
@ -36,7 +50,12 @@ public class MimeBodyPart extends BodyPart {
} }
public MimeBodyPart(Body body, String contentType) throws MessagingException { public MimeBodyPart(Body body, String contentType) throws MessagingException {
this(body, contentType, false);
}
private MimeBodyPart(Body body, String contentType, boolean checkHeaders) throws MessagingException {
mHeader = new MimeHeader(); mHeader = new MimeHeader();
mHeader.setCheckHeaders(checkHeaders);
if (contentType != null) { if (contentType != null) {
addHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType); addHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
} }

View file

@ -17,6 +17,8 @@ class MimeHeader {
val headers: List<Header> val headers: List<Header>
get() = fields.map { Header(it.name, it.value) } get() = fields.map { Header(it.name, it.value) }
var checkHeaders = false
fun clear() { fun clear() {
fields.clear() fields.clear()
} }
@ -26,11 +28,13 @@ class MimeHeader {
} }
fun addHeader(name: String, value: String) { fun addHeader(name: String, value: String) {
val field = NameValueField(name, MimeUtility.foldAndEncode(value)) requireValidHeader(name, value)
val field = NameValueField(name, value)
fields.add(field) fields.add(field)
} }
fun addRawHeader(name: String, raw: String) { fun addRawHeader(name: String, raw: String) {
requireValidRawHeader(name, raw)
val field = RawField(name, raw) val field = RawField(name, raw)
fields.add(field) fields.add(field)
} }
@ -76,21 +80,34 @@ class MimeHeader {
} }
private fun Appendable.appendNameValueField(field: Field) { private fun Appendable.appendNameValueField(field: Field) {
val value = field.value
val encodedValue = if (hasToBeEncoded(value)) {
EncoderUtil.encodeEncodedWord(value)
} else {
value
}
append(field.name) append(field.name)
append(": ") append(": ")
append(encodedValue) append(field.value)
} }
// encode non printable characters except LF/CR/TAB codes. private fun requireValidHeader(name: String, value: String) {
private fun hasToBeEncoded(text: String): Boolean { if (checkHeaders) {
return text.any { !it.isVChar() && !it.isWspOrCrlf() } checkHeader(name, value)
}
}
private fun requireValidRawHeader(name: String, raw: String) {
if (checkHeaders) {
if (!raw.startsWith(name)) throw AssertionError("Raw header value needs to start with header name")
val delimiterIndex = raw.indexOf(':')
val value = if (delimiterIndex == raw.lastIndex) "" else raw.substring(delimiterIndex + 1).trimStart()
checkHeader(name, value)
}
}
private fun checkHeader(name: String, value: String) {
try {
MimeHeaderChecker.checkHeader(name, value)
} catch (e: MimeHeaderParserException) {
// Use AssertionError so we crash the app
throw AssertionError("Invalid header", e)
}
} }
companion object { companion object {

View file

@ -0,0 +1,115 @@
package com.fsck.k9.mail.internet
/**
* Check unstructured header field syntax.
*
* This does not allow the obsolete syntax. Only use this for messages constructed by K-9 Mail, not incoming messages.
*
* See RFC 5322
* ```
* optional-field = field-name ":" unstructured CRLF
* field-name = 1*ftext
* ftext = %d33-57 / %d59-126 ; Printable US-ASCII characters not including ":".
*
* unstructured = (*([FWS] VCHAR) *WSP) / obs-unstruct
* FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
* ```
*/
object MimeHeaderChecker {
fun checkHeader(name: String, value: String) {
if (!name.isValidFieldName()) {
throw MimeHeaderParserException("Header name contains characters not allowed: $name")
}
val initialLineLength = name.length + 2 // name + colon + space
UnstructuredHeaderChecker(value, initialLineLength).checkHeaderValue()
}
private fun String.isValidFieldName() = all { it.isFieldText() }
private fun Char.isFieldText() = isVChar() && this != ':'
}
private class UnstructuredHeaderChecker(val input: String, initialLineLength: Int) {
private val endIndex = input.length
private var currentIndex = 0
private var lineLength = initialLineLength
fun checkHeaderValue() {
while (!endReached()) {
val char = peek()
when {
char == CR -> {
expectCr()
expectLf()
if (lineLength > 1000) {
throw MimeHeaderParserException("Line exceeds 998 characters", currentIndex - 1)
}
lineLength = 0
expectWsp()
skipWsp()
expectVChar()
}
char.isVChar() || char.isWsp() -> {
skipVCharAndWsp()
}
else -> {
throw MimeHeaderParserException("Unexpected character (${char.toInt()})", currentIndex)
}
}
}
if (lineLength > 998) {
throw MimeHeaderParserException("Line exceeds 998 characters", currentIndex)
}
}
private fun expectCr() = expect("CR", CR)
private fun expectLf() = expect("LF", LF)
private fun expectVChar() = expect("VCHAR") { it.isVChar() }
private fun expectWsp() = expect("WSP") { it.isWsp() }
private fun skipWsp() {
while (!endReached() && peek().isWsp()) {
skip()
}
}
private fun skipVCharAndWsp() {
while (!endReached() && peek().let { it.isVChar() || it.isWsp() }) {
skip()
}
}
private fun endReached() = currentIndex >= endIndex
private fun peek(): Char {
if (currentIndex >= input.length) {
throw MimeHeaderParserException("End of input reached unexpectedly", currentIndex)
}
return input[currentIndex]
}
private fun skip() {
currentIndex++
lineLength++
}
private fun expect(displayInError: String, character: Char) {
expect(displayInError) { it == character }
}
private inline fun expect(displayInError: String, predicate: (Char) -> Boolean) {
if (!endReached() && predicate(peek())) {
skip()
} else {
throw MimeHeaderParserException("Expected $displayInError", currentIndex)
}
}
}

View file

@ -0,0 +1,26 @@
package com.fsck.k9.mail.internet
object MimeHeaderEncoder {
@JvmStatic
fun encode(name: String, value: String): String {
// TODO: Fold long text that provides enough opportunities for folding and doesn't contain any characters that
// need to be encoded.
return if (hasToBeEncoded(name, value)) {
EncoderUtil.encodeEncodedWord(value)
} else {
value
}
}
private fun hasToBeEncoded(name: String, value: String): Boolean {
return exceedsRecommendedLineLength(name, value) || charactersNeedEncoding(value)
}
private fun exceedsRecommendedLineLength(name: String, value: String): Boolean {
return name.length + 2 /* colon + space */ + value.length > RECOMMENDED_MAX_LINE_LENGTH
}
private fun charactersNeedEncoding(text: String): Boolean {
return text.any { !it.isVChar() && !it.isWspOrCrlf() }
}
}

View file

@ -183,4 +183,4 @@ class MimeHeaderParser(private val input: String) {
} }
} }
class MimeHeaderParserException(message: String, val errorIndex: Int) : RuntimeException(message) class MimeHeaderParserException(message: String, val errorIndex: Int = -1) : RuntimeException(message)

View file

@ -75,7 +75,19 @@ public class MimeMessage extends Message {
return mimeMessage; return mimeMessage;
} }
/**
* Creates an instance that will check the header field syntax when adding headers.
*/
public static MimeMessage create() {
return new MimeMessage(true);
}
public MimeMessage() { public MimeMessage() {
this(false);
}
private MimeMessage(boolean checkHeaders) {
mHeader.setCheckHeaders(checkHeaders);
} }
/** /**
@ -261,12 +273,13 @@ public class MimeMessage extends Message {
*/ */
@Override @Override
public String getSubject() { public String getSubject() {
return MimeUtility.unfoldAndDecode(getFirstHeader("Subject"), this); return MimeUtility.unfoldAndDecode(getFirstHeader(MimeHeader.SUBJECT), this);
} }
@Override @Override
public void setSubject(String subject) { public void setSubject(String subject) {
setHeader("Subject", subject); String encodedSubject = MimeHeaderEncoder.encode(MimeHeader.SUBJECT, subject);
setHeader(MimeHeader.SUBJECT, encodedSubject);
} }
@Override @Override

View file

@ -914,11 +914,6 @@ public class MimeUtility {
return decode(unfold(s), message); return decode(unfold(s), message);
} }
// TODO implement proper foldAndEncode
public static String foldAndEncode(String s) {
return s;
}
/** /**
* Returns the named parameter of a header field. * Returns the named parameter of a header field.
* *

View file

@ -0,0 +1,210 @@
package com.fsck.k9.mail.internet
import org.junit.Assert.fail
import org.junit.Test
class MimeHeaderCheckerTest {
@Test
fun emptyValue() {
assertValidHeader("Subject: ")
}
@Test
fun blankValue() {
assertValidHeader("Subject: ")
}
@Test
fun textEndingInSpace() {
assertValidHeader("Subject: Text ")
}
@Test
fun textContainingSpaces() {
assertValidHeader("Subject: Text containing spaces")
}
@Test
fun allVisibleCharacters() {
val text = (33..126).map { it.toChar() }.joinToString("")
assertValidHeader("Subject: $text")
}
@Test
fun blankFirstLine() {
assertValidHeader("Subject: \r\n Two")
}
@Test
fun twoLines() {
assertValidHeader("Subject: One\r\n Two")
}
@Test
fun threeLines() {
assertValidHeader("Subject: One\r\n Two\r\n Three")
}
@Test
fun secondLineStartingWithTab() {
assertValidHeader("Subject: One\r\n\tTwo")
}
@Test
fun secondLineStartingWithMultipleWhitespace() {
assertValidHeader("Subject: One\r\n \t Two")
}
@Test
fun singleLineAtMaximumLineLength() {
val longText = "x".repeat(998 /* text limit */ - 4 /* Test */ - 2 /* colon, space */)
assertValidHeader("Test: $longText")
}
@Test
fun firstLineAtMaximumLineLength() {
val longText = "x".repeat(998 /* text limit */ - 4 /* Test */ - 2 /* colon, space */)
assertValidHeader("Test: $longText\r\n Text")
}
@Test
fun middleLineAtMaximumLineLength() {
val longText = "x".repeat(998 - 1 /* space */)
assertValidHeader("Test: One\r\n $longText\r\n Three")
}
@Test
fun lastLineAtMaximumLineLength() {
val longText = "x".repeat(998 - 1 /* space */)
assertValidHeader("Test: One\r\n $longText")
}
@Test
fun colonInHeaderName() {
assertInvalidHeader("Header:Name: Text")
}
@Test
fun nonAsciiCharacterInHeaderName() {
assertInvalidHeader("Sübject: Text")
}
@Test
fun headerNameExceedingLineLimit() {
val longName = "x".repeat(998 - 2 /* space, colon */ + 1)
assertInvalidHeader("$longName: ")
}
@Test
fun nonAsciiCharacter() {
assertInvalidHeader("Subject: ö")
}
@Test
fun nonVisibleCharacter() {
assertInvalidHeader("Subject: \u0007")
}
@Test
fun endingInCR() {
assertInvalidHeader("Subject: Text\r")
}
@Test
fun endingInLF() {
assertInvalidHeader("Subject: Text\n")
}
@Test
fun endingInCRLF() {
assertInvalidHeader("Subject: Text\r\n")
}
@Test
fun lineBreakNotFollowedByWhitespace() {
assertInvalidHeader("Subject: One\r\nTwo")
}
@Test
fun singleCR() {
assertInvalidHeader("Subject: One\rTwo")
}
@Test
fun singleCrFollowedByWhitespace() {
assertInvalidHeader("Subject: One\r Two")
}
@Test
fun consecutiveCRs() {
assertInvalidHeader("Subject: \r\r\n Two")
}
@Test
fun singleLF() {
assertInvalidHeader("Subject: One\nTwo")
}
@Test
fun singleLfFollowedByWhitespace() {
assertInvalidHeader("Subject: One\n Two")
}
@Test
fun consecutiveLFs() {
assertInvalidHeader("Subject: \r\n\n Two")
}
@Test
fun consecutiveLineBreaks() {
assertInvalidHeader("Subject: One\r\n\r\n Two")
}
@Test
fun blankMiddleLine() {
assertInvalidHeader("Subject: One\r\n \r\n Two")
}
@Test
fun endsWithBlankLine() {
assertInvalidHeader("Subject: One\r\n ")
}
@Test
fun singleLineExceedingLineLength() {
val longText = "x".repeat(998 /* text limit */ - 4 /* Test */ - 2 /* colon, space */ + 1)
assertInvalidHeader("Test: $longText")
}
@Test
fun firstLineExceedingLineLength() {
val longText = "x".repeat(998 /* text limit */ - 4 /* Test */ - 2 /* colon, space */ + 1)
assertInvalidHeader("Test: $longText\r\n Text")
}
@Test
fun middleLineExceedingLineLength() {
val longText = "x".repeat(998 - 1 /* space */ + 1)
assertInvalidHeader("Test: One\r\n $longText\r\n Three")
}
@Test
fun lastLineExceedingLineLength() {
val longText = "x".repeat(998 - 1 /* space */ + 1)
assertInvalidHeader("Test: One\r\n $longText")
}
private fun assertValidHeader(header: String) {
val (name, value) = header.split(": ", limit = 2)
MimeHeaderChecker.checkHeader(name, value)
}
private fun assertInvalidHeader(header: String) {
val (name, value) = header.split(": ", limit = 2)
try {
MimeHeaderChecker.checkHeader(name, value)
fail("Expected exception")
} catch (expected: MimeHeaderParserException) {
}
}
}