diff --git a/MsgReader.sln.DotSettings b/MsgReader.sln.DotSettings index 3c7a7125..77b754f5 100644 --- a/MsgReader.sln.DotSettings +++ b/MsgReader.sln.DotSettings @@ -3,22 +3,26 @@ MR NL RDN + True True True True True True True + True True True True True True + True True True True True True + True True True True @@ -71,6 +75,7 @@ True True True + True True True True @@ -81,6 +86,7 @@ True True True + True True True @@ -91,6 +97,7 @@ True True True + True True True True @@ -102,6 +109,8 @@ True True True + True + True True True \ No newline at end of file diff --git a/MsgReaderCore/Mime/Decode/Base64.cs b/MsgReaderCore/Mime/Decode/Base64.cs index 7766502a..6f0ba145 100644 --- a/MsgReaderCore/Mime/Decode/Base64.cs +++ b/MsgReaderCore/Mime/Decode/Base64.cs @@ -34,7 +34,7 @@ public static byte[] Decode(string base64Encoded) catch (Exception) { Logger.WriteToLog("Base64 decoding still failed returning empty byte array"); - return new byte[0]; + return Array.Empty(); } } } @@ -82,9 +82,9 @@ private static string RemoveInvalidBase64Chars(string base64Encoded) foreach (var chr in base64Encoded) { var val = (int)chr; - if ((val >= 65 && val <= 90) || // 'A'..'Z' - (val >= 97 && val <= 122) || // 'a'..'z' - (val >= 48 && val <= 57) || // '0'..'9' + if (val is >= 65 and <= 90 || // 'A'..'Z' + val is >= 97 and <= 122 || // 'a'..'z' + val is >= 48 and <= 57 || // '0'..'9' val == 43 || val == 47) // '+' and '/' result.Append(chr); else diff --git a/MsgReaderCore/Mime/Decode/EncodedWord.cs b/MsgReaderCore/Mime/Decode/EncodedWord.cs index e906283c..3c3ee9f7 100644 --- a/MsgReaderCore/Mime/Decode/EncodedWord.cs +++ b/MsgReaderCore/Mime/Decode/EncodedWord.cs @@ -122,7 +122,7 @@ public static string Decode(string encodedWords) break; default: - throw new ArgumentException("The encoding " + encoding + " was not recognized"); + throw new ArgumentException($"The encoding {encoding} was not recognized"); } // Replace our encoded value with our decoded value diff --git a/MsgReaderCore/Mime/Decode/EncodingFinder.cs b/MsgReaderCore/Mime/Decode/EncodingFinder.cs index d7f20a68..e78e8bd5 100644 --- a/MsgReaderCore/Mime/Decode/EncodingFinder.cs +++ b/MsgReaderCore/Mime/Decode/EncodingFinder.cs @@ -52,7 +52,7 @@ public static class EncodingFinder public static FallbackDecoderDelegate FallbackDecoder { private get; set; } /// - /// Mapping from charactersets to encodings. + /// Mapping from character sets to encodings. /// private static Dictionary EncodingMap { get; set; } #endregion @@ -69,7 +69,7 @@ static EncodingFinder() #region Reset /// - /// Used to reset this static class to facilite isolated unit testing. + /// Used to reset this static class to facilitate isolated unit testing. /// internal static void Reset() { @@ -97,8 +97,8 @@ internal static Encoding FindEncoding(string characterSet) var charSetUpper = characterSet.ToUpperInvariant(); // Check if the characterSet is explicitly mapped to an encoding - if (EncodingMap.ContainsKey(charSetUpper)) - return EncodingMap[charSetUpper]; + if (EncodingMap.TryGetValue(charSetUpper, out var encoding)) + return encoding; // Try to generally find the encoding try @@ -133,7 +133,7 @@ internal static Encoding FindEncoding(string characterSet) if (fallbackDecoderResult != null) return fallbackDecoderResult; - // If no solution was found, throw catched exception + // If no solution was found, throw exception throw; } } diff --git a/MsgReaderCore/Mime/Decode/QuotedPrintable.cs b/MsgReaderCore/Mime/Decode/QuotedPrintable.cs index 8d92b6b2..9b22a4f3 100644 --- a/MsgReaderCore/Mime/Decode/QuotedPrintable.cs +++ b/MsgReaderCore/Mime/Decode/QuotedPrintable.cs @@ -79,52 +79,50 @@ private static byte[] Rfc2047QuotedPrintableDecode(string toDecode, bool encoded throw new ArgumentNullException(nameof(toDecode)); // Create a byte array builder which is roughly equivalent to a StringBuilder - using (var byteArrayBuilder = StreamHelpers.Manager.GetStream()) - { - // Remove illegal control characters - toDecode = RemoveIllegalControlCharacters(toDecode); + using var byteArrayBuilder = StreamHelpers.Manager.GetStream(); + // Remove illegal control characters + toDecode = RemoveIllegalControlCharacters(toDecode); - // Run through the whole string that needs to be decoded - for (var i = 0; i < toDecode.Length; i++) + // Run through the whole string that needs to be decoded + for (var i = 0; i < toDecode.Length; i++) + { + var currentChar = toDecode[i]; + if (currentChar == '=') { - var currentChar = toDecode[i]; - if (currentChar == '=') + // Check that there is at least two characters behind the equal sign + if (toDecode.Length - i < 3) { - // Check that there is at least two characters behind the equal sign - if (toDecode.Length - i < 3) - { - // We are at the end of the toDecode string, but something is missing. Handle it the way RFC 2045 states - WriteAllBytesToStream(byteArrayBuilder, DecodeEqualSignNotLongEnough(toDecode.Substring(i))); - - // Since it was the last part, we should stop parsing anymore - break; - } - - // Decode the Quoted-Printable part - var quotedPrintablePart = toDecode.Substring(i, 3); - WriteAllBytesToStream(byteArrayBuilder, DecodeEqualSign(quotedPrintablePart)); - - // We now consumed two extra characters. Go forward two extra characters - i += 2; + // We are at the end of the toDecode string, but something is missing. Handle it the way RFC 2045 states + WriteAllBytesToStream(byteArrayBuilder, DecodeEqualSignNotLongEnough(toDecode.Substring(i))); + + // Since it was the last part, we should stop parsing anymore + break; } + + // Decode the Quoted-Printable part + var quotedPrintablePart = toDecode.Substring(i, 3); + WriteAllBytesToStream(byteArrayBuilder, DecodeEqualSign(quotedPrintablePart)); + + // We now consumed two extra characters. Go forward two extra characters + i += 2; + } + else + { + // This character is not quoted printable hex encoded. + + // Could it be the _ character, which represents space + // and are we using the encoded word variant of QuotedPrintable + if (currentChar == '_' && encodedWordVariant) + // The RFC specifies that the "_" always represents hexadecimal 20 even if the + // SPACE character occupies a different code position in the character set in use. + byteArrayBuilder.WriteByte(0x20); else - { - // This character is not quoted printable hex encoded. - - // Could it be the _ character, which represents space - // and are we using the encoded word variant of QuotedPrintable - if (currentChar == '_' && encodedWordVariant) - // The RFC specifies that the "_" always represents hexadecimal 20 even if the - // SPACE character occupies a different code position in the character set in use. - byteArrayBuilder.WriteByte(0x20); - else - // This is not encoded at all. This is a literal which should just be included into the output. - byteArrayBuilder.WriteByte((byte)currentChar); - } + // This is not encoded at all. This is a literal which should just be included into the output. + byteArrayBuilder.WriteByte((byte)currentChar); } - - return byteArrayBuilder.ToArray(); } + + return byteArrayBuilder.ToArray(); } #endregion @@ -162,7 +160,7 @@ private static string RemoveIllegalControlCharacters(string input) throw new ArgumentNullException(nameof(input)); // First we remove any \r or \n which is not part of a \r\n pair - input = RemoveCarriageReturnAndNewLinewIfNotInPair(input); + input = RemoveCarriageReturnAndNewLineIfNotInPair(input); // Here only legal \r\n is left over // We now simply keep them, and the \t which is also allowed @@ -173,14 +171,14 @@ private static string RemoveIllegalControlCharacters(string input) } #endregion - #region RemoveCarriageReturnAndNewLinewIfNotInPair + #region RemoveCarriageReturnAndNewLineIfNotInPair /// /// This method will remove any \r and \n which is not paired as \r\n /// /// String to remove lonely \r and \n's from /// A string without lonely \r and \n's /// If is - private static string RemoveCarriageReturnAndNewLinewIfNotInPair(string input) + private static string RemoveCarriageReturnAndNewLineIfNotInPair(string input) { if (input == null) throw new ArgumentNullException(nameof(input)); @@ -263,7 +261,7 @@ private static byte[] DecodeEqualSignNotLongEnough(string decode) throw new ArgumentException(@"First part of decode must be an equal sign", nameof(decode)); // We will now believe that the string sent to us, was actually not encoded - // Therefore it must be in US-ASCII and we will return the bytes it corrosponds to + // Therefore it must be in US-ASCII and we will return the bytes it corresponds to return Encoding.ASCII.GetBytes(decode); } #endregion @@ -297,14 +295,14 @@ private static byte[] DecodeEqualSign(string decode) // It might be a // - hex-string like =3D, denoting the character with hex value 3D // - it might be the last character on the line before a CRLF - // pair, denoting a soft linebreak, which simply + // pair, denoting a soft line break, which simply // splits the text up, because of the 76 chars per line restriction if (decode.Contains("\r\n")) // Soft break detected // We want to return string.Empty which is equivalent to a zero-length byte array - return new byte[0]; + return Array.Empty(); - // Hex string detected. Convertion needed. + // Hex string detected. Conversion needed. // It might be that the string located after the equal sign is not hex characters // An example: =JU // In that case we would like to catch the FormatException and do something else @@ -338,12 +336,12 @@ private static byte[] DecodeEqualSign(string decode) // the data. // So we choose to believe this is actually an un-encoded string - // Therefore it must be in US-ASCII and we will return the bytes it corrosponds to + // Therefore it must be in US-ASCII and we will return the bytes it corresponds to return Encoding.ASCII.GetBytes(decode); } catch (Exception) { - return new byte[0]; + return Array.Empty(); } } #endregion diff --git a/MsgReaderCore/Mime/Decode/Rfc2231Decoder.cs b/MsgReaderCore/Mime/Decode/Rfc2231Decoder.cs index 5ff24373..3f3931b1 100644 --- a/MsgReaderCore/Mime/Decode/Rfc2231Decoder.cs +++ b/MsgReaderCore/Mime/Decode/Rfc2231Decoder.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; +// ReSharper disable CommentTypo namespace MsgReader.Mime.Decode; @@ -213,7 +214,7 @@ private static List> DecodePairs(IList> DecodePairs(IList(key, value)); diff --git a/MsgReaderCore/Mime/Decode/Rfc2822DateTime.cs b/MsgReaderCore/Mime/Decode/Rfc2822DateTime.cs index 0cf781ae..b9d533e3 100644 --- a/MsgReaderCore/Mime/Decode/Rfc2822DateTime.cs +++ b/MsgReaderCore/Mime/Decode/Rfc2822DateTime.cs @@ -305,12 +305,12 @@ private static DateTime ExtractDateTime(string dateInput) // Needs to find: 21 Nov 1997 09:55:06 // Seconds does not need to be specified - // Even though it is illigal, sometimes hours, minutes or seconds are only specified with one digit + // Even though it is illegal, sometimes hours, minutes or seconds are only specified with one digit // Year with 2 or 4 digits (1922 or 22) const string year = @"(\d\d\d\d|\d\d)"; - // Time with one or two digits for hour and minute and optinal seconds (06:04:06 or 6:4:6 or 06:04 or 6:4) + // Time with one or two digits for hour and minute and optional seconds (06:04:06 or 6:4:6 or 06:04 or 6:4) const string time = @"\d?\d:\d?\d(:\d?\d)?"; // Correct format is 21 Nov 1997 09:55:06 diff --git a/MsgReaderCore/Mime/Decode/SizeParser.cs b/MsgReaderCore/Mime/Decode/SizeParser.cs index 57ec6a37..65d896f0 100644 --- a/MsgReaderCore/Mime/Decode/SizeParser.cs +++ b/MsgReaderCore/Mime/Decode/SizeParser.cs @@ -66,7 +66,7 @@ private static string ExtractUnit(string sizeWithUnit) private static bool IsDigit(char value) { // we don't want to use char.IsDigit since it would accept esoterical unicode digits - return value >= '0' && value <= '9'; + return value is >= '0' and <= '9'; } #endregion diff --git a/MsgReaderCore/Mime/Decode/UUEncode.cs b/MsgReaderCore/Mime/Decode/UUEncode.cs index 479b33d0..817d8290 100644 --- a/MsgReaderCore/Mime/Decode/UUEncode.cs +++ b/MsgReaderCore/Mime/Decode/UUEncode.cs @@ -1,11 +1,13 @@ using System; using System.IO; using MsgReader.Helpers; +// ReSharper disable InconsistentNaming namespace MsgReader.Mime.Decode; internal static class UUEncode { + #region Fields private static readonly byte[] UUDecMap = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -25,80 +27,81 @@ internal static class UUEncode 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + #endregion + #region Decode public static byte[] Decode(byte[] encodeBytes) { - using (Stream input = StreamHelpers.Manager.GetStream("UUEncode.cs", encodeBytes, 0, encodeBytes.Length)) - using (var output = StreamHelpers.Manager.GetStream()) + using Stream input = StreamHelpers.Manager.GetStream("UUEncode.cs", encodeBytes, 0, encodeBytes.Length); + using var output = StreamHelpers.Manager.GetStream(); + try { - try - { - if (input == null) - throw new ArgumentNullException("input"); - - var len = input.Length; - if (len == 0) - return new byte[0]; + if (input == null) + throw new ArgumentNullException(nameof(encodeBytes)); - long didx = 0; - var nextByte = input.ReadByte(); - while (nextByte >= 0) - { - // Get line length (in number of encoded octets) - int line_len = UUDecMap[nextByte]; + var len = input.Length; + if (len == 0) + return Array.Empty(); - // Ascii printable to 0-63 and 4-byte to 3-byte conversion - var end = didx + line_len; - byte A, B, C, D; - if (end > 2) - while (didx < end - 2) - { - A = UUDecMap[input.ReadByte()]; - B = UUDecMap[input.ReadByte()]; - C = UUDecMap[input.ReadByte()]; - D = UUDecMap[input.ReadByte()]; - - output.WriteByte((byte)(((A << 2) & 255) | ((B >> 4) & 3))); - output.WriteByte((byte)(((B << 4) & 255) | ((C >> 2) & 15))); - output.WriteByte((byte)(((C << 6) & 255) | (D & 63))); - didx += 3; - } + long didX = 0; + var nextByte = input.ReadByte(); + while (nextByte >= 0) + { + // Get line length (in number of encoded octets) + int lineLen = UUDecMap[nextByte]; - if (didx < end) + // Ascii printable to 0-63 and 4-byte to 3-byte conversion + var end = didX + lineLen; + byte a, b, c; + if (end > 2) + while (didX < end - 2) { - A = UUDecMap[input.ReadByte()]; - B = UUDecMap[input.ReadByte()]; - output.WriteByte((byte)(((A << 2) & 255) | ((B >> 4) & 3))); - didx++; - } + a = UUDecMap[input.ReadByte()]; + b = UUDecMap[input.ReadByte()]; + c = UUDecMap[input.ReadByte()]; + var d = UUDecMap[input.ReadByte()]; - if (didx < end) - { - B = UUDecMap[input.ReadByte()]; - C = UUDecMap[input.ReadByte()]; - output.WriteByte((byte)(((B << 4) & 255) | ((C >> 2) & 15))); - didx++; + output.WriteByte((byte)(((a << 2) & 255) | ((b >> 4) & 3))); + output.WriteByte((byte)(((b << 4) & 255) | ((c >> 2) & 15))); + output.WriteByte((byte)(((c << 6) & 255) | (d & 63))); + didX += 3; } - // Skip padding - do - { - nextByte = input.ReadByte(); - } while (nextByte >= 0 && nextByte != '\n' && nextByte != '\r'); + if (didX < end) + { + a = UUDecMap[input.ReadByte()]; + b = UUDecMap[input.ReadByte()]; + output.WriteByte((byte)(((a << 2) & 255) | ((b >> 4) & 3))); + didX++; + } - // Skip end of line - do - { - nextByte = input.ReadByte(); - } while (nextByte >= 0 && (nextByte == '\n' || nextByte == '\r')); + if (didX < end) + { + b = UUDecMap[input.ReadByte()]; + c = UUDecMap[input.ReadByte()]; + output.WriteByte((byte)(((b << 4) & 255) | ((c >> 2) & 15))); + didX++; } - return output.ToArray(); - } - catch (Exception) - { - return new byte[0]; + // Skip padding + do + { + nextByte = input.ReadByte(); + } while (nextByte >= 0 && nextByte != '\n' && nextByte != '\r'); + + // Skip end of line + do + { + nextByte = input.ReadByte(); + } while (nextByte >= 0 && nextByte is '\n' or '\r'); } + + return output.ToArray(); + } + catch (Exception) + { + return Array.Empty(); } } + #endregion } \ No newline at end of file diff --git a/MsgReaderCore/Mime/Header/ContentTransferEncoding.cs b/MsgReaderCore/Mime/Header/ContentTransferEncoding.cs index bf1ad926..8bf78a38 100644 --- a/MsgReaderCore/Mime/Header/ContentTransferEncoding.cs +++ b/MsgReaderCore/Mime/Header/ContentTransferEncoding.cs @@ -1,4 +1,5 @@ using System; +// ReSharper disable InconsistentNaming namespace MsgReader.Mime.Header; diff --git a/MsgReaderCore/Mime/Header/HeaderExtractor.cs b/MsgReaderCore/Mime/Header/HeaderExtractor.cs index 53ba08aa..974492d7 100644 --- a/MsgReaderCore/Mime/Header/HeaderExtractor.cs +++ b/MsgReaderCore/Mime/Header/HeaderExtractor.cs @@ -41,21 +41,18 @@ private static int FindHeaderEndPosition(byte[] messageContent) throw new ArgumentNullException(nameof(messageContent)); // Convert the byte array into a stream - using (Stream stream = - StreamHelpers.Manager.GetStream("HeaderExtractor,cs", messageContent, 0, messageContent.Length)) + using Stream stream = StreamHelpers.Manager.GetStream("HeaderExtractor,cs", messageContent, 0, messageContent.Length); + while (true) { - while (true) - { - // Read a line from the stream. We know headers are in US-ASCII - // therefore it is not problem to read them as such - var line = StreamUtility.ReadLineAsAscii(stream); - - // The end of headers is signaled when a blank line is found - // or if the line is null - in which case the email is actually an email with - // only headers but no body - if (string.IsNullOrEmpty(line)) - return (int)stream.Position; - } + // Read a line from the stream. We know headers are in US-ASCII + // therefore it is not problem to read them as such + var line = StreamUtility.ReadLineAsAscii(stream); + + // The end of headers is signaled when a blank line is found + // or if the line is null - in which case the email is actually an email with + // only headers but no body + if (string.IsNullOrEmpty(line)) + return (int)stream.Position; } } #endregion @@ -83,8 +80,8 @@ public static void ExtractHeadersAndBody(byte[] fullRawMessage, out MessageHeade // using US-ASCII encoding //var headersString = Encoding.ASCII.GetString(fullRawMessage, 0, endOfHeaderLocation); - // MIME headers should aways be ASCII encoded, but sometimes they don't so we read them as UTF8. - // It should not make any difference if we do it this way because UTF-8 superseeds ASCII encoding + // MIME headers should always be ASCII encoded, but sometimes they don't so we read them as UTF8. + // It should not make any difference if we do it this way because UTF-8 super seeds ASCII encoding var headersString = Encoding.UTF8.GetString(fullRawMessage, 0, endOfHeaderLocation); // Now parse the headers to a NameValueCollection @@ -119,59 +116,55 @@ private static NameValueCollection ExtractHeaders(string messageContent) var headers = new NameValueCollection(); - using (var messageReader = new StringReader(messageContent)) + using var messageReader = new StringReader(messageContent); + // Read until all headers have ended. + // The headers ends when an empty line is encountered + // An empty message might actually not have an empty line, in which + // case the headers end with null value. + string line; + + while (!string.IsNullOrEmpty(line = messageReader.ReadLine())) { - // Read until all headers have ended. - // The headers ends when an empty line is encountered - // An empty message might actually not have an empty line, in which - // case the headers end with null value. - string line; + // Split into name and value + var header = SeparateHeaderNameAndValue(line); + + // First index is header name + var headerName = header.Key; + + // Second index is the header value. + // Use a StringBuilder since the header value may be continued on the next line + var headerValue = new StringBuilder(header.Value); + + // Keep reading until we would hit next header + // This if for handling multi line headers + while (IsMoreLinesInHeaderValue(messageReader)) + { + // Unfolding is accomplished by simply removing any CRLF + // that is immediately followed by WSP + // This was done using ReadLine (it discards CRLF) + // See http://tools.ietf.org/html/rfc822#section-3.1.1 for more information + var moreHeaderValue = messageReader.ReadLine(); + + // If this exception is ever raised, there is an serious algorithm failure + // IsMoreLinesInHeaderValue does not return true if the next line does not exist + // This check is only included to stop the nagging "possibly null" code analysis hint + if (moreHeaderValue == null) + throw new ArgumentException("This will never happen"); + + // Simply append the line just read to the header value + headerValue.Append(moreHeaderValue); + } + + // Now we have the name and full value. Add it - while (!string.IsNullOrEmpty(line = messageReader.ReadLine())) + if (headers.AllKeys.Contains(headerName)) { - // Split into name and value - var header = SeparateHeaderNameAndValue(line); - - // First index is header name - var headerName = header.Key; - - // Second index is the header value. - // Use a StringBuilder since the header value may be continued on the next line - var headerValue = new StringBuilder(header.Value); - - // Keep reading until we would hit next header - // This if for handling multi line headers - while (IsMoreLinesInHeaderValue(messageReader)) - { - // Unfolding is accomplished by simply removing any CRLF - // that is immediately followed by WSP - // This was done using ReadLine (it discards CRLF) - // See http://tools.ietf.org/html/rfc822#section-3.1.1 for more information - var moreHeaderValue = messageReader.ReadLine(); - - // If this exception is ever raised, there is an serious algorithm failure - // IsMoreLinesInHeaderValue does not return true if the next line does not exist - // This check is only included to stop the nagging "possibly null" code analysis hint - if (moreHeaderValue == null) - throw new ArgumentException("This will never happen"); - - // Simply append the line just read to the header value - headerValue.Append(moreHeaderValue); - } - - // Now we have the name and full value. Add it - - if (headers.AllKeys.Contains(headerName)) - { - var value = headers[headerName]; - value += "," + headerValue; - headers[headerName] = value; - } - else - { - headers.Add(headerName, headerValue.ToString()); - } + var value = headers[headerName]; + value += "," + headerValue; + headers[headerName] = value; } + else + headers.Add(headerName, headerValue.ToString()); } return headers; diff --git a/MsgReaderCore/Mime/Header/HeaderFieldParser.cs b/MsgReaderCore/Mime/Header/HeaderFieldParser.cs index d5fe634e..2bf5cdff 100644 --- a/MsgReaderCore/Mime/Header/HeaderFieldParser.cs +++ b/MsgReaderCore/Mime/Header/HeaderFieldParser.cs @@ -204,7 +204,7 @@ public static ContentDisposition ParseContentDisposition(string headerValue) switch (key) { case "": - // This is the DispisitionType - it has no key since it is the first one + // This is the Disposition type - it has no key since it is the first one // and has no = in it. contentDisposition.DispositionType = value; break; @@ -214,7 +214,7 @@ public static ContentDisposition ParseContentDisposition(string headerValue) case "NAME": case "FILENAME": case "REMOTE-IMAGE": - // The filename might be in qoutes, and it might be encoded-word encoded + // The filename might be in quotes, and it might be encoded-word encoded contentDisposition.FileName = EncodedWord.Decode(value); break; @@ -231,8 +231,8 @@ public static ContentDisposition ParseContentDisposition(string headerValue) case "MODIFICATION-DATE": case "MODIFICATION-DATE-PARM": - var midificationDate = new DateTime(Rfc2822DateTime.StringToDate(value).Ticks); - contentDisposition.ModificationDate = midificationDate; + var modificationDate = new DateTime(Rfc2822DateTime.StringToDate(value).Ticks); + contentDisposition.ModificationDate = modificationDate; break; case "READ-DATE": @@ -244,14 +244,17 @@ public static ContentDisposition ParseContentDisposition(string headerValue) contentDisposition.Size = SizeParser.Parse(value); break; + case "ALT": + contentDisposition.Parameters.Add(key, value); + break; + case "CHARSET": // ignoring invalid parameter in Content-Disposition case "VOICE": break; default: if (!key.StartsWith("X-")) - throw new ArgumentException( - "Unknown parameter in Content-Disposition. Ask developer to fix! Parameter: " + key); + throw new ArgumentException("Unknown parameter in Content-Disposition. Ask developer to fix! Parameter: " + key); contentDisposition.Parameters.Add(key, value); break; } diff --git a/MsgReaderCore/Mime/Header/Received.cs b/MsgReaderCore/Mime/Header/Received.cs index af31b8e3..3fab2f59 100644 --- a/MsgReaderCore/Mime/Header/Received.cs +++ b/MsgReaderCore/Mime/Header/Received.cs @@ -55,16 +55,13 @@ public class Received /// public Received(string headerValue) { - if (headerValue == null) - throw new ArgumentNullException(nameof(headerValue)); - - // Remember the raw input if someone whishes to use it - Raw = headerValue; + // Remember the raw input if someone wishes to use it + Raw = headerValue ?? throw new ArgumentNullException(nameof(headerValue)); // Default Date value Date = DateTime.MinValue; - // The date part is the last part of the string, and is preceeded by a semicolon + // The date part is the last part of the string, and is preceded by a semicolon // Some emails forgets to specify the date, therefore we need to check if it is there if (headerValue.Contains(";")) { diff --git a/MsgReaderCore/Mime/Header/RfcMailAddress.cs b/MsgReaderCore/Mime/Header/RfcMailAddress.cs index 7a0b991f..0dea9206 100644 --- a/MsgReaderCore/Mime/Header/RfcMailAddress.cs +++ b/MsgReaderCore/Mime/Header/RfcMailAddress.cs @@ -86,16 +86,10 @@ public class RfcMailAddress /// private RfcMailAddress(MailAddress mailAddress, string raw) { - if (mailAddress == null) - throw new ArgumentNullException(nameof(mailAddress)); - - if (raw == null) - throw new ArgumentNullException(nameof(raw)); - - MailAddress = mailAddress; + MailAddress = mailAddress ?? throw new ArgumentNullException(nameof(mailAddress)); Address = mailAddress.Address; DisplayName = mailAddress.DisplayName; - Raw = raw; + Raw = raw ?? throw new ArgumentNullException(nameof(raw)); } /// @@ -106,12 +100,9 @@ private RfcMailAddress(MailAddress mailAddress, string raw) /// If is private RfcMailAddress(string raw) { - if (raw == null) - throw new ArgumentNullException(nameof(raw)); - MailAddress = null; Address = string.Empty; - DisplayName = raw; + DisplayName = raw ?? throw new ArgumentNullException(nameof(raw)); Raw = raw; } #endregion @@ -130,13 +121,6 @@ public override string ToString() #region Parsing /// /// Parses an email address from a MIME header
- ///
- /// Examples of input: - /// Eksperten mailrobot <noreply@mail.eksperten.dk>
- /// "Eksperten mailrobot" <noreply@mail.eksperten.dk>
- /// <noreply@mail.eksperten.dk>
- /// noreply@mail.eksperten.dk
- ///
/// It might also contain encoded text, which will then be decoded. ///
/// The value to parse out and email and/or a username @@ -200,33 +184,27 @@ internal static RfcMailAddress ParseMailAddress(string input) var emailLength = indexEndEmail - indexStartEmail; var emailAddress = input.Substring(indexStartEmail, emailLength).Trim(); - // There has been cases where there was no emailaddress between the < and > + // There has been cases where there was no email address between the < and > if (!string.IsNullOrEmpty(emailAddress)) // If the username is quoted, MailAddress' constructor will remove them for us return new RfcMailAddress(new MailAddress(emailAddress, username), input); } // This might be on the form noreply@mail.eksperten.dk - // Check if there is an email, if notm there is no need to try + // Check if there is an email, if not there is no need to try if (input.Contains("@")) return new RfcMailAddress(new MailAddress(input), input); } catch (FormatException) { - // Sometimes invalid emails are sent, like sqlmap-user@sourceforge.net. (last period is illigal) + // Sometimes invalid emails are sent, like sqlmap-user@sourceforge.net. (last period is illegal) } - // It could be that the format used was simply a name - // which is indeed valid according to the RFC - // Example: - // Eksperten mailrobot return new RfcMailAddress(input); } /// - /// Parses input of the form
- /// Eksperten mailrobot <noreply@mail.eksperten.dk>, ...
- /// to a list of RFCMailAddresses + /// Parses input to a list of RFCMailAddresses ///
/// The input that is a comma-separated list of EmailAddresses to parse /// A List of objects extracted from the parameter. diff --git a/MsgReaderCore/Mime/Message.cs b/MsgReaderCore/Mime/Message.cs index 169563af..1e0901e5 100644 --- a/MsgReaderCore/Mime/Message.cs +++ b/MsgReaderCore/Mime/Message.cs @@ -270,8 +270,7 @@ private void ProcessSignedContent(byte[] data) } // Get the decoded attachment - using (var memoryStream = StreamHelpers.Manager.GetStream("Message.cs", signedCms.ContentInfo.Content, 0, - signedCms.ContentInfo.Content.Length)) + using (var memoryStream = StreamHelpers.Manager.GetStream("Message.cs", signedCms.ContentInfo.Content, 0, signedCms.ContentInfo.Content.Length)) { var eml = Load(memoryStream); if (eml.TextBody != null) @@ -279,8 +278,6 @@ private void ProcessSignedContent(byte[] data) if (eml.HtmlBody != null) HtmlBody = eml.HtmlBody; - - //foreach (var emlAttachment in eml.Attachments) } Logger.WriteToLog("Signed content processed"); diff --git a/MsgReaderCore/Mime/MessagePart.cs b/MsgReaderCore/Mime/MessagePart.cs index 9a46b4c0..32ff2199 100644 --- a/MsgReaderCore/Mime/MessagePart.cs +++ b/MsgReaderCore/Mime/MessagePart.cs @@ -9,6 +9,7 @@ using MsgReader.Mime.Header; using MsgReader.Tnef; using MsgReader.Tnef.Enums; +// ReSharper disable UnusedMember.Global // ReSharper disable UnusedAutoPropertyAccessor.Global @@ -354,17 +355,20 @@ private static string FindFileName(byte[] rawBody, MessageHeader headers, string if (!string.IsNullOrEmpty(headers.Subject)) return FileManager.RemoveInvalidFileNameChars(headers.Subject) + extensionFromContentType; - if (extensionFromContentType.Equals(".eml", StringComparison.OrdinalIgnoreCase)) - try - { - var message = new Message(rawBody); - if (!string.IsNullOrEmpty(message.Headers?.Subject)) - return FileManager.RemoveInvalidFileNameChars(message.Headers.Subject) + extensionFromContentType; - } - // ReSharper disable once EmptyGeneralCatchClause - catch - { - } + if (!extensionFromContentType.Equals(".eml", StringComparison.OrdinalIgnoreCase)) + return !string.IsNullOrEmpty(contentTypeName) + ? FileManager.RemoveInvalidFileNameChars(contentTypeName) + : FileManager.RemoveInvalidFileNameChars(defaultName + extensionFromContentType); + try + { + var message = new Message(rawBody); + if (!string.IsNullOrEmpty(message.Headers?.Subject)) + return FileManager.RemoveInvalidFileNameChars(message.Headers.Subject) + extensionFromContentType; + } + catch + { + // Ignore + } return !string.IsNullOrEmpty(contentTypeName) ? FileManager.RemoveInvalidFileNameChars(contentTypeName) diff --git a/MsgReaderCore/Mime/Traverse/AttachmentFinder.cs b/MsgReaderCore/Mime/Traverse/AttachmentFinder.cs index 61884843..8df8375c 100644 --- a/MsgReaderCore/Mime/Traverse/AttachmentFinder.cs +++ b/MsgReaderCore/Mime/Traverse/AttachmentFinder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using MsgReader.Helpers; using MsgReader.Tnef; @@ -19,39 +20,32 @@ protected override List CaseLeaf(MessagePart messagePart) // Maximum space needed is one var leafAnswer = new List(1); - if (messagePart.IsAttachment) + if (!messagePart.IsAttachment) return leafAnswer; + if (messagePart.FileName.ToLowerInvariant() == "winmail.dat") { - if (messagePart.FileName.ToLowerInvariant() == "winmail.dat") + try { - try + Logger.WriteToLog("Found winmail.dat attachment, trying to get attachments from it"); + var stream = StreamHelpers.Manager.GetStream("AttachmentFinder.CaseLeaf", messagePart.Body, 0, messagePart.Body.Length); + using var tnefReader = new TnefReader(stream); { - Logger.WriteToLog("Found winmail.dat attachment, trying to get attachments from it"); - var stream = StreamHelpers.Manager.GetStream("AttachmentFinder.CaseLeaf", messagePart.Body, 0, messagePart.Body.Length); - using var tnefReader = new TnefReader(stream); + var attachments = Part.ExtractAttachments(tnefReader); + var count = attachments.Count; + if (count > 0) { - var attachments = Part.ExtractAttachments(tnefReader); - var count = attachments.Count; - if (count > 0) - { - Logger.WriteToLog($"Found {count} attachment{(count == 1 ? string.Empty : "s")}, removing winmail.dat and adding {(count == 1 ? "this attachment" : "these attachments")}"); - - foreach (var attachment in attachments) - { - var temp = new MessagePart(attachment); - leafAnswer.Add(temp); - } - } + Logger.WriteToLog($"Found {count} attachment{(count == 1 ? string.Empty : "s")}, removing winmail.dat and adding {(count == 1 ? "this attachment" : "these attachments")}"); + leafAnswer.AddRange(attachments.Select(attachment => new MessagePart(attachment))); } } - catch (Exception exception) - { - Logger.WriteToLog($"Could not parse winmail.dat attachment, error: {ExceptionHelpers.GetInnerException(exception)}"); - leafAnswer.Add(messagePart); - } } - else + catch (Exception exception) + { + Logger.WriteToLog($"Could not parse winmail.dat attachment, error: {ExceptionHelpers.GetInnerException(exception)}"); leafAnswer.Add(messagePart); + } } + else + leafAnswer.Add(messagePart); return leafAnswer; }