Skip to content

Commit 17b8663

Browse files
committed
Improved validation for Base64 and QuotedPrintable
TODO: bring the UUEncodeValidator up to par.
1 parent 0c235b3 commit 17b8663

7 files changed

Lines changed: 148 additions & 97 deletions

File tree

MimeKit/AsyncMimeReader.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ async Task<ScanContentResult> ScanContentAsync (ScanContentType type, long begin
400400
bool midline = false;
401401

402402
if (DetectMimeComplianceViolations && type == ScanContentType.MimeContent && currentEncoding.HasValue)
403-
validator = GetEncodingValidator (currentEncoding.Value);
403+
validator = GetEncodingValidator (currentEncoding.Value, beginOffset, beginLineNumber);
404404

405405
do {
406406
int atleast = incomplete ? Math.Max (maxBoundaryLength, (inputEnd - inputIndex) + 1) : maxBoundaryLength;
@@ -436,14 +436,7 @@ async Task<ScanContentResult> ScanContentAsync (ScanContentType type, long begin
436436
}
437437
} while (boundary == BoundaryType.None);
438438

439-
if (validator is not null && !validator.Validate ()) {
440-
if (validator.Encoding == ContentEncoding.Base64)
441-
OnMimeComplianceViolation (MimeComplianceViolation.InvalidBase64Content, beginOffset, beginLineNumber);
442-
else if (validator.Encoding == ContentEncoding.UUEncode)
443-
OnMimeComplianceViolation (MimeComplianceViolation.InvalidUUEncodedContent, beginOffset, beginLineNumber);
444-
else
445-
OnMimeComplianceViolation (MimeComplianceViolation.InvalidQuotedPrintableContent, beginOffset, beginLineNumber);
446-
}
439+
validator?.Flush ();
447440

448441
// FIXME: need to redesign the above loop so that we don't consume the last <CR><LF> that belongs to the boundary marker.
449442
var isEmpty = contentLength == 0;

MimeKit/Encodings/Base64Validator.cs

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
55
//
6-
// Copyright (c) 2013-2025 .NET Foundation and Contributors
6+
// Copyright (c) 2013-2026 .NET Foundation and Contributors
77
//
88
// Permission is hereby granted, free of charge, to any person obtaining a copy
99
// of this software and associated documentation files (the "Software"), to deal
@@ -41,7 +41,9 @@ namespace MimeKit.Encodings {
4141
/// </remarks>
4242
class Base64Validator : IEncodingValidator
4343
{
44-
bool invalid;
44+
readonly MimeReader reader;
45+
long streamOffset;
46+
int lineNumber;
4547
int padding;
4648
int octets;
4749

@@ -51,8 +53,14 @@ class Base64Validator : IEncodingValidator
5153
/// <remarks>
5254
/// Creates a new base64 validator.
5355
/// </remarks>
54-
public Base64Validator ()
56+
/// <param name="reader">The mime reader.</param>
57+
/// <param name="streamOffset">The current stream offset.</param>
58+
/// <param name="lineNumber">The current line number.</param>
59+
public Base64Validator (MimeReader reader, long streamOffset, int lineNumber)
5560
{
61+
this.reader = reader;
62+
this.streamOffset = streamOffset;
63+
this.lineNumber = lineNumber;
5664
}
5765

5866
/// <summary>
@@ -95,15 +103,13 @@ unsafe void Validate (ref byte table, byte* input, int length)
95103

96104
if (rank == 0xFF) {
97105
if (c == (byte) '\n') {
98-
if (octets % 4 != 0) {
99-
invalid = true;
100-
return;
101-
}
106+
if (octets % 4 != 0)
107+
reader.OnMimeComplianceViolation (MimeComplianceViolation.IncompleteBase64Quantum, streamOffset, lineNumber);
102108

109+
lineNumber++;
103110
octets = 0;
104111
} else if (!c.IsWhitespace ()) {
105-
invalid = true;
106-
return;
112+
reader.OnMimeComplianceViolation (MimeComplianceViolation.InvalidBase64Character, streamOffset, lineNumber);
107113
}
108114
} else if (c == (byte) '=') {
109115
padding = 1;
@@ -112,31 +118,31 @@ unsafe void Validate (ref byte table, byte* input, int length)
112118
} else {
113119
octets++;
114120
}
121+
122+
streamOffset++;
115123
}
116124
}
117125

118126
while (inptr < inend) {
119127
byte c = *inptr++;
120128

121129
if (c == (byte) '\n') {
122-
if (octets % 4 != 0) {
123-
invalid = true;
124-
return;
125-
}
130+
if (octets % 4 != 0)
131+
reader.OnMimeComplianceViolation (MimeComplianceViolation.IncompleteBase64Quantum, streamOffset, lineNumber);
126132

133+
lineNumber++;
127134
octets = 0;
128135
} else if (c == (byte) '=') {
129136
padding++;
130137
octets++;
131138

132-
if (padding > 2) {
133-
invalid = true;
134-
return;
135-
}
139+
if (padding > 2)
140+
reader.OnMimeComplianceViolation (MimeComplianceViolation.InvalidBase64Padding, streamOffset, lineNumber);
136141
} else if (!c.IsWhitespace ()) {
137-
invalid = true;
138-
return;
142+
reader.OnMimeComplianceViolation (MimeComplianceViolation.Base64CharactersAfterPadding, streamOffset, lineNumber);
139143
}
144+
145+
streamOffset++;
140146
}
141147
}
142148

@@ -160,9 +166,6 @@ public unsafe void Write (byte[] buffer, int startIndex, int length)
160166
{
161167
ValidateArguments (buffer, startIndex, length);
162168

163-
if (invalid)
164-
return;
165-
166169
fixed (byte* inbuf = buffer) {
167170
ref byte table = ref MemoryMarshal.GetReference (Base64Decoder.base64_rank);
168171

@@ -171,15 +174,15 @@ public unsafe void Write (byte[] buffer, int startIndex, int length)
171174
}
172175

173176
/// <summary>
174-
/// Validate the content that was written to the validator.
177+
/// Flush the validator state.
175178
/// </summary>
176179
/// <remarks>
177-
/// Validates the content that was written to the validator.
180+
/// Flushes the validator state.
178181
/// </remarks>
179-
/// <returns><see langword="true"/> if the content was valid; otherwise, <see langword="false"/>.</returns>
180-
public bool Validate ()
182+
public void Flush ()
181183
{
182-
return !invalid && octets % 4 == 0 && padding <= 2;
184+
if (octets % 4 != 0)
185+
reader.OnMimeComplianceViolation (MimeComplianceViolation.IncompleteBase64Quantum, streamOffset, lineNumber);
183186
}
184187
}
185188
}

MimeKit/Encodings/IEncodingValidator.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
55
//
6-
// Copyright (c) 2013-2025 .NET Foundation and Contributors
6+
// Copyright (c) 2013-2026 .NET Foundation and Contributors
77
//
88
// Permission is hereby granted, free of charge, to any person obtaining a copy
99
// of this software and associated documentation files (the "Software"), to deal
@@ -63,12 +63,11 @@ interface IEncodingValidator
6363
void Write (byte[] buffer, int startIndex, int length);
6464

6565
/// <summary>
66-
/// Validate the content that was written to the validator.
66+
/// Flush the validator state.
6767
/// </summary>
6868
/// <remarks>
69-
/// Validates the content that was written to the validator.
69+
/// Flushes the validator state.
7070
/// </remarks>
71-
/// <returns><see langword="true"/> if the content was valid; otherwise, <see langword="false"/>.</returns>
72-
bool Validate ();
71+
void Flush ();
7372
}
7473
}

MimeKit/Encodings/QuotedPrintableValidator.cs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
55
//
6-
// Copyright (c) 2013-2025 .NET Foundation and Contributors
6+
// Copyright (c) 2013-2026 .NET Foundation and Contributors
77
//
88
// Permission is hereby granted, free of charge, to any person obtaining a copy
99
// of this software and associated documentation files (the "Software"), to deal
@@ -45,10 +45,12 @@ enum QpValidatorState : byte
4545
PassThrough,
4646
EqualSign,
4747
SoftBreak,
48-
DecodeByte,
49-
Invalid
48+
DecodeByte
5049
}
5150

51+
readonly MimeReader reader;
52+
long streamOffset;
53+
int lineNumber;
5254
QpValidatorState state;
5355

5456
/// <summary>
@@ -57,8 +59,14 @@ enum QpValidatorState : byte
5759
/// <remarks>
5860
/// Creates a new quoted-printable validator.
5961
/// </remarks>
60-
public QuotedPrintableValidator ()
62+
/// <param name="reader">The mime reader.</param>
63+
/// <param name="streamOffset">The current stream offset.</param>
64+
/// <param name="lineNumber">The current line number.</param>
65+
public QuotedPrintableValidator (MimeReader reader, long streamOffset, int lineNumber)
6166
{
67+
this.reader = reader;
68+
this.streamOffset = streamOffset;
69+
this.lineNumber = lineNumber;
6270
}
6371

6472
/// <summary>
@@ -104,6 +112,8 @@ unsafe void Validate (byte* input, int length)
104112
if (c == '=') {
105113
state = QpValidatorState.EqualSign;
106114
break;
115+
} else if (c == '\n') {
116+
lineNumber++;
107117
}
108118
}
109119
break;
@@ -116,35 +126,39 @@ unsafe void Validate (byte* input, int length)
116126
state = QpValidatorState.SoftBreak;
117127
} else if (c == '\n') {
118128
state = QpValidatorState.PassThrough;
129+
lineNumber++;
119130
} else {
120-
// invalid encoded sequence
121-
state = QpValidatorState.Invalid;
122-
return;
131+
reader.OnMimeComplianceViolation (MimeComplianceViolation.InvalidQuotedPrintableEncoding, streamOffset + (inptr - input), lineNumber);
132+
state = QpValidatorState.PassThrough;
123133
}
124134
break;
125135
case QpValidatorState.SoftBreak:
126-
state = QpValidatorState.PassThrough;
127136
c = *inptr++;
128137

129-
if (c != '\n') {
130-
// invalid encoded sequence
131-
state = QpValidatorState.Invalid;
132-
return;
138+
if (c == '\n') {
139+
lineNumber++;
140+
} else {
141+
reader.OnMimeComplianceViolation (MimeComplianceViolation.InvalidQuotedPrintableSoftBreak, streamOffset + (inptr - input), lineNumber);
133142
}
143+
144+
state = QpValidatorState.PassThrough;
134145
break;
135146
case QpValidatorState.DecodeByte:
136147
c = *inptr++;
137148

138149
if (!c.IsXDigit ()) {
139-
// invalid encoded sequence
140-
state = QpValidatorState.Invalid;
141-
return;
150+
reader.OnMimeComplianceViolation (MimeComplianceViolation.InvalidQuotedPrintableEncoding, streamOffset + (inptr - input), lineNumber);
151+
152+
if (c == '\n')
153+
lineNumber++;
142154
}
143155

144156
state = QpValidatorState.PassThrough;
145157
break;
146158
}
147159
}
160+
161+
streamOffset += length;
148162
}
149163

150164
/// <summary>
@@ -167,25 +181,24 @@ public unsafe void Write (byte[] buffer, int startIndex, int length)
167181
{
168182
ValidateArguments (buffer, startIndex, length);
169183

170-
if (state == QpValidatorState.Invalid)
171-
return;
172-
173184
fixed (byte* inbuf = buffer) {
174185
Validate (inbuf + startIndex, length);
175186
}
176187
}
177188

178189
/// <summary>
179-
/// Validate the content that was written to the validator.
190+
/// Flush the validator state.
180191
/// </summary>
181192
/// <remarks>
182-
/// Validates the content that was written to the validator.
193+
/// Flushes the validator state.
183194
/// </remarks>
184-
/// <returns><see langword="true"/> if the content was valid; otherwise, <see langword="false"/>.</returns>
185-
public bool Validate ()
195+
public void Flush ()
186196
{
187197
// Note: the only valid state to end on is the pass-through state.
188-
return state == QpValidatorState.PassThrough;
198+
if (state == QpValidatorState.EqualSign || state == QpValidatorState.DecodeByte)
199+
reader.OnMimeComplianceViolation (MimeComplianceViolation.InvalidQuotedPrintableEncoding, streamOffset, lineNumber);
200+
else if (state == QpValidatorState.SoftBreak)
201+
reader.OnMimeComplianceViolation (MimeComplianceViolation.InvalidQuotedPrintableSoftBreak, streamOffset, lineNumber);
189202
}
190203
}
191204
}

MimeKit/Encodings/UUValidator.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
55
//
6-
// Copyright (c) 2013-2025 .NET Foundation and Contributors
6+
// Copyright (c) 2013-2026 .NET Foundation and Contributors
77
//
88
// Permission is hereby granted, free of charge, to any person obtaining a copy
99
// of this software and associated documentation files (the "Software"), to deal
@@ -61,6 +61,9 @@ enum UUValidatorState : byte
6161
Invalid
6262
}
6363

64+
readonly MimeReader reader;
65+
long streamOffset;
66+
int lineNumber;
6467
UUValidatorState state;
6568
byte nsaved;
6669
byte uulen;
@@ -71,8 +74,14 @@ enum UUValidatorState : byte
7174
/// <remarks>
7275
/// Creates a new Unix-to-Unix validator.
7376
/// </remarks>
74-
public UUValidator ()
77+
/// <param name="reader">The mime reader.</param>
78+
/// <param name="streamOffset">The current stream offset.</param>
79+
/// <param name="lineNumber">The current line number.</param>
80+
public UUValidator (MimeReader reader, long streamOffset, int lineNumber)
7581
{
82+
this.reader = reader;
83+
this.streamOffset = streamOffset;
84+
this.lineNumber = lineNumber;
7685
}
7786

7887
/// <summary>
@@ -102,6 +111,7 @@ static void ValidateArguments (byte[] input, int startIndex, int length)
102111
[MethodImpl (MethodImplOptions.AggressiveInlining)]
103112
unsafe bool ScanBeginMarker (ref byte* inptr, byte* inend)
104113
{
114+
// TODO: properly track streamOffset and lineNumber for error reporting
105115
while (inptr < inend) {
106116
if (state == UUValidatorState.ExpectBegin) {
107117
if (nsaved != 0 && nsaved != (byte) '\n') {
@@ -262,6 +272,7 @@ unsafe bool ScanBeginMarker (ref byte* inptr, byte* inend)
262272
[MethodImpl (MethodImplOptions.AggressiveInlining)]
263273
unsafe void Validate (byte* input, int length)
264274
{
275+
// TODO: properly track streamOffset and lineNumber for error reporting
265276
bool last_was_eoln = uulen == 0;
266277
byte* inend = input + length;
267278
byte* inptr = input;
@@ -415,16 +426,15 @@ public unsafe void Write (byte[] buffer, int startIndex, int length)
415426
}
416427

417428
/// <summary>
418-
/// Validate the content that was written to the validator.
429+
/// Flush the validator state.
419430
/// </summary>
420431
/// <remarks>
421-
/// Validates the content that was written to the validator.
432+
/// Flushes the validator state.
422433
/// </remarks>
423-
/// <returns><see langword="true"/> if the content was valid; otherwise, <see langword="false"/>.</returns>
424-
public bool Validate ()
434+
public void Flush ()
425435
{
426436
// Note: the only valid state to end on is the 'end' state.
427-
return state == UUValidatorState.End;
437+
// TODO: report any remaining error(s).
428438
}
429439
}
430440
}

0 commit comments

Comments
 (0)