From 017f0f968e23dc11bf894a478c4ea35d5faacfe9 Mon Sep 17 00:00:00 2001 From: MZimmermannR Date: Thu, 19 Jan 2017 12:46:27 +0100 Subject: [PATCH] reduced memory impact --- ActiveUp.Net.sln | 4 +- .../ActiveUp.Net.Common.csproj | 1 + .../ActiveUp.Net.Common/Extensions.cs | 51 +++++++ Class Library/ActiveUp.Net.Common/MimePart.cs | 9 ++ Class Library/ActiveUp.Net.Common/Parser.cs | 127 +++++++----------- .../ActiveUp.Net.Imap4/Imap4Client.cs | 23 ++-- 6 files changed, 124 insertions(+), 91 deletions(-) create mode 100644 Class Library/ActiveUp.Net.Common/Extensions.cs diff --git a/ActiveUp.Net.sln b/ActiveUp.Net.sln index 533b108..35225b8 100644 --- a/ActiveUp.Net.sln +++ b/ActiveUp.Net.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.26020.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Class Library", "Class Library", "{629D406B-F46A-4A9D-A31F-C5956E0AB157}" EndProject diff --git a/Class Library/ActiveUp.Net.Common/ActiveUp.Net.Common.csproj b/Class Library/ActiveUp.Net.Common/ActiveUp.Net.Common.csproj index 3646690..d68f9ce 100644 --- a/Class Library/ActiveUp.Net.Common/ActiveUp.Net.Common.csproj +++ b/Class Library/ActiveUp.Net.Common/ActiveUp.Net.Common.csproj @@ -119,6 +119,7 @@ + diff --git a/Class Library/ActiveUp.Net.Common/Extensions.cs b/Class Library/ActiveUp.Net.Common/Extensions.cs new file mode 100644 index 0000000..29e8ee0 --- /dev/null +++ b/Class Library/ActiveUp.Net.Common/Extensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ActiveUp.Net.Common +{ + /// + /// Some general extension methodes + /// + public static class Extensions + { + /// + /// Searches a array for the occurance of another array + /// + /// + /// the array to search in + /// the array which should be found in the + /// + public static IEnumerable IndexOf(this T[] haystack, T[] needle) + { + if ((needle != null) && (haystack.Length >= needle.Length)) + { + for (int l = 0; l < haystack.Length - needle.Length + 1; l++) + { + if (!needle.Where((data, index) => !haystack[l + index].Equals(data)).Any()) + yield return l; + } + } + } + + /// + /// Converts the byte array to an ASCII encoded string. + /// + /// The byte array to convert + public static string ToASCII(this byte[] data) + { + const int BUFFER_SIZE = 2048; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < data.Length; i += BUFFER_SIZE) + sb.Append(ConvertByteBlock(data, i, Math.Min(BUFFER_SIZE, data.Length - i))); + + return sb.ToString(); + } + + private static string ConvertByteBlock(byte[] data, int start, int length) + { + return Encoding.ASCII.GetString(data, start, length); + } + } +} diff --git a/Class Library/ActiveUp.Net.Common/MimePart.cs b/Class Library/ActiveUp.Net.Common/MimePart.cs index 5ac8f98..827469d 100644 --- a/Class Library/ActiveUp.Net.Common/MimePart.cs +++ b/Class Library/ActiveUp.Net.Common/MimePart.cs @@ -21,6 +21,7 @@ using System.Linq; using System.Collections.Generic; using ActiveUp.Net.Common.Rfc2047; +using ActiveUp.Net.Common; #if !PocketPC using System.Security.Cryptography.Pkcs; #endif @@ -356,6 +357,14 @@ private string Base64EncodeAndWrap() /// public string OriginalContent { get; set; } + /// + /// Decodes the again to + /// + public void DecodeOriginalContent() + { + OriginalContent = BinaryContent.ToASCII(); + } + /// /// The Content-Type of the MimePart. /// diff --git a/Class Library/ActiveUp.Net.Common/Parser.cs b/Class Library/ActiveUp.Net.Common/Parser.cs index 79b31a8..024bc7e 100644 --- a/Class Library/ActiveUp.Net.Common/Parser.cs +++ b/Class Library/ActiveUp.Net.Common/Parser.cs @@ -23,6 +23,8 @@ using System.Text; using ActiveUp.Net.Security; using System.IO; +using ActiveUp.Net.Common; +using System.Linq; namespace ActiveUp.Net.Mail { @@ -133,55 +135,36 @@ private static int GetASCIIByteCountOfPart(string part) /// Parses the sub parts. /// /// The part. - private static void ParseSubParts(ref MimePart part, Message message) + /// The message to add the data + /// removes the original content string from mime parts. To get the string later, use + private static void ParseSubParts(ref MimePart part, Message message, bool compact) { - string boundary = part.ContentType.Parameters["boundary"]; - string parentPartAsciiBody = ToASCII(part.BinaryContent); byte[] parentPartBinary = part.BinaryContent; + byte[] boundary = Encoding.ASCII.GetBytes("--" + part.ContentType.Parameters["boundary"]); + int[] boundaryPositions = parentPartBinary.IndexOf(boundary).ToArray(); + int last = 0; - Logger.AddEntry(typeof(Parser), "boundary : " + boundary); - string[] arrpart = Regex.Split(parentPartAsciiBody, @"\r?\n?" + Regex.Escape("--" + boundary)); - - foreach (var strpart in arrpart) + foreach (int pos in boundaryPositions) { - if (string.IsNullOrWhiteSpace(strpart)) + if (pos == 0) continue; - - int bounaryByteLen = GetASCIIByteCountOfPart(parentPartAsciiBody.Substring(0, parentPartAsciiBody.IndexOf(strpart))); - int binaryPartLen = bounaryByteLen + GetASCIIByteCountOfPart(strpart); - parentPartAsciiBody = null; - //complete Part (incl. boundary) - byte[] binaryPart = new byte[binaryPartLen]; - Array.Copy(parentPartBinary, binaryPart, binaryPart.Length); + byte[] binaryPart = new byte[pos - last]; + Array.Copy(parentPartBinary, last, binaryPart, 0, binaryPart.Length); //Body only (without Boundary) - byte[] binaryBody = new byte[GetASCIIByteCountOfPart(strpart)]; - Array.Copy(binaryPart, bounaryByteLen, binaryBody, 0, binaryBody.Length); - - //Remove Subpart from ParentPart - byte[] tmp = new byte[parentPartBinary.Length - binaryPart.Length]; - Array.Copy(parentPartBinary, binaryPart.Length, tmp, 0, (parentPartBinary.Length - binaryPart.Length)); + int bodyLength = pos - last - (boundary.Length + 2); + if (bodyLength <= 0) + continue; + byte[] binaryBody = new byte[bodyLength]; + Array.Copy(binaryPart, (boundary.Length + 2), binaryBody, 0, binaryBody.Length); - parentPartBinary = null; binaryPart = null; - GC.Collect(GC.MaxGeneration); - GC.WaitForPendingFinalizers(); - - parentPartBinary = tmp; - parentPartAsciiBody = ToASCII(parentPartBinary); - tmp = null; - if (!strpart.StartsWith("--") && !string.IsNullOrEmpty(strpart)) - { - MimePart newpart = ParseMimePart(binaryBody, message); - newpart.Container = part; - part.SubParts.Add(newpart); - } - - binaryBody = null; - GC.Collect(GC.MaxGeneration); - GC.WaitForPendingFinalizers(); + MimePart newpart = ParseMimePart(binaryBody, message, compact); + newpart.Container = part; + part.SubParts.Add(newpart); + last = pos; } } @@ -192,8 +175,8 @@ private static void ParseSubParts(ref MimePart part, Message message) /// The message. private static void DispatchParts(MimePart root, ref Message message) { - foreach (MimePart entity in root.SubParts) - DispatchPart(entity, ref message); + foreach (MimePart part in root.SubParts) + DispatchPart(part, ref message); } /// @@ -314,7 +297,7 @@ private static void DecodePartBody(ref MimePart part) } else if (part.ContentTransferEncoding.Equals(ContentTransferEncoding.QuotedPrintable)) { - part.TextContent = Codec.FromQuotedPrintable(ToASCII(part.BinaryContent), charset); + part.TextContent = Codec.FromQuotedPrintable(part.BinaryContent.ToASCII(), charset); part.BinaryContent = Codec.GetEncoding(charset).GetBytes(part.TextContent); } else @@ -330,7 +313,7 @@ private static void DecodePartBody(ref MimePart part) private static void DecodeBase64Part(MimePart part, string charset) { - string text = ToASCII(part.BinaryContent); + string text = part.BinaryContent.ToASCII(); byte[] binary = null; #if !PocketPC try @@ -345,7 +328,7 @@ private static void DecodeBase64Part(MimePart part, string charset) binary = Convert.FromBase64String(text); } #endif - text = ToASCII(binary); + text = binary.ToASCII(); if (part.ContentDisposition != ContentDisposition.Attachment) text = Codec.GetEncoding(charset).GetString(binary, 0, binary.Length); @@ -451,7 +434,7 @@ public static string Unfold(string input) /// Delegate for body parsed event. /// /// The sender object. - /// The header object. + /// The message object. public delegate void OnBodyParsedEvent(object sender, Message message); /// @@ -459,21 +442,6 @@ public static string Unfold(string input) /// public static event OnBodyParsedEvent BodyParsed; - private static string ToASCII(byte[] data) - { - const int BUFFER_SIZE = 2048; - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < data.Length; i += BUFFER_SIZE) - sb.Append(ConvertByteBlock(data, i, Math.Min(BUFFER_SIZE, data.Length - i))); - - return sb.ToString(); - } - - private static string ConvertByteBlock(byte[] data, int start, int length) - { - return Encoding.ASCII.GetString(data, start, length); - } - private static void ParseHeaderFields(MimePart part, int headerEnd) { string header = Unfold(part.OriginalContent.Substring(0, headerEnd)); @@ -504,12 +472,14 @@ private static void ParseBody(byte[] binaryData, MimePart part, int bodyStart) /// Parses the MIME part. /// /// The data. + /// The message to add the data + /// removes the original content string from mime parts. To get the string later, use /// - public static MimePart ParseMimePart(byte[] binaryData, Message message) + public static MimePart ParseMimePart(byte[] binaryData, Message message, bool compact) { MimePart part = new MimePart(); part.ParentMessage = message; - part.OriginalContent = ToASCII(binaryData); //ASCII content for header parsing + part.OriginalContent = binaryData.ToASCII(); //ASCII content for header parsing try { @@ -532,9 +502,7 @@ public static MimePart ParseMimePart(byte[] binaryData, Message message) // Build the part tree. // This is a container part. if (part.ContentType.Type.ToLower().Equals("multipart")) - { - ParseSubParts(ref part, message); - } + ParseSubParts(ref part, message, compact); // This is a nested message. else if (part.ContentType.Type.ToLower().Equals("message")) { @@ -542,11 +510,11 @@ public static MimePart ParseMimePart(byte[] binaryData, Message message) } // Other types. else - { DecodePartBody(ref part); - } - // Call event id BodyParsed is not null. + if (compact) + part.OriginalContent = null; + BodyParsed?.Invoke(null, message); } } @@ -779,7 +747,7 @@ public static Header ParseHeaderString(string data) /// Delegate for OnErrorParsingEvent. /// /// The sender object. - /// The exception object. + /// The exception object. public delegate void OnErrorParsingEvent(object sender, Exception ex); /// @@ -791,15 +759,16 @@ public static Header ParseHeaderString(string data) /// Parses the message. /// /// The data. + /// removes the original content string from mime parts. To get the string later, use /// - public static Message ParseMessage(byte[] data) + public static Message ParseMessage(byte[] data, bool compact = false) { Message message = new Message(); try { // Build a part tree and get all headers. - MimePart part = ParseMimePart(data, message); + MimePart part = ParseMimePart(data, message, compact); // Fill a new message object with the new information. message.OriginalData = data; @@ -861,8 +830,7 @@ public static Message ParseMessage(byte[] data) } catch (Exception ex) { - if (ErrorParsing != null) - ErrorParsing(null, ex); + ErrorParsing?.Invoke(null, ex); } return message; @@ -872,6 +840,7 @@ public static Message ParseMessage(byte[] data) /// Parses a MemoryStream's content to a Message object. /// /// The MemoryStream containing the Header data to be parsed. + /// removes the original content string from mime parts. To get the string later, use /// The parsed Header as a Message object. /// /// @@ -894,13 +863,13 @@ public static Message ParseMessage(byte[] data) /// var subject:string = message.Subject; /// /// - public static Message ParseMessage(MemoryStream inputStream) + public static Message ParseMessage(MemoryStream inputStream, bool compact = false) { byte[] buf = new byte[inputStream.Length]; inputStream.Read(buf, 0, buf.Length); Message msg = new Message(); msg.OriginalData = buf; - ParseMessage(buf); + ParseMessage(buf, compact); return msg; } @@ -908,6 +877,7 @@ public static Message ParseMessage(MemoryStream inputStream) /// Parses a Message from a string formatted accordingly to the RFC822. /// /// The string containing the message data to be parsed. + /// removes the original content string from mime parts. To get the string later, use /// The parsed message as a Message object. /// /// @@ -930,10 +900,10 @@ public static Message ParseMessage(MemoryStream inputStream) /// var subject:string = message.Subject; /// /// - public static Message ParseMessage(string data) + public static Message ParseMessage(string data, bool compact = false) { #if !PocketPC - return ParseMessage(Encoding.GetEncoding("iso-8859-1").GetBytes(data)); + return ParseMessage(Encoding.GetEncoding("iso-8859-1").GetBytes(data), compact); #else return Parser.ParseMessage(Pop3Client.PPCEncode.GetBytes(data)); #endif @@ -943,6 +913,7 @@ public static Message ParseMessage(string data) /// Parses a message from a file to a Message object. /// /// The path of the file to be parsed. + /// removes the original content string from mime parts. To get the string later, use /// The parsed message as a Message object. /// /// @@ -965,7 +936,7 @@ public static Message ParseMessage(string data) /// var subject:string = message.Subject; /// /// - public static Message ParseMessageFromFile(string filePath) + public static Message ParseMessageFromFile(string filePath, bool compact = false) { byte[] data = null; @@ -974,7 +945,7 @@ public static Message ParseMessageFromFile(string filePath) data = new byte[fs.Length]; ReadStream(fs, data); } - return ParseMessage(data); + return ParseMessage(data, compact); } private static void ReadStream(Stream stream, byte[] data) diff --git a/Class Library/ActiveUp.Net.Imap4/Imap4Client.cs b/Class Library/ActiveUp.Net.Imap4/Imap4Client.cs index 407f536..7656b40 100644 --- a/Class Library/ActiveUp.Net.Imap4/Imap4Client.cs +++ b/Class Library/ActiveUp.Net.Imap4/Imap4Client.cs @@ -1160,19 +1160,20 @@ public byte[] CommandBinary(string command, string stamp, CommandOptions options } } - var bufferString = buffer.ToString(); byte[] bufferBytes = new byte[sr.BaseStream.Length]; sr.BaseStream.Seek(0, SeekOrigin.Begin); sr.BaseStream.Read(bufferBytes, 0, bufferBytes.Length); - if (!sr.CurrentEncoding.Equals(Encoding.UTF8)) - { - var utf8Bytes = Encoding.Convert(sr.CurrentEncoding, Encoding.UTF8, sr.CurrentEncoding.GetBytes(bufferString)); - bufferString = Encoding.UTF8.GetString(utf8Bytes); - } - if (buffer.Length < 200) + { + var bufferString = buffer.ToString(); + if (!sr.CurrentEncoding.Equals(Encoding.UTF8)) + { + var utf8Bytes = Encoding.Convert(sr.CurrentEncoding, Encoding.UTF8, sr.CurrentEncoding.GetBytes(bufferString)); + bufferString = Encoding.UTF8.GetString(utf8Bytes); + } OnTcpRead(new TcpReadEventArgs(bufferString)); + } else OnTcpRead(new TcpReadEventArgs("long data")); if (lastLine.StartsWith(stamp + " OK") || lastLine.ToLower().StartsWith("* " + command.Split(' ')[0].ToLower()) || lastLine.StartsWith("+ ")) @@ -1185,13 +1186,13 @@ public byte[] CommandBinary(string command, string stamp, CommandOptions options private bool CheckResponse(StringBuilder buffer, string command, string lastLineOfPartResponse, ref string lastLine, string stamp) { if (buffer.Length > 100) - lastLineOfPartResponse = buffer.ToString().Substring(buffer.Length - 100).Replace("\r\n", ""); + lastLineOfPartResponse = buffer.ToString(buffer.Length - 100, 100).Replace("\r\n", ""); else lastLineOfPartResponse = buffer.ToString().Replace("\r\n", ""); int stampPos = lastLineOfPartResponse.IndexOf(stamp + " OK"); if (stampPos >= 0) { - if (buffer.ToString().EndsWith("\n") || buffer.ToString().EndsWith("\r")) + if (buffer.ToString(buffer.Length - 100, 100).EndsWith("\n") || buffer.ToString(buffer.Length - 100, 100).EndsWith("\r")) { lastLine = lastLineOfPartResponse.Substring(stampPos); return true; @@ -1200,7 +1201,7 @@ private bool CheckResponse(StringBuilder buffer, string command, string lastLine stampPos = lastLineOfPartResponse.IndexOf(stamp + " NO"); if (stampPos >= 0) { - if (buffer.ToString().EndsWith("\n") || buffer.ToString().EndsWith("\r")) + if (buffer.ToString(buffer.Length - 100, 100).EndsWith("\n") || buffer.ToString(buffer.Length - 100, 100).EndsWith("\r")) { lastLine = lastLineOfPartResponse.Replace(stamp, ""); throw new Imap4Exception("Command \"" + command + "\" failed\r\nServer replied:" + lastLine); @@ -1209,7 +1210,7 @@ private bool CheckResponse(StringBuilder buffer, string command, string lastLine stampPos = lastLineOfPartResponse.IndexOf(stamp + " BAD"); if (stampPos >= 0) { - if (buffer.ToString().EndsWith("\n") || buffer.ToString().EndsWith("\r")) + if (buffer.ToString(buffer.Length - 100, 100).EndsWith("\n") || buffer.ToString(buffer.Length - 100, 100).EndsWith("\r")) { lastLine = lastLineOfPartResponse.Replace(stamp, ""); throw new Imap4Exception("Command \"" + command + "\" failed\r\nServer replied:" + lastLine);