public void DecodeMessageLines( ) { if (AcCommon.StringValue(Properties.ContentTransferEncoding) == "quoted-printable") { mRawMessageLines = mMessageLines; mMessageLines = QuotedPrintable.DecodeLines(mRawMessageLines); } }
/// <summary> /// Is the position in the string the start of an encoded-word /// </summary> /// <param name="InString">String to examine</param> /// <param name="InBx">Position in string</param> /// <returns></returns> public static bool IsStartOfEncodedWord(string InString, int InBx) { bool isEncodedWord = true; if (AcCommon.PullString(InString, InBx, 2, null) != "=?") { isEncodedWord = false; } else { isEncodedWord = CrackEncodedWord(InString, InBx).a; } return(isEncodedWord); }
/// <summary> /// Add an array of strings as a value to the comma sep value string. ( the array /// is its converted to CsvString form and enclosed in parenthesis before being added /// to the string. /// </summary> /// <param name="InValue"></param> /// <returns></returns> public CsvString Add(string[] InValue) { if (InValue == null) { string Value = null; AddString(Value); } else { StringBuilder sb = new StringBuilder( ); sb.Append("_sa("); sb.Append(AcCommon.ToCsvString(InValue)); sb.Append(")"); AddString(sb.ToString( )); } return(this); }
// --------------------------- Dequote ------------------------------ public static string Dequote(string InText, QuoteEncapsulation InQem) { int Lx = InText.Length + 2; // 2=quote chars. char QuoteChar = InText[0]; StringBuilder sb = new StringBuilder(Lx); int Ix = 0; int EndIx = InText.Length - 2; while (true) { ++Ix; if (Ix > EndIx) { break; } char ch1 = InText[Ix]; // using the escape method to enclose quote chars. This means the escape char // is used to encapsulate other special characters in the quoted string. // todo: dequote using "QuotedStringTraits" rules. if ((ch1 == '\\') && (InQem == QuoteEncapsulation.Escape) && (Ix < (EndIx - 1))) { sb.Append(MaterializeEscapeChar(InText, Ix)); ++Ix; } // quote char enquoted using the "double the char" method. else if ((ch1 == QuoteChar) && (InQem == QuoteEncapsulation.Double) && (AcCommon.PullChar(InText, Ix + 1) == QuoteChar)) { sb.Append(ch1); ++Ix; } // any other character. append to result string. else { sb.Append(ch1); } } return(sb.ToString( )); }
// ------------------- CalcMessageShowCodes --------------------------- // return the message string in a form that shows the cr, lf, and tab codes private string CalcMessageShowCodes( ) { string msg = Message; StringBuilder sb = new StringBuilder(msg.Length); for (int Ix = 0; Ix < msg.Length; ++Ix) { char ch1 = msg[Ix]; if (ch1 == '\t') { sb.Append("<TAB>"); } // a crlf followed by a space in a message header is a FOLD. mail agents // interpret folds as whitespace. else if ((ch1 == '\r') && (AcCommon.PullString(msg, Ix, 3, null) == "\r\n ")) { sb.Append("<FOLD>"); Ix += 2; } else if ((ch1 == '\r') && (AcCommon.PullString(msg, Ix, 2, null) == NetworkConstants.CrLf)) { sb.Append("<CRLF>"); ++Ix; } else if (ch1 == '\r') { sb.Append("<CR>"); } else if (ch1 == '\n') { sb.Append("<LF>"); } else { sb.Append(ch1); } } return(sb.ToString( )); }
// ------------------------ ScanCloseParen ------------------------- public int ScanCloseParen(int InBx) { char OpenParenChar = mCmds[InBx]; char CloseParenChar = AcCommon.CalcCloseBraceChar(mCmds[InBx]); int Ix = InBx, Fx = 0; int Lx = mCmds.Length; int ParenLevel = 1; while (true) { ++Ix; if (Ix >= Lx) { throw(new TextScanException( "ScanCloseParen", "Close paren not found.")); } char ch1 = mCmds[Ix]; if (ch1 == OpenParenChar) { ++ParenLevel; } else if (ch1 == CloseParenChar) { --ParenLevel; if (ParenLevel == 0) { break; } } else if (IsOpenParenChar(Ix) == true) { Fx = ScanCloseParen(Ix); Ix = Fx; } else if (IsOpenQuoteChar(Ix) == true) { Fx = ScanCloseQuote(Ix); Ix = Fx; } } return(Ix); }
// ------------------------ ScanCloseBrace ------------------------- // todo: pass TextTraits to a version of this method. Use recursion for each brace // char found. /// <summary> /// scan for closing brace char that matches the start at open brace char. /// </summary> /// <param name="InString"></param> /// <param name="InBx">scan start position</param> /// <param name="InEx">end position in string</param> /// <param name="InQem"></param> /// <returns></returns> public static int ScanCloseBrace( string InString, int InBx, int InEx, QuoteEncapsulation InQem) { char openBraceChar = InString[InBx]; char closeBraceChar = AcCommon.CalcCloseBraceChar(openBraceChar); int Ix = InBx, Fx = 0; int ParenLevel = 1; if (InEx >= InString.Length) { throw new ApplicationException("ScanCloseBrace end pos exceeds string length"); } while (true) { ++Ix; if (Ix > InEx) { Ix = -1; break; } char ch1 = InString[Ix]; if (ch1 == openBraceChar) { ++ParenLevel; } else if (ch1 == closeBraceChar) { --ParenLevel; if (ParenLevel == 0) { break; } } else if (IsOpenQuoteChar(ch1) == true) { Fx = ScanCloseQuote(InString, Ix, InQem); Ix = Fx; } } return(Ix); }
// ------------------------ ScanCloseBrace ------------------------- // todo: pass TextTraits to a version of this method. Use recursion for each brace // char found. public static int ScanCloseBrace( string InString, int InBx, QuoteEncapsulation InQem) { char openBraceChar = InString[InBx]; char closeBraceChar = AcCommon.CalcCloseBraceChar(openBraceChar); int Ix = InBx, Fx = 0; int Lx = InString.Length; int ParenLevel = 1; while (true) { ++Ix; if (Ix >= Lx) { throw(new TextException( "ScanCloseBrace", "Close brace not found.")); } char ch1 = InString[Ix]; if (ch1 == openBraceChar) { ++ParenLevel; } else if (ch1 == closeBraceChar) { --ParenLevel; if (ParenLevel == 0) { break; } } else if (IsOpenQuoteChar(ch1) == true) { Fx = ScanCloseQuote(InString, Ix, InQem); Ix = Fx; } } return(Ix); }
public MimeMessagePart AddNewPart( string InStream, MimeMessagePart.PartInProgress InPip) { MimeMessagePart part = null; if (InPip.PartCode == MimePartCode.Top) { part = new MimeTopPart( ); } else { part = new MimeMessagePart( ); } part.PartCode = InPip.PartCode; base.Add(part); // store the property lines of the part. if (InPip.PropBx != -1) { part.LoadPropertyLines(InStream, InPip.PropBx, InPip.PropLineCx); } // store the message lines of the part. if (InPip.MessageBx != -1) { part.LoadMessageLines(InStream, InPip.MessageBx, InPip.MessageLineCx); } // the message lines are quoted-printable encoded. decode here. if (AcCommon.StringValue(part.Properties.ContentTransferEncoding) == "quoted-printable") { part.DecodeMessageLines( ); } return(part); }
// --------------------------- ScanCloseQuote ------------------------------ public static int ScanCloseQuote( string InString, int InBx, QuoteEncapsulation InQem) { char QuoteChar = InString[InBx]; int cloqIx = -1; for (int Ix = InBx + 1; Ix < InString.Length; ++Ix) { char ch1 = InString[Ix]; // using the escape method to enclose quote chars. This means the escape char // is used to encapsulate other special characters in the quoted string. // todo: dequote using "QuotedStringTraits" rules. if ((ch1 == '\\') && (InQem == QuoteEncapsulation.Escape) && (Ix < (InString.Length - 1))) { ++Ix; } // quote char enquoted using the "double the char" method. else if ((ch1 == QuoteChar) && (InQem == QuoteEncapsulation.Double) && (AcCommon.PullChar(InString, Ix + 1) == QuoteChar)) { ++Ix; } // found the closing quote char. else if (ch1 == QuoteChar) { cloqIx = Ix; break; } } return(cloqIx); }
// ----------------------- MaterializeEscapeChar --------------------- // used by the Dequote method. unpacks the standard escape sequences used in // quoted strings. // returns an int/char pair holding the length of the escape sequence and the // materialized character value. private static IntCharPair MaterializeEscapeChar(string InString, int InIx) { char nx = AcCommon.PullCharArray(InString, InIx, 2)[1]; if (nx == 't') { return(new IntCharPair(2, '\t')); } else if (nx == 'r') { return(new IntCharPair(2, '\r')); } else if (nx == 'n') { return(new IntCharPair(2, '\n')); } else if (nx == '\'') { return(new IntCharPair(2, '\'')); } else if (nx == '\\') { return(new IntCharPair(2, '\\')); } else if (nx == '"') { return(new IntCharPair(2, '"')); } else if (nx == '0') { return(new IntCharPair(2, '\0')); } else { throw(new ApplicationException("Unexpected escape sequence starting at " + "position " + InIx + " in string: " + InString)); } }
// -------------------------- CalcEncodedByteBounds ------------------------ // An encoded byte can either be in form "=xx" or a single literal byte character. // The bounds of the encoded byte are either the single char, or run from the // "=" character to the 2nd octet external form character. // This method is passed and encoded string and a position in that string. It returns, // by reference, the bounds of the encoded byte at that position. private static void CalcEncodedByteBounds( ref int OutBx, ref int OutEx, string InEncodedChars, int InIx) { // isolate chars prior to InIx. char[] chars = AcCommon.PullCharArray(InEncodedChars, InIx - 2, 3); // find the begin position of the encoded byte. if (chars[2] == '=') { OutBx = InIx; } else if (chars[1] == '=') { OutBx = InIx - 1; } else if (chars[0] == '=') { OutBx = InIx - 2; } else { OutBx = InIx; } // calc the end position of the encoded byte. if (InEncodedChars[OutBx] == '=') { OutEx = OutBx + 2; } else { OutEx = OutBx; } }
/// <summary> /// Decode the quoted-printable encoded string. /// Note: use DecodeLines to decode lines of text that include the QP line /// continuation character ( "=" ). /// </summary> /// <param name="InString"></param> /// <returns></returns> public static string DecodeString(string InString) { StringBuilder sb = new StringBuilder(InString.Length); for (int Ix = 0; Ix < InString.Length; ++Ix) { char ch1 = InString[Ix]; if (ch1 != '=') { sb.Append(ch1); } else { string hex = AcCommon.PullString(InString, Ix + 1, 2, " "); // note: not an error if a lone "=" in a QP encoded string. if (AcCommon.IsHexExternalForm(hex) == false) { sb.Append(ch1); } else { // sb.Append( (char) Convert.ToInt32( hex, 16 )) ; // Ix += 2 ; byte singleByte = System.Convert.ToByte(hex, 16); byte[] bytes = AcCommon.ToByteArray(singleByte); string hexChar = System.Text.Encoding.ASCII.GetString(bytes); sb.Append(hexChar); Ix += 2; } } } return(sb.ToString( )); }
// -------------------- ScanWord_IsolateDelim --------------------------- private static void ScanWord_IsolateDelim( string InString, int InBx, ref WordCursor InOutResults, TextTraits InTraits) { int Bx, Lx; string delim; // setup the start of the delim. if (InOutResults.WordBx == -1) { Bx = InBx; } else { Bx = InOutResults.WordEx + 1; } // word went to the end of the string. if (Bx >= InString.Length) { Bx = -1; } // we have a delimiter of some kind. if (Bx != -1) { InOutResults.DelimIsWhitespace = false; // the delim is a hard delim ( not whitespace ) char ch1 = InString[Bx]; if (AcCommon.Contains(InTraits.WhitespaceChars, ch1) == false) { Lx = 1; delim = InString.Substring(Bx, Lx); InOutResults.SetDelim(delim, Bx); } // is a soft delim ( whitespace ). Look for hard delim after the ws. else { ScanCharResults scanResults = ScanNotEqual(InString, Bx, InTraits.WhitespaceChars); if ((scanResults.ResultPos != -1) && (AcCommon.Contains(InTraits.DelimChars, scanResults.ResultChar))) { Lx = 1; delim = AcCommon.CharToString(scanResults.ResultChar); InOutResults.SetDelim(delim, scanResults.ResultPos); } // the whitespace char is the delim of record. else { Lx = 1; delim = InString.Substring(Bx, Lx); InOutResults.SetDelim(delim, Bx); InOutResults.DelimIsWhitespace = true; } } } }
// ------------------------ RequiresEncoding ------------------------- // evaluate if the string requires encoding according to the QuotedPrintableTraits. public static bool RequiresEncoding(string InValue, QuotedPrintableTraits InTraits) { bool requiresEncoding = false; char[] newLineChars = Environment.NewLine.ToCharArray( ); // encode always. if (InTraits.EncodeAlways == true) { requiresEncoding = true; } // length of string exceeds the "RequiresEncodingTriggerLength" else if ((InTraits.RequiresEncodingTriggerLength != -1) && (InValue.Length > InTraits.RequiresEncodingTriggerLength)) { requiresEncoding = true; } // loop for each character in the string. Test each to determine if the // string requires Quoted-Printable encoding. for (int Ix = 0; Ix < InValue.Length; ++Ix) { if (requiresEncoding == true) { break; } char ch1 = InValue[Ix]; // one of the "other" chars to encode. if ((InTraits.OtherEncodeChars != null) && (InTraits.OtherEncodeChars.IndexOf(ch1) != -1)) { requiresEncoding = true; } // space or tab. encoding depends on if followed by crlf or not. else if ((ch1 == 9) || (ch1 == 32)) { char[] nxChars = AcCommon.PullCharArray(InValue, Ix + 1, newLineChars.Length); if ((InTraits.LinebreakTreatment == LinebreakTreatment.Break) && (AcCommon.CompareEqual(nxChars, newLineChars) == true)) { requiresEncoding = true; } } // LineBreak sequence handled as a line break. else if ((ch1 == newLineChars[0]) && (AcCommon.CompareEqual(newLineChars, AcCommon.PullCharArray(InValue, Ix, newLineChars.Length)) == true)) { if (InTraits.LinebreakTreatment == LinebreakTreatment.Encode) { requiresEncoding = true; } } // a basic ascii char. literal representation. else if (((ch1 >= 33) && (ch1 <= 60)) || ((ch1 >= 62) && (ch1 <= 126))) { } // an equal sign. by itself, does not trigger QP encoding. else if (ch1 == '=') { } // an encode required character. else { requiresEncoding = true; } } return(requiresEncoding); }
// ------------------------- EncodeChars ----------------------------------- // Quoted-Printable encode the chars of a string without regard for the line // length maximum. private static string EncodeChars( string InValue, QuotedPrintableTraits InTraits) { char[] newLineChars = Environment.NewLine.ToCharArray( ); StringBuilder sb = new StringBuilder(InValue.Length * 2); // first pass. encode one char at a time, without regard to 76 char line // limit. for (int Ix = 0; Ix < InValue.Length; ++Ix) { char ch1 = InValue[Ix]; // one of the "other" chars to encode. if ((InTraits.OtherEncodeChars != null) && (InTraits.OtherEncodeChars.IndexOf(ch1) != -1)) { sb.Append(EncodeChar(InTraits, ch1)); } // space or tab. encoding depends on if followed by crlf or not. else if ((ch1 == 9) || (ch1 == 32)) { char[] nxChars = AcCommon.PullCharArray(InValue, Ix + 1, newLineChars.Length); if ((InTraits.LinebreakTreatment == LinebreakTreatment.Break) && (AcCommon.CompareEqual(nxChars, newLineChars) == true)) { sb.Append(EncodeChar(InTraits, ch1)); } else { sb.Append(ch1); } } // Linebreak sequence handled as a line break. else if ((ch1 == newLineChars[0]) && (InTraits.LinebreakTreatment == LinebreakTreatment.Break) && (AcCommon.CompareEqual( newLineChars, AcCommon.PullCharArray(InValue, Ix, newLineChars.Length)) == true)) { sb.Append("\r\n"); } // a basic ascii char. literal representation. else if (((ch1 >= 33) && (ch1 <= 60)) || ((ch1 >= 62) && (ch1 <= 126))) { sb.Append(ch1); } else { sb.Append(EncodeChar(InTraits, ch1)); } } return(sb.ToString( )); }
// -------------------- ScanWord_IsolateWord --------------------------- private static void ScanWord_IsolateWord( string InString, int InBx, ref WordCursor InOutResults, TextTraits InTraits) { int Bx, Fx, Ix, Lx; string word; Bx = InBx; char ch1 = InString[Bx]; // is quoted. the word runs to the closing quote. if (IsOpenQuoteChar(ch1) == true) { Ix = ScanCloseQuote(InString, Bx, InTraits.QuoteEncapsulation); if (Ix == -1) { throw(new ApplicationException("Closing quote not found starting at position " + Bx + " in " + InString)); } Lx = Ix - Bx + 1; word = InString.Substring(Bx, Lx); InOutResults.SetWord(word, WordClassification.Quoted, Bx); return; } // look for a brace or delim character. char[] combo = AcCommon.Concat(InTraits.DelimChars, InTraits.BraceChars); ScanCharResults results = ScanEqual(InString, Bx, combo); Fx = results.ResultPos; ch1 = results.ResultChar; // found a brace char if ((InTraits.IsOpenBraceChar(ch1) == true) && (InTraits.IsDelimChar(ch1) == false)) { Ix = ScanCloseBrace(InString, Fx); if (Ix == -1) { throw(new ApplicationException("Closing brace not found starting at position " + Fx + " in " + InString)); } Lx = Ix - Bx + 1; word = InString.Substring(Bx, Lx); if (Bx == Fx) { InOutResults.SetWord(word, WordClassification.Braced, Bx); } else { InOutResults.SetWord(word, WordClassification.NameBraced, Bx, ch1); } } // no delim found. all word to the end of the string. else if (Fx == -1) { word = InString.Substring(Bx); InOutResults.SetWord(word, WordClassification.Name, Bx); } // delim is same position as the word. so there is no word, only a delim. else if (Fx == Bx) { InOutResults.SetNullWord( ); } // we have a word that ends with a delim. else { Lx = Fx - Bx; word = InString.Substring(Bx, Lx); InOutResults.SetWord(word, WordClassification.Name, Bx); } }
// ------------------------------- LoadLines ------------------------------- /// <summary> /// load the lines of the part from an array of all the lines of the message. /// </summary> /// <param name="InLines"></param> /// <param name="InLineBx"></param> /// <param name="InLineCx"></param> /// <returns>reference to this object</returns> public MimeMessagePart LoadLines(string[] InLines, int InLineBx, int InLineCx) { int lineBx = InLineBx; int lineCx = InLineCx; string line = null; // first thing. if this is the top part, check for and trim off the initial // "+OK" response line from the server. ( server sends this line, followed immed // by the message data. this is the first opportunity to strip it out. ) if ((PartCode == MimePartCode.Top) && (lineCx > 0) && (InLines[0].Length >= 3) && (InLines[0].Substring(0, 3) == "+OK")) { ++lineBx; --lineCx; } // calc the number of property lines, then calc the number of message lines. int propLineCx = CountPropertyLines(InLines, lineBx, lineCx); int msgLineCx = lineCx - propLineCx; int msgLineBx = lineBx + propLineCx; // store the part property lines. mPropertyLines = new string[propLineCx]; for (int Ix = 0; Ix < propLineCx; ++Ix) { mPropertyLines[Ix] = InLines[Ix + lineBx]; } // load the property dictionary from the property lines. LoadPropertyDictionary( ); // reduce the message line bounds by one in case the first line is blank. if (msgLineCx > 0) { line = InLines[msgLineBx]; if (line == "") { msgLineCx -= 1; msgLineBx += 1; } } // store the message lines of the part. mMessageLines = new string[msgLineCx]; for (int Ix = 0; Ix < msgLineCx; ++Ix) { mMessageLines[Ix] = InLines[Ix + msgLineBx]; } // the message lines are either quoted-printable or base64 encoded. // Decode the message lines. if (AcCommon.StringValue(Properties.ContentTransferEncoding) == "quoted-printable") { mRawMessageLines = mMessageLines; mMessageLines = QuotedPrintable.DecodeLines(mRawMessageLines); } return(this); }
/// <summary> /// Examine the string for its WordClassification content. /// </summary> /// <param name="InWord"></param> /// <param name="InTraits"></param> /// <returns></returns> public static CharObjectPair CalcWordClassification( string InWord, TextTraits InTraits) { int Fx = 0, Ix = 0; WordClassification wc = WordClassification.None; char braceChar = ' '; char ch1 = AcCommon.PullChar(InWord, 0); int Ex = InWord.Length - 1; // is quoted. the word runs to the closing quote. if (Scanner.IsOpenQuoteChar(ch1) == true) { Ix = Scanner.ScanCloseQuote(InWord, 0, InTraits.QuoteEncapsulation); if (Ix == Ex) { wc = WordClassification.Quoted; } else { wc = WordClassification.MixedText; } } // check if the string is a Braced or NameBraced word. if (wc == WordClassification.None) { char[] combo = AcCommon.Concat(InTraits.DelimChars, InTraits.BraceChars); Scanner.ScanCharResults results = Scanner.ScanEqual(InWord, 0, combo); Fx = results.ResultPos; ch1 = results.ResultChar; // found a brace char if ((InTraits.IsOpenBraceChar(ch1) == true) && (InTraits.IsDelimChar(ch1) == false)) { Ix = Scanner.ScanCloseBrace(InWord, Fx); if (Ix == Ex) { braceChar = ch1; if (Fx == 0) { wc = WordClassification.Braced; } else { wc = WordClassification.NameBraced; } } } } // word is all delimeter. if (wc == WordClassification.None) { Fx = Scanner.ScanNotEqual(InWord, 0, InTraits.DelimChars).a; if (Fx >= 0) { wc = WordClassification.Delimeter; } } // check if a numeric string. if (wc == WordClassification.None) { char[] digitChars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '-' }; Fx = Scanner.ScanNotEqual(InWord, 0, digitChars).a; if (Fx == -1) { wc = WordClassification.Numeric; try { double vx = double.Parse(InWord); } catch (Exception) { wc = WordClassification.None; } } } // any delim chars in the string. if not, the string is a name. otherwise, it is // mixed. if (wc == WordClassification.None) { Fx = Scanner.ScanEqual(InWord, 0, InTraits.DelimChars).a; if (Fx == -1) { wc = WordClassification.Name; } else { wc = WordClassification.MixedText; } } return(new CharObjectPair(braceChar, wc)); }
/// <summary> /// Crack the component parts of the encoded-word string starting at InBx /// in the string. Return an object pair. pair.a is a bool set to false if the /// encoded-word string is not correctly formed. pair.b is a MimeEncodedWord object /// containing the component parts of the cracked word. /// </summary> /// <param name="InString"></param> /// <param name="InBx"></param> /// <returns></returns> public static BoolObjectPair CrackEncodedWord(string InString, int InBx) { int Fx, Ix, Lx, RemLx; string ws = null; MimeEncodedWord ew = new MimeEncodedWord( ); bool isEncodedWord = true; try { // isolate the next 80 chars from the string as a workspace ( encoded words are // limited to 75 chars or less ) ew.Bx = InBx; ws = AcCommon.PullString(InString, InBx, 80, null).ToLower( ); Ix = 0; // isolate the charset name Ix = 2; if (isEncodedWord == true) { RemLx = ws.Length - Ix; if (RemLx <= 3) { isEncodedWord = false; } else { Fx = ws.IndexOf("?q?", Ix); if (Fx == -1) { isEncodedWord = false; } else { Lx = Fx - Ix; ew.CharSet = InString.Substring(InBx + Ix, Lx); Ix = Fx + 3; } } } // quoted-printable encoded text runs until "?=" if (isEncodedWord == true) { RemLx = ws.Length - Ix; if (RemLx <= 2) { isEncodedWord = false; } else { Fx = ws.IndexOf("?=", Ix); if (Fx == -1) { isEncodedWord = false; } else { Lx = Fx - Ix; string qpEncoded = InString.Substring(InBx + Ix, Lx); ew.DecodedValue = QuotedPrintable.DecodeString(qpEncoded); ew.Lx = Fx + 2; } } } } catch (Exception) { isEncodedWord = false; } return(new BoolObjectPair(isEncodedWord, ew)); }
/// <summary> /// standard set of delimeter characters. /// </summary> /// <returns></returns> public static char[] StandardDelimChars( ) { char[] ch3 = new char[] { ' ', '\t', ',' }; return(AcCommon.Concat(ch3, StandardBraceChars( ))); }