void FetchStream (ImapEngine engine, ImapCommand ic, int index) { var token = engine.ReadToken (ic.CancellationToken); var labels = new MessageLabelsChangedEventArgs (index); var flags = new MessageFlagsChangedEventArgs (index); var ctx = (FetchStreamContext) ic.UserData; var section = new StringBuilder (); bool labelsChanged = false; bool flagsChanged = false; var buf = new byte[4096]; long nread = 0, size = 0; UniqueId? uid = null; Stream stream; int n; if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen || token.Type == ImapTokenType.Eoln) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); var atom = (string) token.Value; int offset = 0, length; ulong modseq; uint value; switch (atom) { case "BODY": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenBracket) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); section.Clear (); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseBracket) break; if (token.Type == ImapTokenType.OpenParen) { section.Append (" ("); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen) break; // the header field names will generally be atoms or qstrings but may also be literals switch (token.Type) { case ImapTokenType.Literal: section.Append (engine.ReadLiteral (ic.CancellationToken)); section.Append (' '); break; case ImapTokenType.QString: case ImapTokenType.Atom: section.Append ((string) token.Value); break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } } while (true); if (section[section.Length - 1] == ' ') section.Length--; section.Append (')'); } else if (token.Type != ImapTokenType.Atom) { throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } else { section.Append ((string) token.Value); } } while (true); if (token.Type != ImapTokenType.CloseBracket) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.Atom) { // this might be a region ("<###>") var expr = (string) token.Value; if (expr.Length > 2 && expr[0] == '<' && expr[expr.Length - 1] == '>') { var region = expr.Substring (1, expr.Length - 2); int.TryParse (region, out offset); token = engine.ReadToken (ic.CancellationToken); } } switch (token.Type) { case ImapTokenType.Literal: length = (int) token.Value; size += length; stream = CreateStream (uid, section.ToString (), offset, length); try { while ((n = engine.Stream.Read (buf, 0, buf.Length, ic.CancellationToken)) > 0) { stream.Write (buf, 0, n); nread += n; ctx.Report (nread, size); } stream.Position = 0; } catch { stream.Dispose (); throw; } break; case ImapTokenType.QString: case ImapTokenType.Atom: var buffer = Encoding.UTF8.GetBytes ((string) token.Value); length = buffer.Length; nread += length; size += length; stream = CreateStream (uid, section.ToString (), offset, length); try { stream.Write (buffer, 0, length); ctx.Report (nread, size); stream.Position = 0; } catch { stream.Dispose (); throw; } break; case ImapTokenType.Nil: stream = CreateStream (uid, section.ToString (), offset, 0); break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } if (uid.HasValue) ctx.Sections[section.ToString ()] = CommitStream (stream, uid.Value); else ctx.Sections[section.ToString ()] = stream; break; case "UID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out value) || value == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); uid = new UniqueId (UidValidity, value); foreach (var key in ctx.Sections.Keys.ToArray ()) ctx.Sections[key] = CommitStream (ctx.Sections[key], uid.Value); labels.UniqueId = uid.Value; flags.UniqueId = uid.Value; break; case "MODSEQ": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out modseq)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); labels.ModSeq = modseq; flags.ModSeq = modseq; break; case "FLAGS": // even though we didn't request this piece of information, the IMAP server // may send it if another client has recently modified the message flags. flags.Flags = ImapUtils.ParseFlagsList (engine, atom, flags.UserFlags, ic.CancellationToken); flagsChanged = true; break; case "X-GM-LABELS": // even though we didn't request this piece of information, the IMAP server // may send it if another client has recently modified the message labels. labels.Labels = ImapUtils.ParseLabelsList (engine, ic.CancellationToken); labelsChanged = true; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); } } while (true); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); if (flagsChanged) ic.Folder.OnMessageFlagsChanged (flags); if (labelsChanged) ic.Folder.OnMessageLabelsChanged (labels); }
static string ReadStringToken (ImapEngine engine, string format, CancellationToken cancellationToken) { var token = engine.ReadToken (cancellationToken); switch (token.Type) { case ImapTokenType.Literal: return engine.ReadLiteral (cancellationToken); case ImapTokenType.QString: return (string) token.Value; case ImapTokenType.Atom: return (string) token.Value; default: throw ImapEngine.UnexpectedToken (format, token); } }
void FetchSummaryItems (ImapEngine engine, ImapCommand ic, int index) { var token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); var ctx = (FetchSummaryContext) ic.UserData; IMessageSummary isummary; MessageSummary summary; if (!ctx.Results.TryGetValue (index, out isummary)) { summary = new MessageSummary (index); ctx.Results.Add (index, summary); } else { summary = (MessageSummary) isummary; } do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen || token.Type == ImapTokenType.Eoln) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); var atom = (string) token.Value; string format; ulong value64; uint value; int idx; switch (atom) { case "INTERNALDATE": token = engine.ReadToken (ic.CancellationToken); switch (token.Type) { case ImapTokenType.QString: case ImapTokenType.Atom: summary.InternalDate = ImapUtils.ParseInternalDate ((string) token.Value); break; case ImapTokenType.Nil: summary.InternalDate = null; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } summary.Fields |= MessageSummaryItems.InternalDate; break; case "RFC822.SIZE": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out value)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.Fields |= MessageSummaryItems.MessageSize; summary.Size = value; break; case "BODYSTRUCTURE": format = string.Format (ImapEngine.GenericItemSyntaxErrorFormat, "BODYSTRUCTURE", "{0}"); summary.Body = ImapUtils.ParseBody (engine, format, string.Empty, ic.CancellationToken); summary.Fields |= MessageSummaryItems.BodyStructure; break; case "BODY": token = engine.PeekToken (ic.CancellationToken); if (token.Type == ImapTokenType.OpenBracket) { // consume the '[' token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenBracket) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); // References and/or other headers were requested... do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseBracket) break; if (token.Type == ImapTokenType.OpenParen) { do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen) break; // the header field names will generally be atoms or qstrings but may also be literals switch (token.Type) { case ImapTokenType.Literal: engine.ReadLiteral (ic.CancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } } while (true); } else if (token.Type != ImapTokenType.Atom) { throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); } } while (true); if (token.Type != ImapTokenType.CloseBracket) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Literal) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.References = new MessageIdList (); try { summary.Headers = engine.ParseHeaders (engine.Stream, ic.CancellationToken); } catch (FormatException) { // consume any remaining literal data... ReadLiteralData (engine, ic.CancellationToken); summary.Headers = new HeaderList (); } if ((idx = summary.Headers.IndexOf (HeaderId.References)) != -1) { var references = summary.Headers[idx]; var rawValue = references.RawValue; foreach (var msgid in MimeUtils.EnumerateReferences (rawValue, 0, rawValue.Length)) summary.References.Add (msgid); } summary.Fields |= MessageSummaryItems.References; } else { summary.Fields |= MessageSummaryItems.Body; try { format = string.Format (ImapEngine.GenericItemSyntaxErrorFormat, "BODY", "{0}"); summary.Body = ImapUtils.ParseBody (engine, format, string.Empty, ic.CancellationToken); } catch (ImapProtocolException ex) { if (!ex.UnexpectedToken) throw; // Note: GMail's IMAP implementation sometimes replies with completely broken BODY values // (see issue #32 for the `BODY ("ALTERNATIVE")` example), so to work around this nonsense, // we need to drop the remainder of this line. do { token = engine.PeekToken (ic.CancellationToken); if (token.Type == ImapTokenType.Eoln) break; token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.Literal) ReadLiteralData (engine, ic.CancellationToken); } while (true); return; } } break; case "ENVELOPE": summary.Envelope = ImapUtils.ParseEnvelope (engine, ic.CancellationToken); summary.Fields |= MessageSummaryItems.Envelope; break; case "FLAGS": summary.Flags = ImapUtils.ParseFlagsList (engine, atom, summary.UserFlags, ic.CancellationToken); summary.Fields |= MessageSummaryItems.Flags; break; case "MODSEQ": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out value64)) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.Fields |= MessageSummaryItems.ModSeq; summary.ModSeq = value64; break; case "UID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out value) || value == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.UniqueId = new UniqueId (ic.Folder.UidValidity, value); summary.Fields |= MessageSummaryItems.UniqueId; break; case "X-GM-MSGID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out value64) || value64 == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.Fields |= MessageSummaryItems.GMailMessageId; summary.GMailMessageId = value64; break; case "X-GM-THRID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out value64) || value64 == 0) throw ImapEngine.UnexpectedToken (ImapEngine.GenericItemSyntaxErrorFormat, atom, token); summary.Fields |= MessageSummaryItems.GMailThreadId; summary.GMailThreadId = value64; break; case "X-GM-LABELS": summary.GMailLabels = ImapUtils.ParseLabelsList (engine, ic.CancellationToken); summary.Fields |= MessageSummaryItems.GMailLabels; break; default: throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); } } while (true); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "FETCH", token); if ((ctx.RequestedItems & summary.Fields) == ctx.RequestedItems) OnMessageSummaryFetched (summary); }
static void SkipBodyExtensions (ImapEngine engine, CancellationToken cancellationToken) { var token = engine.ReadToken (cancellationToken); switch (token.Type) { case ImapTokenType.OpenParen: do { token = engine.PeekToken (cancellationToken); if (token.Type == ImapTokenType.CloseParen) break; SkipBodyExtensions (engine, cancellationToken); } while (true); // read the ')' engine.ReadToken (cancellationToken); break; case ImapTokenType.Literal: engine.ReadLiteral (cancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: case ImapTokenType.Nil: break; default: throw ImapEngine.UnexpectedToken (token, false); } }
static EnvelopeAddress ParseEnvelopeAddress (ImapEngine engine, CancellationToken cancellationToken) { var values = new string[4]; ImapToken token; int index = 0; do { token = engine.ReadToken (cancellationToken); switch (token.Type) { case ImapTokenType.Literal: values[index] = engine.ReadLiteral (cancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: values[index] = (string) token.Value; break; case ImapTokenType.Nil: break; default: throw ImapEngine.UnexpectedToken (token, false); } index++; } while (index < 4); token = engine.ReadToken (cancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); return new EnvelopeAddress (values); }
static string[] ParseContentLanguage (ImapEngine engine, CancellationToken cancellationToken) { var token = engine.ReadToken (cancellationToken); var languages = new List<string> (); string language; switch (token.Type) { case ImapTokenType.Literal: language = engine.ReadLiteral (cancellationToken); languages.Add (language); break; case ImapTokenType.QString: case ImapTokenType.Atom: language = (string) token.Value; languages.Add (language); break; case ImapTokenType.Nil: return null; case ImapTokenType.OpenParen: do { token = engine.PeekToken (cancellationToken); if (token.Type == ImapTokenType.CloseParen) break; language = ReadStringToken (engine, cancellationToken); languages.Add (language); } while (true); // read the ')' engine.ReadToken (cancellationToken); break; default: throw ImapEngine.UnexpectedToken (token, false); } return languages.ToArray (); }
static string ReadNStringToken (ImapEngine engine, bool rfc2047, CancellationToken cancellationToken) { var token = engine.ReadToken (cancellationToken); string value; switch (token.Type) { case ImapTokenType.Literal: value = engine.ReadLiteral (cancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: value = (string) token.Value; break; case ImapTokenType.Nil: return null; default: throw ImapEngine.UnexpectedToken (token, false); } return rfc2047 ? Rfc2047.DecodeText (Latin1.GetBytes (value)) : value; }
/// <summary> /// Parses an untagged LIST or LSUB response. /// </summary> /// <param name="engine">The IMAP engine.</param> /// <param name="ic">The IMAP command.</param> /// <param name="index">The index.</param> public static void ParseFolderList (ImapEngine engine, ImapCommand ic, int index) { var token = engine.ReadToken (ic.CancellationToken); var list = (List<ImapFolder>) ic.UserData; var attrs = FolderAttributes.None; string encodedName; ImapFolder folder; char delim; // parse the folder attributes list if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); while (token.Type == ImapTokenType.Flag || token.Type == ImapTokenType.Atom) { string atom = (string) token.Value; switch (atom) { case "\\NoInferiors": attrs |= FolderAttributes.NoInferiors; break; case "\\Noselect": attrs |= FolderAttributes.NoSelect; break; case "\\Marked": attrs |= FolderAttributes.Marked; break; case "\\Unmarked": attrs |= FolderAttributes.Unmarked; break; case "\\NonExistent": attrs |= FolderAttributes.NonExistent; break; case "\\Subscribed": attrs |= FolderAttributes.Subscribed; break; case "\\Remote": attrs |= FolderAttributes.Remote; break; case "\\HasChildren": attrs |= FolderAttributes.HasChildren; break; case "\\HasNoChildren": attrs |= FolderAttributes.HasNoChildren; break; case "\\All": attrs |= FolderAttributes.All; break; case "\\Archive": attrs |= FolderAttributes.Archive; break; case "\\Drafts": attrs |= FolderAttributes.Drafts; break; case "\\Flagged": attrs |= FolderAttributes.Flagged; break; case "\\Junk": attrs |= FolderAttributes.Junk; break; case "\\Sent": attrs |= FolderAttributes.Sent; break; case "\\Trash": attrs |= FolderAttributes.Trash; break; // XLIST flags: case "\\AllMail": attrs |= FolderAttributes.All; break; case "\\Important": attrs |= FolderAttributes.Flagged; break; case "\\Inbox": attrs |= FolderAttributes.Inbox; break; case "\\Spam": attrs |= FolderAttributes.Junk; break; case "\\Starred": attrs |= FolderAttributes.Flagged; break; } token = engine.ReadToken (ic.CancellationToken); } if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); // parse the path delimeter token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.QString) { var qstring = (string) token.Value; delim = qstring[0]; } else if (token.Type == ImapTokenType.Nil) { delim = '\0'; } else { throw ImapEngine.UnexpectedToken (token, false); } // parse the folder name token = engine.ReadToken (ImapStream.StringSpecials, ic.CancellationToken); switch (token.Type) { case ImapTokenType.Literal: encodedName = engine.ReadLiteral (ic.CancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: encodedName = (string) token.Value; break; default: throw ImapEngine.UnexpectedToken (token, false); } if (IsInbox (encodedName)) attrs |= FolderAttributes.Inbox; if (engine.GetCachedFolder (encodedName, out folder)) { attrs |= (folder.Attributes & ~(FolderAttributes.Marked | FolderAttributes.Unmarked)); folder.UpdateAttributes (attrs); } else { folder = engine.CreateImapFolder (encodedName, attrs, delim); engine.CacheFolder (folder); } list.Add (folder); }
static DateTimeOffset? ParseEnvelopeDate (ImapEngine engine, CancellationToken cancellationToken) { var token = engine.ReadToken (cancellationToken); DateTimeOffset date; string value; switch (token.Type) { case ImapTokenType.Literal: value = engine.ReadLiteral (cancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: value = (string) token.Value; break; case ImapTokenType.Nil: return null; default: throw ImapEngine.UnexpectedToken (token, false); } if (!DateUtils.TryParse (value, out date)) return null; return date; }
static void AddEnvelopeAddress(InternetAddressList list, ImapEngine engine, CancellationToken cancellationToken) { var values = new string[4]; ImapToken token; int index = 0; do { token = engine.ReadToken (cancellationToken); switch (token.Type) { case ImapTokenType.Literal: values[index] = engine.ReadLiteral (cancellationToken); break; case ImapTokenType.QString: case ImapTokenType.Atom: values[index] = (string) token.Value; break; case ImapTokenType.Nil: break; default: throw ImapEngine.UnexpectedToken (token, false); } index++; } while (index < 4); token = engine.ReadToken (cancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); string name = null; if (values[0] != null) { // Note: since the ImapEngine.ReadLiteral() uses iso-8859-1 // to convert bytes to unicode, we can undo that here: name = Rfc2047.DecodePhrase (Latin1.GetBytes (values[0])); } string address = values[3] != null ? values[2] + "@" + values[3] : values[2]; list.Add (new MailboxAddress (name, address)); }