Update of /cvsroot/csmaild/csmaild/src/Imap/Types In directory sc8-pr-cvs1:/tmp/cvs-serv18360/src/Imap/Types Added Files: BaseType.cs FetchRequest.cs FlagList.cs SearchRequest.cs SequenceSet.cs StatusRequest.cs StoreRequest.cs StringType.cs Log Message: Updated VS.NET 2002 project files to reflect currect VS.NET 2003 project Moved message flags into a FlagList class (this makes flag extensions easier to support) Modified the TestClient for easier debugging of the server Added BASE64 decoder to common library Moved argument parsing from ImapCommand into the newly created types (representing various arguments parsed from the client) Added support for AUTHENTICATE PLAIN Added TODO document in the docs !!Some stuff surely has been broken with all this code moving around, it'll compile and run, but some functionality hasn't been verified yet!! --- NEW FILE: BaseType.cs --- using System; using System.IO; using System.Text; namespace Imap.Types { public abstract class BaseType { public abstract bool Read(TextReader rdr); protected string ReadTil(TextReader rdr, char tilChar, bool includeAndPass) { return ReadTil(rdr, new char[]{tilChar}, includeAndPass); } protected string ReadTil(TextReader rdr, char[] tilChars, bool includeAndPass) { StringBuilder str = new StringBuilder(); while(true) { int intVal = rdr.Peek(); if(intVal == -1) break; char ch = Convert.ToChar(intVal); for(int idx = 0; idx < tilChars.Length; ++idx) { if(ch == tilChars[idx]) { if(includeAndPass) { str.Append(ch); rdr.Read(); } return str.ToString(); } } str.Append(ch); rdr.Read(); } return str.ToString(); } } } --- NEW FILE: FetchRequest.cs --- using System; using System.Collections; using System.Collections.Specialized; using System.IO; namespace Imap.Types { public class FetchRequest : BaseType { #region BodySection public class BodySection { private bool mOffsetLength; private uint mOffset; private uint mLength; private bool mHeader; private bool mText; private StringCollection mHeaderFields = new StringCollection(); private StringCollection mHeaderFieldsNot = new StringCollection(); private ArrayList mParts = new ArrayList(); private bool mPartHeader; private bool mPartMime; private bool mPartText; private StringCollection mPartHeaderFields = new StringCollection(); private StringCollection mPartHeaderFieldsNot = new StringCollection(); public bool OffsetLength { get { return mOffsetLength; } set { mOffsetLength = value; } } public uint Offset { get { return mOffset; } set { mOffset = value; } } public uint Length { get { return mLength; } set { mLength = value; } } public bool Header { get { return mHeader; } set { mHeader = value; } } public bool Text { get { return mText; } set { mText = value; } } public StringCollection HeaderFields { get { return mHeaderFields; } } public StringCollection HeaderFieldsNot { get { return mHeaderFieldsNot; } } public ArrayList Parts { get { return mParts; } } public bool PartHeader { get { return mPartHeader; } set { mPartHeader = value; } } public bool PartMime { get { return mPartMime; } set { mPartMime = value; } } public bool PartText { get { return mPartText; } set { mPartText = value; } } public StringCollection PartHeaderFields { get { return mPartHeaderFields; } } public StringCollection PartHeaderFieldsNot { get { return mPartHeaderFieldsNot; } } } #endregion private bool mBody; private bool mPeeking; private bool mNotPeeking; private bool mBodyStructure; private bool mEnvelope; private bool mFlags; private bool mInternalDate; private bool mRfc822; private bool mRfc822Header; private bool mRfc822Size; private bool mRfc822Text; private bool mUid; private ArrayList mBodySections = new ArrayList(); public bool Body { get { return mBody; } set { mBody = value; } } public bool Peeking { get { return mPeeking; } set { if(!value) { mNotPeeking = true; mPeeking = false; } else if(value && !mNotPeeking) mPeeking = true; } } public bool BodyStructure { get { return mBodyStructure; } set { mBodyStructure = value; } } public bool Envelope { get { return mEnvelope; } set { mEnvelope = value; } } public bool Flags { get { return mFlags; } set { mFlags = value; } } public bool InternalDate { get { return mInternalDate; } set { mInternalDate = value; } } public bool Rfc822 { get { return mRfc822; } set { mRfc822 = value; } } public bool Rfc822Header { get { return mRfc822Header; } set { mRfc822Header = value; } } public bool Rfc822Size { get { return mRfc822Size; } set { mRfc822Size = value; } } public bool Rfc822Text { get { return mRfc822Text; } set { mRfc822Text = value; } } public bool Uid { get { return mUid; } set { mUid = value; } } public ArrayList BodySections { get { return mBodySections; } } public void SetAllMacro() { mFlags = true; mInternalDate = true; mRfc822Size = true; mEnvelope = true; } public void SetFullMacro() { mFlags = true; mInternalDate = true; mRfc822Size = true; mEnvelope = true; mBody = true; } public void SetFastMacro() { mFlags = true; mInternalDate = true; mRfc822Size = true; } public override bool Read(TextReader rdr) { if(Convert.ToChar(rdr.Peek()) == '(') { rdr.Read(); while(true) { if(!ReadItem(rdr)) return false; int intVal = rdr.Read(); if(intVal == -1) return false; char ch = Convert.ToChar(intVal); if(ch == ' ') continue; else if(ch == ')') break; else return false; } rdr.Read(); } else ReadItem(rdr); return true; } private bool ReadItem(TextReader rdr) { string text = ReadTil(rdr, new char[]{' ', ')'}, false).ToUpper(); if(text.StartsWith("BODY[")) { Peeking = false; return ReadBodyStructureItem(text.Substring(5), rdr); } else if(text.StartsWith("BODY.PEEK[")) { Peeking = true; return ReadBodyStructureItem(text.Substring(10), rdr); } switch(text) { case "ALL": SetAllMacro(); break; case "FULL": SetFullMacro(); break; case "FAST": SetFastMacro(); break; case "ENVELOPE": mEnvelope = true; break; case "FLAGS": mFlags = true; break; case "INTERNALDATE": mInternalDate = true; break; case "RFC822.HEADER": mRfc822Header = true; break; case "RFC822.SIZE": mRfc822Size = true; break; case "RFC822.TEXT": mRfc822Text = true; break; case "BODY": mBody = true; break; case "BODYSTRUCTURE": mBodyStructure = true; break; case "UID": mUid = true; break; case "RFC822": mRfc822 = true; break; default: return false; } return true; } private bool ReadBodyStructureItem(string left, TextReader rdr) { /* fetch-att = "BODY" section ["<" number "." nz-number ">"] / "BODY.PEEK" section ["<" number "." nz-number ">"] section = "[" [section-spec] "]" section-spec = section-msgtext / (section-part ["." section-text]) section-msgtext = "HEADER" / "HEADER.FIELDS" [".NOT"] SP header-list / "TEXT" section-text = section-msgtext / "MIME" ; text other than actual body part (headers, etc.) section-part = nz-number *("." nz-number) */ return false; } } } --- NEW FILE: FlagList.cs --- using System; using System.Collections.Specialized; using System.IO; namespace Imap.Types { public class FlagList : BaseType { private Common.FlagList mFlags = new Common.FlagList(); public Common.FlagList Flags { get { return mFlags; } } public static implicit operator Common.FlagList(FlagList m) { return m.mFlags; } public override bool Read(TextReader rdr) { if(Convert.ToChar(rdr.Peek()) == '(') { rdr.Read(); while(true) { if(!ReadFlag(rdr)) return false; int intVal = rdr.Read(); if(intVal == -1) return false; char ch = Convert.ToChar(intVal); if(ch == ' ') continue; else if(ch == ')') break; else return false; } rdr.Read(); } else ReadFlag(rdr); return true; } private bool ReadFlag(TextReader rdr) { int intVal = rdr.Peek(); if(intVal == -1) return false; char ch = Convert.ToChar(intVal); if(ch == '\\') // system { rdr.Read(); string str = StringType.ReadWhileGood(rdr, Commands.CommandPart.AtomChar); switch(str.ToLower()) { case "deleted": mFlags.Deleted = true; break; case "flagged": mFlags.Flagged = true; break; case "answered": mFlags.Answered = true; break; case "seen": mFlags.Seen = true; break; case "draft": mFlags.Draft = true; break; default: return false; } } else // extension { string str = StringType.ReadWhileGood(rdr, Commands.CommandPart.AtomChar); if(str == string.Empty) return false; mFlags.Keywords.Add(str); } return true; } } } --- NEW FILE: SearchRequest.cs --- using System; using System.Collections; namespace Imap.Types { public class SearchRequest : BaseType { private ArrayList mExpressions = new ArrayList(); public override bool Read(System.IO.TextReader rdr) { return false; } } public abstract class SearchExpression { } public abstract class SearchStringExpression : SearchExpression { private string mSearchString; public string SearchString { get { return mSearchString; } set { mSearchString = value; } } } public abstract class SearchDateExpression : SearchExpression { private DateTime mSearchDate; public DateTime SearchDate { get { return mSearchDate.Date; } set { mSearchDate = value; } } } public abstract class SearchNumberExpression : SearchExpression { private int mSearchNumber; public int SearchNumber { get { return mSearchNumber; } set { mSearchNumber = value; } } } public abstract class SearchSequenceSetExpression : SearchExpression { private SequenceSet mSearchSet; public SequenceSet SearchSequenceSet { get { return mSearchSet; } set { mSearchSet = value; } } } public class OrExpression : SearchExpression { private SearchExpression mLeftExpression; private SearchExpression mRightExpression; public SearchExpression LeftExpression { get { return mLeftExpression; } set { mLeftExpression = value; } } public SearchExpression RightExpression { get { return mRightExpression; } set { mRightExpression = value; } } } public class NotExpression : SearchExpression { private SearchExpression mExpression; public SearchExpression Expression { get { return mExpression; } set { mExpression = value; } } } public class AllExpression : SearchExpression { } public class AnsweredExpression : SearchExpression { } public class BccExpression : SearchStringExpression { } public class BeforeExpression : SearchDateExpression { } public class BodyExpression : SearchStringExpression { } public class CcExpression : SearchStringExpression { } public class DeletedExpression : SearchExpression { } public class FlaggedExpression : SearchExpression { } public class FromExpression : SearchStringExpression { } public class KeywordExpression : SearchStringExpression { } public class NewExpression : SearchExpression { } public class OldExpression : SearchExpression { } public class OnExpression : SearchDateExpression { } public class RecentExpression : SearchExpression { } public class SeenExpression : SearchExpression { } public class SequenceNumberExpression : SearchSequenceSetExpression { } public class SinceExpression : SearchDateExpression { } public class SubjectExpression : SearchStringExpression { } public class TextExpression : SearchStringExpression { } public class ToExpression : SearchStringExpression { } public class UnansweredExpression : SearchExpression { } public class UndeletedExpression : SearchExpression { } public class UnflaggedExpression : SearchExpression { } public class UnkeywordExpression : SearchStringExpression { } public class UnseenExpression : SearchExpression { } public class DraftExpression : SearchExpression { } public class HeaderExpression : SearchStringExpression { private string mFieldName; public string FieldName { get { return mFieldName; } set { mFieldName = value; } } } public class LargerExpression : SearchNumberExpression { } public class SentBeforeExpression : SearchDateExpression { } public class SentOnExpression : SearchDateExpression { } public class SentSinceExpression : SearchDateExpression { } public class SmallerExpression : SearchNumberExpression { } public class UidExpression : SearchSequenceSetExpression { } public class UndraftExpression : SearchExpression { } } --- NEW FILE: SequenceSet.cs --- using System; using System.Collections; using System.IO; using System.Text; using System.Text.RegularExpressions; namespace Imap.Types { public class SequenceSet : BaseType, IEnumerable { #region SequenceRange public class SequenceRange : IComparable { private uint mLow; private uint mHigh; public uint Low { get { return mLow; } set { mLow = value; } } public uint High { get { return mHigh; } set { mHigh = value; } } public SequenceRange(uint low, uint high) { mLow = low; mHigh = high; } public override string ToString() { if(mLow == mHigh) return mLow.ToString(); else return mLow.ToString() + ":" + mHigh.ToString(); } public int CompareTo(object obj) { SequenceRange oth = obj as SequenceRange; if(oth == null) return 1; if(mLow < oth.mLow) return -1; else if(mLow > oth.mLow) return 1; else return mHigh.CompareTo(oth.mHigh); } } #endregion #region SequenceSetEnumerator private class SequenceSetEnumerator : IEnumerator { private SequenceSet mSequenceSet; private int mRangeIndex; private uint mCurrentValue; private SequenceRange mCurrentRange; public SequenceSetEnumerator(SequenceSet ss) { mSequenceSet = ss; mRangeIndex = -1; } public object Current { get { return mCurrentValue; } } public bool MoveNext() { if(mRangeIndex == -1) return MoveNextRange(); ++mCurrentValue; if(mCurrentValue > mCurrentRange.High) // moved past the end of this range return MoveNextRange(); return true; } private bool MoveNextRange() { ++mRangeIndex; if(mRangeIndex == mSequenceSet.RangeCount) return false; mCurrentRange = mSequenceSet.Ranges[mRangeIndex]; mCurrentValue = mCurrentRange.Low; return true; } public void Reset() { mRangeIndex = -1; } } #endregion private bool mOptimized = true; private SequenceRange[] mOptimizedRanges; private ArrayList mRanges = new ArrayList(); private uint mMaxValue; public SequenceSet(uint maxValue) { mMaxValue = maxValue; } public void AddRange(uint num) { AddRange(num, num); } public void AddRange(uint start, uint end) { uint low = Math.Min(start, end); uint high = Math.Max(start, end); mRanges.Add(new SequenceRange(low, high)); mOptimized = false; mOptimizedRanges = null; } public void Optimize() { if(mOptimized) return; mOptimized = true; // sort them so we can optimize mRanges.Sort(); for(int oidx = 0; oidx < mRanges.Count - 1; ++oidx) { SequenceRange o = mRanges[oidx] as SequenceRange; int iidx = oidx + 1; for(; iidx < mRanges.Count; ++iidx) { SequenceRange i = mRanges[iidx] as SequenceRange; if(o.High + 1 >= i.Low) // these can be combined o.High = Math.Max(o.High, i.High); else // they cannot break; } --iidx; // at this point, oidx points to the last item that can be combined with the item at iidx if(iidx != oidx) // we have combined ranges mRanges.RemoveRange(iidx + 1, oidx - iidx); // remove them permanently and move on } mOptimizedRanges = (SequenceRange[])mRanges.ToArray(typeof(SequenceRange)); } public int RangeCount { get { return mRanges.Count; } } public SequenceRange[] Ranges { get { Optimize(); return mOptimizedRanges; } } public IEnumerator GetEnumerator() { Optimize(); return new SequenceSetEnumerator(this); } public override string ToString() { Optimize(); StringBuilder str = new StringBuilder(); for(int idx = 0; idx < mRanges.Count; ++idx) { str.Append(mRanges[idx].ToString()); if(idx < mRanges.Count - 1) str.Append(','); } return str.ToString(); } public override bool Read(TextReader rdr) { string raw = ReadTil(rdr, ' ', false); string[] ranges = raw.Split(','); foreach(string range in ranges) { Match m = Regex.Match(range, @"^(?:(?:([1-9][0-9]{0,9})(?::([1-9][0-9]{0,9}|\*))?)|(?:(\*):([1-9][0-9]{0,9})))$"); if(!m.Success) return false; uint startNum = 0; uint endNum = 0; if(m.Groups[1].Success) { startNum = uint.Parse(m.Groups[1].Value); if(m.Groups[2].Success) { if(m.Groups[2].Value == "*") endNum = mMaxValue; else endNum = uint.Parse(m.Groups[2].Value); } else endNum = startNum; } else if(m.Groups[3].Success) { if(m.Groups[3].Value == "*") startNum = mMaxValue; else startNum = uint.Parse(m.Groups[3].Value); endNum = uint.Parse(m.Groups[4].Value); } this.AddRange(startNum, endNum); } return true; } } } --- NEW FILE: StatusRequest.cs --- using Common; using System; using System.Text; namespace Imap.Types { public class StatusRequest : BaseType { private bool mMessages; private bool mRecent; private bool mUidNext; private bool mUidValidity; private bool mUnseen; public bool Messages { get { return mMessages; } set { mMessages = value; } } public bool Recent { get { return mRecent; } set { mRecent = value; } } public bool UidNext { get { return mUidNext; } set { mUidNext = value; } } public bool UidValidity { get { return mUidValidity; } set { mUidValidity = value; } } public bool Unseen { get { return mUnseen; } set { mUnseen = value; } } public string GenerateStatusResponse(Mailbox box) { int done = 0; StringBuilder str = new StringBuilder(); str.Append("("); if(mMessages) str.Append((done++ == 0 ? string.Empty : " ") + box.MessageCount); if(mRecent) str.Append((done++ == 0 ? string.Empty : " ") + box.MessageCount); if(mUidNext) str.Append((done++ == 0 ? string.Empty : " ") + box.MessageCount); if(mUidValidity) str.Append((done++ == 0 ? string.Empty : " ") + box.MessageCount); if(mUnseen) str.Append((done++ == 0 ? string.Empty : " ") + box.MessageCount); str.Append(")"); return str.ToString(); } public override bool Read(System.IO.TextReader rdr) { string raw = ReadTil(rdr, ')', true); if(raw[0] != '(' || raw[raw.Length-1] != ')') return false; string[] parts = raw.Substring(1, raw.Length-2).Split(' '); foreach(string part in parts) { switch(part) { case "MESSAGES": mMessages = true; break; case "RECENT": mRecent = true; break; case "UIDNEXT": mUidNext = true; break; case "UIDVALIDITY": mUidValidity = true; break; case "UNSEEN": mUnseen = true; break; default: return false; } } return true; } } } --- NEW FILE: StoreRequest.cs --- using System; namespace Imap.Types { public class StoreRequest : BaseType { private bool mSilent; private bool mAdd; private bool mRemove; private bool mReplace; public bool Silent { get { return mSilent; } set { mSilent = value; } } public bool Add { get { return mAdd; } set { mAdd = value; } } public bool Remove { get { return mRemove; } set { mRemove = value; } } public bool Replace { get { return mReplace; } set { mReplace = value; } } public override bool Read(System.IO.TextReader rdr) { string str = ReadTil(rdr, ' ', false); if(str.EndsWith(".SILENT")) { mSilent = true; str = str.Substring(0, str.Length - 7); } switch(str) { case "FLAGS": mReplace = true; break; case "+FLAGS": mAdd = true; break; case "-FLAGS": mRemove = true; break; default: return false; } return true; } } } --- NEW FILE: StringType.cs --- using System; using System.IO; using System.Text; namespace Imap.Types { public enum StringTypeEnum { Atom, AtomString, Quoted, Literal, AString, List } public class StringType : BaseType { private StringTypeEnum mType; private string mString; public StringType(StringTypeEnum type) { mType = type; } public static implicit operator string(StringType m) { return m.mString; } public override bool Read(TextReader rdr) { char firstChar = Convert.ToChar(rdr.Peek()); if((mType == StringTypeEnum.Quoted || mType == StringTypeEnum.AString || mType == StringTypeEnum.List) && firstChar == '"') return ReadQuoted(rdr); else if((mType == StringTypeEnum.Literal || mType == StringTypeEnum.AString || mType == StringTypeEnum.List) && firstChar == '{') return ReadLiteral(rdr); else if(mType == StringTypeEnum.AString) { mString = ReadWhileGood(rdr, Imap.Commands.CommandPart.AStringChar); return (mString != string.Empty); } else if(mType == StringTypeEnum.List) { mString = ReadWhileGood(rdr, Imap.Commands.CommandPart.ListChar); return (mString != string.Empty); } else if(mType == StringTypeEnum.Atom) { mString = ReadWhileGood(rdr, Imap.Commands.CommandPart.AtomChar); return (mString != string.Empty); } return false; } private bool ReadLiteral(TextReader rdr) { return false; } internal static string ReadWhileGood(TextReader rdr, string validChars) { StringBuilder str = new StringBuilder(); while(true) { int intVal = rdr.Peek(); if(intVal == -1) break; char ch = Convert.ToChar(intVal); if(validChars.IndexOf(ch) == -1) break; str.Append(ch); rdr.Read(); } return str.ToString(); } private bool ReadQuoted(TextReader rdr) { StringBuilder str = new StringBuilder(); rdr.Read(); // skip quote bool readEscape = false; while(true) { int intVal = rdr.Read(); if(intVal == -1) break; char ch = Convert.ToChar(intVal); if(ch == '"') { if(readEscape) str.Append(ch); else break; } else if(ch == '\\') { if(readEscape) str.Append(ch); else readEscape = true; } else str.Append(ch); } mString = str.ToString(); return true; } } } |