diff --git a/MimeKit/AsyncMimeReader.cs b/MimeKit/AsyncMimeReader.cs index 3ce67786ad..6a3603a2d6 100644 --- a/MimeKit/AsyncMimeReader.cs +++ b/MimeKit/AsyncMimeReader.cs @@ -129,6 +129,7 @@ async Task StepMboxMarkerAsync (CancellationToken cancellationToken) async Task StepHeadersAsync (CancellationToken cancellationToken) { int headersBeginLineNumber = lineNumber; + bool detectedBareLinefeed = false; var eof = false; headerBlockBegin = GetOffset (inputIndex); @@ -175,7 +176,7 @@ async Task StepHeadersAsync (CancellationToken cancellationToken) } // Check for an empty line denoting the end of the header block. - if (IsEndOfHeaderBlock (left)) { + if (IsEndOfHeaderBlock (left, ref detectedBareLinefeed)) { state = MimeParserState.Content; break; } @@ -272,12 +273,12 @@ async Task StepHeadersAsync (CancellationToken cancellationToken) do { unsafe { fixed (byte* inbuf = input) { - if (StepHeaderValue (inbuf, ref midline)) + if (StepHeaderValue (inbuf, ref midline, ref detectedBareLinefeed)) break; } } - if (await ReadAheadAsync (1, 0, cancellationToken).ConfigureAwait (false) == 0) { + if (await ReadAheadAsync (1, 1, cancellationToken).ConfigureAwait (false) == 0) { if (midline) OnComplianceIssueEncountered (MimeComplianceStatus.IncompleteHeader, beginOffset, beginLineNumber); else @@ -304,6 +305,9 @@ async Task StepHeadersAsync (CancellationToken cancellationToken) Debugger.Break (); #endif + if (detectedBareLinefeed) + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInHeader, headerBlockBegin, headersBeginLineNumber); + headerBlockEnd = GetOffset (inputIndex); await OnHeadersEndAsync (headerBlockBegin, headersBeginLineNumber, headerBlockEnd, lineNumber, cancellationToken).ConfigureAwait (false); @@ -416,6 +420,9 @@ async Task ConstructMimePartAsync (CancellationToken cancellationToken) var result = await ScanContentAsync (ScanContentType.MimeContent, beginOffset, beginLineNumber, true, cancellationToken).ConfigureAwait (false); await OnMimePartContentEndAsync (beginOffset, beginLineNumber, beginOffset + result.ContentLength, result.Lines, result.Format, cancellationToken).ConfigureAwait (false); + if (result.Format != NewLineFormat.Dos) + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInBody, beginOffset, beginLineNumber); + return result.Lines; } @@ -510,6 +517,9 @@ async Task MultipartScanPreambleAsync (CancellationToken cancellationToken) await OnMultipartPreambleBeginAsync (beginOffset, beginLineNumber, cancellationToken).ConfigureAwait (false); var result = await ScanContentAsync (ScanContentType.MultipartPreamble, beginOffset, beginLineNumber, false, cancellationToken).ConfigureAwait (false); await OnMultipartPreambleEndAsync (beginOffset, beginLineNumber, beginOffset + result.ContentLength, result.Lines, cancellationToken).ConfigureAwait (false); + + if (result.Format != NewLineFormat.Dos) + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInBody, beginOffset, beginLineNumber); } async Task MultipartScanEpilogueAsync (CancellationToken cancellationToken) @@ -520,6 +530,9 @@ async Task MultipartScanEpilogueAsync (CancellationToken cancellationToken) await OnMultipartEpilogueBeginAsync (beginOffset, beginLineNumber, cancellationToken).ConfigureAwait (false); var result = await ScanContentAsync (ScanContentType.MultipartEpilogue, beginOffset, beginLineNumber, true, cancellationToken).ConfigureAwait (false); await OnMultipartEpilogueEndAsync (beginOffset, beginLineNumber, beginOffset + result.ContentLength, result.Lines, cancellationToken).ConfigureAwait (false); + + if (result.Format != NewLineFormat.Dos) + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInBody, beginOffset, beginLineNumber); } async Task MultipartScanSubpartsAsync (ContentType multipartContentType, int depth, CancellationToken cancellationToken) diff --git a/MimeKit/MimeComplianceStatus.cs b/MimeKit/MimeComplianceStatus.cs index d279778a24..f53d245c60 100644 --- a/MimeKit/MimeComplianceStatus.cs +++ b/MimeKit/MimeComplianceStatus.cs @@ -41,82 +41,82 @@ public enum MimeComplianceStatus { /// Compliant = 0, + /// + /// A line was found in a header that was linefeed terminated instead of carriage return & linefeed terminated. + /// + BareLinefeedInHeader = 1 << 0, + + /// + /// A line was found in a MIME part body content that was linefeed terminated instead of carriage return & linefeed terminated. + /// + BareLinefeedInBody = 1 << 1, + /// /// The header was not of the correct form. /// - InvalidHeader = 1 << 0, + InvalidHeader = 1 << 2, /// /// The header ended prematurely at the end of the stream. /// - IncompleteHeader = 1 << 1, + IncompleteHeader = 1 << 3, /// /// The Content-Transfer-Encoding header value was not valid. /// - InvalidContentTransferEncoding = 1 << 2, + InvalidContentTransferEncoding = 1 << 4, /// /// The Content-Type header value was not valid. /// - InvalidContentType = 1 << 3, + InvalidContentType = 1 << 5, /// /// The MIME-Version header value was not valid. /// - InvalidMimeVersion = 1 << 4, + InvalidMimeVersion = 1 << 6, /// /// A line was found that was longer than the SMTP limit of 1000 characters. /// - InvalidWrapping = 1 << 5, + InvalidWrapping = 1 << 7, /// /// An empty line separating the headers from the body was missing. /// - MissingBodySeparator = 1 << 6, + MissingBodySeparator = 1 << 8, /// /// The MIME-Version header is missing. /// - MissingMimeVersion = 1 << 7, + MissingMimeVersion = 1 << 9, /// /// The boundary parameter is missing from a multipart Content-Type header. /// - MissingMultipartBoundaryParameter = 1 << 8, + MissingMultipartBoundaryParameter = 1 << 10, /// /// A multipart boundary was missing. /// - MissingMultipartBoundary = 1 << 9, + MissingMultipartBoundary = 1 << 11, /// /// A MIME part contained multiple Content-Transfer-Encoding headers. /// - DuplicateContentTransferEncoding = 1 << 10, + DuplicateContentTransferEncoding = 1 << 12, /// /// A MIME part contained multiple Content-Type headers. /// - DuplicateContentType = 1 << 11, + DuplicateContentType = 1 << 13, #if false - /// - /// A line was found in a MIME part body content that was linefeed terminated instead of carriage return & linefeed terminated. - /// - BareLinefeedInBody, - /// /// An external body was specified with invalid syntax. /// InvalidExternalBody, - /// - /// A line was found in a MIME part header that was linefeed terminated instead of carriage return & linefeed terminated. - /// - BareLinefeedInHeader, - /// /// Unexpected binary content was found in MIME part body content. /// diff --git a/MimeKit/MimeReader.cs b/MimeKit/MimeReader.cs index 733e352481..062e02a2d8 100644 --- a/MimeKit/MimeReader.cs +++ b/MimeKit/MimeReader.cs @@ -1530,7 +1530,7 @@ void StepHeaderField (int headerFieldLength) inputIndex += headerIndex; } - unsafe bool StepHeaderValue (byte* inbuf, ref bool midline) + unsafe bool StepHeaderValue (byte* inbuf, ref bool midline, ref bool detectedBareLinefeed) { byte* inptr = inbuf + inputIndex; byte* inend = inbuf + inputEnd; @@ -1595,6 +1595,9 @@ unsafe bool StepHeaderValue (byte* inbuf, ref bool midline) break; } + if (index > 0 && *(inptr - 1) != (byte) '\r') + detectedBareLinefeed = true; + // Consume the newline and update our line number state. inptr++; @@ -1619,12 +1622,13 @@ unsafe bool StepHeaderValue (byte* inbuf, ref bool midline) return inptr < inend; } - bool IsEndOfHeaderBlock (int left) + bool IsEndOfHeaderBlock (int left, ref bool detectedBareLinefeed) { if (input[inputIndex] == (byte) '\n') { state = MimeParserState.Content; inputIndex++; IncrementLineNumber (inputIndex); + detectedBareLinefeed = true; return true; } @@ -1724,6 +1728,7 @@ Header CreateHeader (long beginOffset, int beginLineNumber, int fieldNameLength, unsafe void StepHeaders (byte* inbuf, CancellationToken cancellationToken) { int headersBeginLineNumber = lineNumber; + bool detectedBareLinefeed = false; var eof = false; headerBlockBegin = GetOffset (inputIndex); @@ -1770,7 +1775,7 @@ unsafe void StepHeaders (byte* inbuf, CancellationToken cancellationToken) } // Check for an empty line denoting the end of the header block. - if (IsEndOfHeaderBlock (left)) { + if (IsEndOfHeaderBlock (left, ref detectedBareLinefeed)) { state = MimeParserState.Content; break; } @@ -1843,8 +1848,8 @@ unsafe void StepHeaders (byte* inbuf, CancellationToken cancellationToken) bool midline = true; // Consume the header value. - while (!StepHeaderValue (inbuf, ref midline)) { - if (ReadAhead (1, 0, cancellationToken) == 0) { + while (!StepHeaderValue (inbuf, ref midline, ref detectedBareLinefeed)) { + if (ReadAhead (1, 1, cancellationToken) == 0) { if (midline) OnComplianceIssueEncountered (MimeComplianceStatus.IncompleteHeader, beginOffset, beginLineNumber); else @@ -1871,6 +1876,9 @@ unsafe void StepHeaders (byte* inbuf, CancellationToken cancellationToken) Debugger.Break (); #endif + if (detectedBareLinefeed) + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInHeader, headerBlockBegin, headersBeginLineNumber); + headerBlockEnd = GetOffset (inputIndex); OnHeadersEnd (headerBlockBegin, headersBeginLineNumber, headerBlockEnd, lineNumber, cancellationToken); @@ -1889,6 +1897,9 @@ unsafe bool InnerSkipLine (byte* inbuf, bool consumeNewLine) if (inptr < inend) { inputIndex = (int) (inptr - inbuf); + if (input[inputIndex - 1] != (byte) '\r') + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInBody, GetOffset (inputIndex), lineNumber); + if (consumeNewLine) { inputIndex++; IncrementLineNumber (inputIndex); @@ -2236,6 +2247,9 @@ unsafe int ConstructMimePart (byte* inbuf, CancellationToken cancellationToken) var result = ScanContent (ScanContentType.MimeContent, inbuf, beginOffset, beginLineNumber, true, cancellationToken); OnMimePartContentEnd (beginOffset, beginLineNumber, beginOffset + result.ContentLength, result.Lines, result.Format, cancellationToken); + if (result.Format != NewLineFormat.Dos) + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInBody, beginOffset, beginLineNumber); + return result.Lines; } @@ -2326,6 +2340,9 @@ unsafe void MultipartScanPreamble (byte* inbuf, CancellationToken cancellationTo OnMultipartPreambleBegin (beginOffset, beginLineNumber, cancellationToken); var result = ScanContent (ScanContentType.MultipartPreamble, inbuf, beginOffset, beginLineNumber, false, cancellationToken); OnMultipartPreambleEnd (beginOffset, beginLineNumber, beginOffset + result.ContentLength, result.Lines, cancellationToken); + + if (result.Format != NewLineFormat.Dos) + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInBody, beginOffset, beginLineNumber); } unsafe void MultipartScanEpilogue (byte* inbuf, CancellationToken cancellationToken) @@ -2336,6 +2353,9 @@ unsafe void MultipartScanEpilogue (byte* inbuf, CancellationToken cancellationTo OnMultipartEpilogueBegin (beginOffset, beginLineNumber, cancellationToken); var result = ScanContent (ScanContentType.MultipartEpilogue, inbuf, beginOffset, beginLineNumber, true, cancellationToken); OnMultipartEpilogueEnd (beginOffset, beginLineNumber, beginOffset + result.ContentLength, result.Lines, cancellationToken); + + if (result.Format != NewLineFormat.Dos) + OnComplianceIssueEncountered (MimeComplianceStatus.BareLinefeedInBody, beginOffset, beginLineNumber); } unsafe void MultipartScanSubparts (ContentType multipartContentType, byte* inbuf, int depth, CancellationToken cancellationToken)