void FetchSummaryItems (ImapEngine engine, ImapCommand ic, int index) { var token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (token, false); 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 (token, false); var atom = (string) token.Value; ulong value64; uint value; 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 (token, false); } 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 (token, false); summary.Fields |= MessageSummaryItems.MessageSize; summary.MessageSize = value; break; case "BODYSTRUCTURE": summary.Body = ImapUtils.ParseBody (engine, 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 (token, false); // References 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 (token, false); } } while (true); } else if (token.Type != ImapTokenType.Atom) { throw ImapEngine.UnexpectedToken (token, false); } } while (true); if (token.Type != ImapTokenType.CloseBracket) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Literal) throw ImapEngine.UnexpectedToken (token, false); try { var message = engine.ParseMessage (engine.Stream, false, ic.CancellationToken); summary.Fields |= MessageSummaryItems.References; summary.References = message.References; summary.Headers = message.Headers; } catch (FormatException) { // consume any remaining literal data... ReadLiteralData (engine, ic.CancellationToken); } } else { summary.Fields |= MessageSummaryItems.Body; try { summary.Body = ImapUtils.ParseBody (engine, 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, summary.UserFlags, ic.CancellationToken); summary.Fields |= MessageSummaryItems.Flags; break; case "MODSEQ": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out value64) || value64 == 0) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); 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 (token, false); 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 (token, false); 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 (token, false); 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 (token, false); } } while (true); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); if ((ctx.RequestedItems & summary.Fields) == ctx.RequestedItems) OnMessageSummaryFetched (summary); }