/// <summary> /// Starts parsing fetch data-items, /// </summary> /// <param name="imap">IMAP client.</param> /// <param name="r">Fetch line reader.</param> /// <param name="callback">Callback to be called when parsing completes.</param> /// <exception cref="ArgumentNullException">Is raised when <b>imap</b>,<b>r</b> or <b>callback</b> is null reference.</exception> private void ParseDataItems(IMAP_Client imap,StringReader r,EventHandler<EventArgs<Exception>> callback) { if(imap == null){ throw new ArgumentNullException("imap"); } if(r == null){ throw new ArgumentNullException("r"); } if(callback == null){ throw new ArgumentNullException("callback"); } /* RFC 3501 7.4.2. FETCH Response. Example: S: * 23 FETCH (FLAGS (\Seen) RFC822.SIZE 44827) */ while(true){ r.ReadToFirstChar(); #region BODY[] if(r.StartsWith("BODY[",false)){ /* RFC 3501 7.4.2. FETCH Response. BODY[<section>]<<origin octet>> A string expressing the body contents of the specified section. The string SHOULD be interpreted by the client according to the content transfer encoding, body type, and subtype. If the origin octet is specified, this string is a substring of the entire body contents, starting at that origin octet. This means that BODY[]<0> MAY be truncated, but BODY[] is NEVER truncated. Note: The origin octet facility MUST NOT be used by a server in a FETCH response unless the client specifically requested it by means of a FETCH of a BODY[<section>]<<partial>> data item. 8-bit textual data is permitted if a [CHARSET] identifier is part of the body parameter parenthesized list for this section. Note that headers (part specifiers HEADER or MIME, or the header portion of a MESSAGE/RFC822 part), MUST be 7-bit; 8-bit characters are not permitted in headers. Note also that the [RFC-2822] delimiting blank line between the header and the body is not affected by header line subsetting; the blank line is always included as part of header data, except in the case of a message which has no body and no blank line. Non-textual data such as binary data MUST be transfer encoded into a textual form, such as BASE64, prior to being sent to the client. To derive the original binary data, the client MUST decode the transfer encoded string. */ // Eat BODY word. r.ReadWord(); // Read body-section. string section = r.ReadParenthesized(); // Read origin if any. int offset = -1; if(r.StartsWith("<")){ offset = Convert.ToInt32(r.ReadParenthesized().Split(' ')[0]); } IMAP_t_Fetch_r_i_Body dataItem = new IMAP_t_Fetch_r_i_Body(section,offset,new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this,dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if(eArgs.Stream != null){ dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if(ReadData(imap,r,callback,dataItem.Stream)){ return; } // Continue processing. //else{ } #endregion #region BODY else if(r.StartsWith("BODY ",false)){ //IMAP_t_Fetch_r_i_BodyS } #endregion #region BODYSTRUCTURE else if(r.StartsWith("BODYSTRUCTURE",false)){ //IMAP_t_Fetch_r_i_BodyStructure } #endregion #region ENVELOPE else if(r.StartsWith("ENVELOPE",false)){ // Envelope can contain string literals, we just try to parse it. // If parse fails, just get string literal and try again as long as all ENVELOPE data has read. string envelope = null; while(true){ // Create temporary reader(we don't want to read partial ENVELOPE data from reader). StringReader tmpReader = new StringReader(r.SourceString); // Eat ENVELOPE word. tmpReader.ReadWord(); tmpReader.ReadToFirstChar(); try{ envelope = tmpReader.ReadParenthesized(); // We got full ENVELOPE, so use tmp reader as reader. r = tmpReader; break; } catch{ // Read completed async, it will continue parsing. if(ReadStringLiteral(imap,r,callback)){ return; } } } m_pDataItems.Add(IMAP_t_Fetch_r_i_Envelope.Parse(new StringReader(envelope))); } #endregion #region FLAGS else if(r.StartsWith("FLAGS",false)){ /* RFC 3501 7.4.2. FETCH Response. FLAGS A parenthesized list of flags that are set for this message. */ // Eat FLAGS word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Flags(IMAP_t_MsgFlags.Parse(r.ReadParenthesized()))); } #endregion #region INTERNALDATE else if(r.StartsWith("INTERNALDATE",false)){ /* RFC 3501 7.4.2. FETCH Response. INTERNALDATE A string representing the internal date of the message. */ // Eat INTERNALDATE word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_InternalDate(IMAP_Utils.ParseDate(r.ReadWord()))); } #endregion #region RFC822 else if(r.StartsWith("RFC822 ",false)){ /* RFC 3501 7.4.2. FETCH Response. RFC822 Equivalent to BODY[]. */ // Eat RFC822 word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822 dataItem = new IMAP_t_Fetch_r_i_Rfc822(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this,dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if(eArgs.Stream != null){ dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if(ReadData(imap,r,callback,dataItem.Stream)){ return; } // Continue processing. //else{ } #endregion #region RFC822.HEADER else if(r.StartsWith("RFC822.HEADER",false)){ /* RFC 3501 7.4.2. FETCH Response. RFC822.HEADER Equivalent to BODY[HEADER]. Note that this did not result in \Seen being set, because RFC822.HEADER response data occurs as a result of a FETCH of RFC822.HEADER. BODY[HEADER] response data occurs as a result of a FETCH of BODY[HEADER] (which sets \Seen) or BODY.PEEK[HEADER] (which does not set \Seen). */ // Eat RFC822.HEADER word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822Header dataItem = new IMAP_t_Fetch_r_i_Rfc822Header(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this,dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if(eArgs.Stream != null){ dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if(ReadData(imap,r,callback,dataItem.Stream)){ return; } // Continue processing. //else{ } #endregion #region RFC822.SIZE else if(r.StartsWith("RFC822.SIZE",false)){ /* RFC 3501 7.4.2. FETCH Response. RFC822.SIZE A number expressing the [RFC-2822] size of the message. */ // Eat RFC822.SIZE word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Rfc822Size(Convert.ToInt32(r.ReadWord()))); } #endregion #region RFC822.TEXT else if(r.StartsWith("RFC822.TEXT",false)){ /* RFC 3501 7.4.2. FETCH Response. RFC822.TEXT Equivalent to BODY[TEXT]. */ // Eat RFC822.TEXT word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822Text dataItem = new IMAP_t_Fetch_r_i_Rfc822Text(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this,dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if(eArgs.Stream != null){ dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if(ReadData(imap,r,callback,dataItem.Stream)){ return; } // Continue processing. //else{ } #endregion #region UID else if(r.StartsWith("UID",false)){ /* RFC 3501 7.4.2. FETCH Response. UID A number expressing the unique identifier of the message. */ // Eat UID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Uid(Convert.ToInt64(r.ReadWord()))); } #endregion #region X-GM-MSGID else if(r.StartsWith("X-GM-MSGID",false)){ /* http://code.google.com/intl/et/apis/gmail/imap X-GM-MSGID. */ // Eat X-GM-MSGID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_X_GM_MSGID(Convert.ToUInt64(r.ReadWord()))); } #endregion #region X-GM-THRID else if(r.StartsWith("X-GM-THRID",false)){ /* http://code.google.com/intl/et/apis/gmail/imap X-GM-THRID. */ // Eat X-GM-THRID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_X_GM_THRID(Convert.ToUInt64(r.ReadWord()))); } #endregion #region ) - fetch closing. else if(r.StartsWith(")",false)){ break; } #endregion else{ throw new ParseException("Not supported FETCH data-item '" + r.ReadToEnd() + "'."); } } callback(this,new EventArgs<Exception>(null)); }
/// <summary> /// Starts parsing fetch data-items, /// </summary> /// <param name="imap">IMAP client.</param> /// <param name="r">Fetch line reader.</param> /// <param name="callback">Callback to be called when parsing completes.</param> /// <exception cref="ArgumentNullException">Is raised when <b>imap</b>,<b>r</b> or <b>callback</b> is null reference.</exception> private void ParseDataItems(IMAP_Client imap, StringReader r, EventHandler <EventArgs <Exception> > callback) { if (imap == null) { throw new ArgumentNullException("imap"); } if (r == null) { throw new ArgumentNullException("r"); } if (callback == null) { throw new ArgumentNullException("callback"); } /* RFC 3501 7.4.2. FETCH Response. * Example: S: * 23 FETCH (FLAGS (\Seen) RFC822.SIZE 44827) */ while (true) { r.ReadToFirstChar(); #region BODY[] if (r.StartsWith("BODY[", false)) { /* RFC 3501 7.4.2. FETCH Response. * BODY[<section>]<<origin octet>> * A string expressing the body contents of the specified section. * The string SHOULD be interpreted by the client according to the * content transfer encoding, body type, and subtype. * * If the origin octet is specified, this string is a substring of * the entire body contents, starting at that origin octet. This * means that BODY[]<0> MAY be truncated, but BODY[] is NEVER * truncated. * * Note: The origin octet facility MUST NOT be used by a server * in a FETCH response unless the client specifically requested * it by means of a FETCH of a BODY[<section>]<<partial>> data * item. * * 8-bit textual data is permitted if a [CHARSET] identifier is * part of the body parameter parenthesized list for this section. * Note that headers (part specifiers HEADER or MIME, or the * header portion of a MESSAGE/RFC822 part), MUST be 7-bit; 8-bit * characters are not permitted in headers. Note also that the * [RFC-2822] delimiting blank line between the header and the * body is not affected by header line subsetting; the blank line * is always included as part of header data, except in the case * of a message which has no body and no blank line. * * Non-textual data such as binary data MUST be transfer encoded * into a textual form, such as BASE64, prior to being sent to the * client. To derive the original binary data, the client MUST * decode the transfer encoded string. */ // Eat BODY word. r.ReadWord(); // Read body-section. string section = r.ReadParenthesized(); // Read origin if any. int offset = -1; if (r.StartsWith("<")) { offset = Convert.ToInt32(r.ReadParenthesized().Split(' ')[0]); } IMAP_t_Fetch_r_i_Body dataItem = new IMAP_t_Fetch_r_i_Body(section, offset, new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this, dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if (eArgs.Stream != null) { dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if (ReadData(imap, r, callback, dataItem.Stream)) { return; } // Continue processing. //else{ } #endregion #region BODY else if (r.StartsWith("BODY ", false)) { // BODYSTRUCTURE can contain string literals, we just try to parse it. // If parse fails, just get string literal and try again as long as all BODYSTRUCTURE data has read. string bodyStructure = null; while (true) { // Create temporary reader(we don't want to read partial BODYSTRUCTURE data from reader). StringReader tmpReader = new StringReader(r.SourceString); // Eat BODYSTRUCTURE word. tmpReader.ReadWord(); tmpReader.ReadToFirstChar(); try{ bodyStructure = tmpReader.ReadParenthesized(); // We got full BODYSTRUCTURE, so use tmp reader as reader. r = tmpReader; break; } catch { // Read completed async, it will continue parsing. if (ReadStringLiteral(imap, r, callback)) { return; } } } m_pDataItems.Add(IMAP_t_Fetch_r_i_BodyStructure.Parse(new StringReader(bodyStructure))); } #endregion #region BODYSTRUCTURE else if (r.StartsWith("BODYSTRUCTURE", false)) { // BODYSTRUCTURE can contain string literals, we just try to parse it. // If parse fails, just get string literal and try again as long as all BODYSTRUCTURE data has read. string bodyStructure = null; while (true) { // Create temporary reader(we don't want to read partial BODYSTRUCTURE data from reader). StringReader tmpReader = new StringReader(r.SourceString); // Eat BODYSTRUCTURE word. tmpReader.ReadWord(); tmpReader.ReadToFirstChar(); try{ bodyStructure = tmpReader.ReadParenthesized(); // We got full BODYSTRUCTURE, so use tmp reader as reader. r = tmpReader; break; } catch { // Read completed async, it will continue parsing. if (ReadStringLiteral(imap, r, callback)) { return; } } } m_pDataItems.Add(IMAP_t_Fetch_r_i_BodyStructure.Parse(new StringReader(bodyStructure))); } #endregion #region ENVELOPE else if (r.StartsWith("ENVELOPE", false)) { // Envelope can contain string literals, we just try to parse it. // If parse fails, just get string literal and try again as long as all ENVELOPE data has read. string envelope = null; while (true) { // Create temporary reader(we don't want to read partial ENVELOPE data from reader). StringReader tmpReader = new StringReader(r.SourceString); // Eat ENVELOPE word. tmpReader.ReadWord(); tmpReader.ReadToFirstChar(); try{ envelope = tmpReader.ReadParenthesized(); // We got full ENVELOPE, so use tmp reader as reader. r = tmpReader; break; } catch { // Read completed async, it will continue parsing. if (ReadStringLiteral(imap, r, callback)) { return; } } } m_pDataItems.Add(IMAP_t_Fetch_r_i_Envelope.Parse(new StringReader(envelope))); } #endregion #region FLAGS else if (r.StartsWith("FLAGS", false)) { /* RFC 3501 7.4.2. FETCH Response. * FLAGS * A parenthesized list of flags that are set for this message. */ // Eat FLAGS word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Flags(IMAP_t_MsgFlags.Parse(r.ReadParenthesized()))); } #endregion #region INTERNALDATE else if (r.StartsWith("INTERNALDATE", false)) { /* RFC 3501 7.4.2. FETCH Response. * INTERNALDATE * A string representing the internal date of the message. */ // Eat INTERNALDATE word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_InternalDate(IMAP_Utils.ParseDate(r.ReadWord()))); } #endregion #region RFC822 else if (r.StartsWith("RFC822 ", false)) { /* RFC 3501 7.4.2. FETCH Response. * RFC822 * Equivalent to BODY[]. */ // Eat RFC822 word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822 dataItem = new IMAP_t_Fetch_r_i_Rfc822(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this, dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if (eArgs.Stream != null) { dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if (ReadData(imap, r, callback, dataItem.Stream)) { return; } // Continue processing. //else{ } #endregion #region RFC822.HEADER else if (r.StartsWith("RFC822.HEADER", false)) { /* RFC 3501 7.4.2. FETCH Response. * RFC822.HEADER * Equivalent to BODY[HEADER]. Note that this did not result in * \Seen being set, because RFC822.HEADER response data occurs as * a result of a FETCH of RFC822.HEADER. BODY[HEADER] response * data occurs as a result of a FETCH of BODY[HEADER] (which sets * \Seen) or BODY.PEEK[HEADER] (which does not set \Seen). */ // Eat RFC822.HEADER word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822Header dataItem = new IMAP_t_Fetch_r_i_Rfc822Header(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this, dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if (eArgs.Stream != null) { dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if (ReadData(imap, r, callback, dataItem.Stream)) { return; } // Continue processing. //else{ } #endregion #region RFC822.SIZE else if (r.StartsWith("RFC822.SIZE", false)) { /* RFC 3501 7.4.2. FETCH Response. * RFC822.SIZE * A number expressing the [RFC-2822] size of the message. */ // Eat RFC822.SIZE word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Rfc822Size(Convert.ToInt32(r.ReadWord()))); } #endregion #region RFC822.TEXT else if (r.StartsWith("RFC822.TEXT", false)) { /* RFC 3501 7.4.2. FETCH Response. * RFC822.TEXT * Equivalent to BODY[TEXT]. */ // Eat RFC822.TEXT word. r.ReadWord(); r.ReadToFirstChar(); IMAP_t_Fetch_r_i_Rfc822Text dataItem = new IMAP_t_Fetch_r_i_Rfc822Text(new MemoryStreamEx(32000)); m_pDataItems.Add(dataItem); // Raise event, allow user to specify store stream. IMAP_Client_e_FetchGetStoreStream eArgs = new IMAP_Client_e_FetchGetStoreStream(this, dataItem); imap.OnFetchGetStoreStream(eArgs); // User specified own stream, use it. if (eArgs.Stream != null) { dataItem.Stream.Dispose(); dataItem.SetStream(eArgs.Stream); } // Read data will complete async and will continue data-items parsing, exit this method. if (ReadData(imap, r, callback, dataItem.Stream)) { return; } // Continue processing. //else{ } #endregion #region UID else if (r.StartsWith("UID", false)) { /* RFC 3501 7.4.2. FETCH Response. * UID * A number expressing the unique identifier of the message. */ // Eat UID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_Uid(Convert.ToInt64(r.ReadWord()))); } #endregion #region X-GM-MSGID else if (r.StartsWith("X-GM-MSGID", false)) { /* http://code.google.com/intl/et/apis/gmail/imap X-GM-MSGID. * */ // Eat X-GM-MSGID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_X_GM_MSGID(Convert.ToUInt64(r.ReadWord()))); } #endregion #region X-GM-THRID else if (r.StartsWith("X-GM-THRID", false)) { /* http://code.google.com/intl/et/apis/gmail/imap X-GM-THRID. * */ // Eat X-GM-THRID word. r.ReadWord(); m_pDataItems.Add(new IMAP_t_Fetch_r_i_X_GM_THRID(Convert.ToUInt64(r.ReadWord()))); } #endregion #region ) - fetch closing. else if (r.StartsWith(")", false)) { break; } #endregion else { throw new ParseException("Not supported FETCH data-item '" + r.ReadToEnd() + "'."); } } callback(this, new EventArgs <Exception>(null)); }