/// <summary> /// Updates current folder messages info with new messages info. /// </summary> /// <param name="folder"></param> /// <returns></returns> internal string Update(IMAP_SelectedFolder folder) { StringBuilder retVal = new StringBuilder(); long maxUID = this.MessageUidNext - 1; long countExists = this.Messages.Count; long countRecent = this.RecentCount; // Add new messages for (int i = folder.Messages.Count - 1; i > 0; i--) { IMAP_Message message = folder.Messages[i]; // New message if (message.UID > maxUID) { m_pMessages.Add( message.ID, message.UID, message.InternalDate, message.Size, message.Flags ); } // New messages ended else { break; } } // Remove deleted messages for (int i = 0; i < m_pMessages.Count - 1; i++) { IMAP_Message message = m_pMessages[i]; if (!folder.m_pMessages.ContainsUID(message.UID)) { retVal.Append("* " + message.SequenceNo + " EXPUNGE\r\n"); m_pMessages.Remove(message); i--; } } if (countExists != this.Messages.Count) { retVal.Append("* " + this.Messages.Count + " EXISTS\r\n"); } if (countRecent != this.RecentCount) { retVal.Append("* " + this.RecentCount + " RECENT\r\n"); } return(retVal.ToString()); }
/// <summary> /// Raises event 'GetMessagesInfo'. /// </summary> /// <param name="session">Reference to IMAP session.</param> /// <param name="folder">Folder which messages info to get.</param> /// <returns></returns> internal IMAP_eArgs_GetMessagesInfo OnGetMessagesInfo(IMAP_Session session, IMAP_SelectedFolder folder) { IMAP_eArgs_GetMessagesInfo eArgs = new IMAP_eArgs_GetMessagesInfo(session, folder); if (this.GetMessagesInfo != null) { this.GetMessagesInfo(session, eArgs); } return(eArgs); }
/// <summary> /// Default constructor. /// </summary> public IMAP_eArgs_GetMessagesInfo(IMAP_Session session,IMAP_SelectedFolder folder) { m_pSession = session; m_pFolderInfo = folder; }
private void Examine(string cmdTag,string argsText) { /* Rfc 3501 6.3.2 EXAMINE Command Arguments: mailbox name Responses: REQUIRED untagged responses: FLAGS, EXISTS, RECENT REQUIRED OK untagged responses: UNSEEN, PERMANENTFLAGS, UIDNEXT, UIDVALIDITY Result: OK - examine completed, now in selected state NO - examine failure, now in authenticated state: no such mailbox, can't access mailbox BAD - command unknown or arguments invalid The EXAMINE command is identical to SELECT and returns the same output; however, the selected mailbox is identified as read-only. No changes to the permanent state of the mailbox, including per-user state, are permitted; in particular, EXAMINE MUST NOT cause messages to lose the \Recent flag. The text of the tagged OK response to the EXAMINE command MUST begin with the "[READ-ONLY]" response code. Example: C: A932 EXAMINE blurdybloop S: * 17 EXISTS S: * 2 RECENT S: * OK [UNSEEN 8] Message 8 is first unseen S: * OK [UIDVALIDITY 3857529045] UIDs valid S: * OK [UIDNEXT 4392] Predicted next UID S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) S: * OK [PERMANENTFLAGS ()] No permanent flags permitted S: A932 OK [READ-ONLY] EXAMINE completed */ if(!this.Authenticated){ this.Socket.WriteLine(cmdTag + " NO Authenticate first !"); return; } string[] args = TextUtils.SplitQuotedString(argsText,' ',true); if(args.Length != 1){ this.Socket.WriteLine(cmdTag + " BAD EXAMINE invalid arguments. Syntax: {<command-tag> EXAMINE \"mailboxName\"}"); return; } // Store start time long startTime = DateTime.Now.Ticks; IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(Core.Decode_IMAP_UTF7_String(args[0])); IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this,selectedFolder); if(eArgs.ErrorText == null){ m_pSelectedFolder = selectedFolder; m_pSelectedFolder.ReadOnly = true; m_SelectedMailbox = Core.Decode_IMAP_UTF7_String(args[0]); string response = ""; response += "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"; response += "* " + m_pSelectedFolder.Messages.Count + " EXISTS\r\n"; response += "* " + m_pSelectedFolder.RecentCount + " RECENT\r\n"; response += "* OK [UNSEEN " + m_pSelectedFolder.FirstUnseen + "] Message " + m_pSelectedFolder.FirstUnseen + " is first unseen\r\n"; response += "* OK [UIDVALIDITY " + m_pSelectedFolder.FolderUID + "] UIDs valid\r\n"; response += "* OK [UIDNEXT " + m_pSelectedFolder.MessageUidNext + "] Predicted next UID\r\n"; response += "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)] Available permanent falgs\r\n"; response += cmdTag + " OK [READ-ONLY] EXAMINE Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n"; this.Socket.Write(response); } else{ this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText); } }
private void Close(string cmdTag) { /* RFC 3501 6.4.2 CLOSE Command Arguments: none Responses: no specific responses for this command Result: OK - close completed, now in authenticated state BAD - command unknown or arguments invalid The CLOSE command permanently removes from the currently selected mailbox all messages that have the \Deleted flag set, and returns to authenticated state from selected state. No untagged EXPUNGE responses are sent. No messages are removed, and no error is given, if the mailbox is selected by an EXAMINE command or is otherwise selected read-only. Even if a mailbox is selected, a SELECT, EXAMINE, or LOGOUT command MAY be issued without previously issuing a CLOSE command. The SELECT, EXAMINE, and LOGOUT commands implicitly close the currently selected mailbox without doing an expunge. However, when many messages are deleted, a CLOSE-LOGOUT or CLOSE-SELECT sequence is considerably faster than an EXPUNGE-LOGOUT or EXPUNGE-SELECT because no untagged EXPUNGE responses (which the client would probably ignore) are sent. Example: C: A341 CLOSE S: A341 OK CLOSE completed */ if(!this.Authenticated){ this.Socket.WriteLine(cmdTag + " NO Authenticate first !"); return; } if(m_SelectedMailbox.Length == 0){ this.Socket.WriteLine(cmdTag + " NO Select mailbox first !"); return; } if(!m_pSelectedFolder.ReadOnly){ IMAP_Message[] messages = m_pSelectedFolder.Messages.GetWithFlags(IMAP_MessageFlags.Deleted); foreach(IMAP_Message msg in messages){ m_pServer.OnDeleteMessage(this,msg); } } m_SelectedMailbox = ""; m_pSelectedFolder = null; this.Socket.WriteLine(cmdTag + " OK CLOSE completed"); }
private void Status(string cmdTag,string argsText) { /* RFC 3501 6.3.10 STATUS Command Arguments: mailbox name status data item names Responses: untagged responses: STATUS Result: OK - status completed NO - status failure: no status for that name BAD - command unknown or arguments invalid The STATUS command requests the status of the indicated mailbox. It does not change the currently selected mailbox, nor does it affect the state of any messages in the queried mailbox (in particular, STATUS MUST NOT cause messages to lose the \Recent flag). The STATUS command provides an alternative to opening a second IMAP4rev1 connection and doing an EXAMINE command on a mailbox to query that mailbox's status without deselecting the current mailbox in the first IMAP4rev1 connection. Unlike the LIST command, the STATUS command is not guaranteed to be fast in its response. In some implementations, the server is obliged to open the mailbox read-only internally to obtain certain status information. Also unlike the LIST command, the STATUS command does not accept wildcards. The currently defined status data items that can be requested are: MESSAGES The number of messages in the mailbox. RECENT The number of messages with the \Recent flag set. UIDNEXT The next unique identifier value of the mailbox. Refer to section 2.3.1.1 for more information. UIDVALIDITY The unique identifier validity value of the mailbox. Refer to section 2.3.1.1 for more information. UNSEEN The number of messages which do not have the \Seen flag set. Example: C: A042 STATUS blurdybloop (UIDNEXT MESSAGES) S: * STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292) S: A042 OK STATUS completed */ if(!this.Authenticated){ this.Socket.WriteLine(cmdTag + " NO Authenticate first !"); return; } string[] args = ParseParams(argsText); if(args.Length != 2){ this.Socket.WriteLine(cmdTag + " BAD Invalid STATUS arguments. Syntax: {<command-tag> STATUS \"<mailbox-name>\" \"(status-data-items)\"}"); return; } string folder = Core.Decode_IMAP_UTF7_String(args[0]); string wantedItems = args[1].ToUpper(); // See wanted items are valid. if(wantedItems.Replace("MESSAGES","").Replace("RECENT","").Replace("UIDNEXT","").Replace("UIDVALIDITY","").Replace("UNSEEN","").Trim().Length > 0){ this.Socket.WriteLine(cmdTag + " BAD STATUS invalid arguments"); return; } IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(folder); IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this,selectedFolder); if(eArgs.ErrorText == null){ string itemsReply = ""; if(wantedItems.IndexOf("MESSAGES") > -1){ itemsReply += " MESSAGES " + selectedFolder.Messages.Count; } if(wantedItems.IndexOf("RECENT") > -1){ itemsReply += " RECENT " + selectedFolder.RecentCount; } if(wantedItems.IndexOf("UNSEEN") > -1){ itemsReply += " UNSEEN " + selectedFolder.UnSeenCount; } if(wantedItems.IndexOf("UIDVALIDITY") > -1){ itemsReply += " UIDVALIDITY " + selectedFolder.FolderUID; } if(wantedItems.IndexOf("UIDNEXT") > -1){ itemsReply += " UIDNEXT " + selectedFolder.MessageUidNext; } itemsReply = itemsReply.Trim(); this.Socket.WriteLine("* STATUS " + args[0] + " (" + itemsReply + ")"); this.Socket.WriteLine(cmdTag + " OK STATUS completed"); } else{ this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText); } }
private void Select(string cmdTag,string argsText) { /* Rfc 3501 6.3.1 SELECT Command Arguments: mailbox name Responses: REQUIRED untagged responses: FLAGS, EXISTS, RECENT REQUIRED OK untagged responses: UNSEEN, PERMANENTFLAGS, UIDNEXT, UIDVALIDITY Result: OK - select completed, now in selected state NO - select failure, now in authenticated state: no such mailbox, can't access mailbox BAD - command unknown or arguments invalid The SELECT command selects a mailbox so that messages in the mailbox can be accessed. Before returning an OK to the client, the server MUST send the following untagged data to the client. Note that earlier versions of this protocol only required the FLAGS, EXISTS, and RECENT untagged data; consequently, client implementations SHOULD implement default behavior for missing data as discussed with the individual item. FLAGS Defined flags in the mailbox. See the description of the FLAGS response for more detail. <n> EXISTS The number of messages in the mailbox. See the description of the EXISTS response for more detail. <n> RECENT The number of messages with the \Recent flag set. See the description of the RECENT response for more detail. OK [UNSEEN <n>] The message sequence number of the first unseen message in the mailbox. If this is missing, the client can not make any assumptions about the first unseen message in the mailbox, and needs to issue a SEARCH command if it wants to find it. OK [PERMANENTFLAGS (<list of flags>)] A list of message flags that the client can change permanently. If this is missing, the client should assume that all flags can be changed permanently. OK [UIDNEXT <n>] The next unique identifier value. Refer to section 2.3.1.1 for more information. If this is missing, the client can not make any assumptions about the next unique identifier value. OK [UIDVALIDITY <n>] The unique identifier validity value. Refer to section 2.3.1.1 for more information. If this is missing, the server does not support unique identifiers. Only one mailbox can be selected at a time in a connection; simultaneous access to multiple mailboxes requires multiple connections. The SELECT command automatically deselects any currently selected mailbox before attempting the new selection. Consequently, if a mailbox is selected and a SELECT command that fails is attempted, no mailbox is selected. If the client is permitted to modify the mailbox, the server SHOULD prefix the text of the tagged OK response with the "[READ-WRITE]" response code. If the client is not permitted to modify the mailbox but is permitted read access, the mailbox is selected as read-only, and the server MUST prefix the text of the tagged OK response to SELECT with the "[READ-ONLY]" response code. Read-only access through SELECT differs from the EXAMINE command in that certain read-only mailboxes MAY permit the change of permanent state on a per-user (as opposed to global) basis. Netnews messages marked in a server-based .newsrc file are an example of such per-user permanent state that can be modified with read-only mailboxes. Example: C: A142 SELECT INBOX S: * 172 EXISTS S: * 1 RECENT S: * OK [UNSEEN 12] Message 12 is first unseen S: * OK [UIDVALIDITY 3857529045] UIDs valid S: * OK [UIDNEXT 4392] Predicted next UID S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited S: A142 OK [READ-WRITE] SELECT completed */ if(!this.Authenticated){ this.Socket.WriteLine(cmdTag + " NO Authenticate first !"); return; } string[] args = TextUtils.SplitQuotedString(argsText,' ',true); if(args.Length != 1){ this.Socket.WriteLine(cmdTag + " BAD SELECT invalid arguments. Syntax: {<command-tag> SELECT \"mailboxName\"}"); return; } // Store start time long startTime = DateTime.Now.Ticks; IMAP_SelectedFolder selectedFolder = new IMAP_SelectedFolder(Core.Decode_IMAP_UTF7_String(args[0])); IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this,selectedFolder); if(eArgs.ErrorText == null){ m_pSelectedFolder = selectedFolder; m_SelectedMailbox = Core.Decode_IMAP_UTF7_String(args[0]); string response = ""; response += "* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n"; response += "* " + m_pSelectedFolder.Messages.Count + " EXISTS\r\n"; response += "* " + m_pSelectedFolder.RecentCount + " RECENT\r\n"; response += "* OK [UNSEEN " + m_pSelectedFolder.FirstUnseen + "] Message " + m_pSelectedFolder.FirstUnseen + " is first unseen\r\n"; response += "* OK [UIDVALIDITY " + m_pSelectedFolder.FolderUID + "] Folder UID\r\n"; response += "* OK [UIDNEXT " + m_pSelectedFolder.MessageUidNext + "] Predicted next message UID\r\n"; response += "* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)] Available permanent flags\r\n"; response += cmdTag + " OK [" + (m_pSelectedFolder.ReadOnly ? "READ-ONLY" : "READ-WRITE") + "] SELECT Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n"; this.Socket.Write(response); } else{ this.Socket.WriteLine(cmdTag + " NO " + eArgs.ErrorText); } }
/// <summary> /// Processes changes and sends status responses if there are changes in selected mailbox. /// </summary> private void ProcessMailboxChanges() { // Get status IMAP_SelectedFolder folderInfo = new IMAP_SelectedFolder(m_SelectedMailbox); IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this,folderInfo); // Join new info with exisiting string statusResponse = m_pSelectedFolder.Update(folderInfo); if(!string.IsNullOrEmpty(statusResponse)){ this.Socket.WriteLine(statusResponse); m_pSelectedFolder = folderInfo; } }
/// <summary> /// Updates current folder messages info with new messages info. /// </summary> /// <param name="folder"></param> /// <returns></returns> internal string Update(IMAP_SelectedFolder folder) { StringBuilder retVal = new StringBuilder(); long maxUID = this.MessageUidNext - 1; long countExists = this.Messages.Count; long countRecent = this.RecentCount; // Add new messages for(int i=folder.Messages.Count-1;i>0;i--){ IMAP_Message message = folder.Messages[i]; // New message if(message.UID > maxUID){ m_pMessages.Add( message.ID, message.UID, message.InternalDate, message.Size, message.Flags ); } // New messages ended else{ break; } } // Remove deleted messages for(int i=0;i<m_pMessages.Count-1;i++){ IMAP_Message message = m_pMessages[i]; if(!folder.m_pMessages.ContainsUID(message.UID)){ retVal.Append("* " + message.SequenceNo + " EXPUNGE\r\n"); m_pMessages.Remove(message); i--; } } if(countExists != this.Messages.Count){ retVal.Append("* " + this.Messages.Count + " EXISTS\r\n"); } if(countRecent != this.RecentCount){ retVal.Append("* " + this.RecentCount + " RECENT\r\n"); } return retVal.ToString(); }
/// <summary> /// Default constructor. /// </summary> public IMAP_eArgs_GetMessagesInfo(IMAP_Session session, IMAP_SelectedFolder folder) { m_pSession = session; m_pFolderInfo = folder; }
private void Noop(string cmdTag) { /* RFC 3501 6.1.2 NOOP Command Arguments: none Responses: no specific responses for this command (but see below) Result: OK - noop completed BAD - command unknown or arguments invalid The NOOP command always succeeds. It does nothing. Since any command can return a status update as untagged data, the NOOP command can be used as a periodic poll for new messages or message status updates during a period of inactivity. The NOOP command can also be used to reset any inactivity autologout timer on the server. Example: C: a002 NOOP S: a002 OK NOOP completed */ // Store start time long startTime = DateTime.Now.Ticks; // If there is selected mailbox, see if messages status has changed if(m_SelectedMailbox.Length > 0){ // Get status IMAP_SelectedFolder folderInfo = new IMAP_SelectedFolder(m_SelectedMailbox); IMAP_eArgs_GetMessagesInfo eArgs = m_pServer.OnGetMessagesInfo(this,folderInfo); // Join new info with exisiting string statusResponse = m_pSelectedFolder.Update(folderInfo); this.Socket.WriteLine(statusResponse + cmdTag + " OK NOOP Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n"); } else{ this.Socket.WriteLine(cmdTag + " OK NOOP Completed in " + ((DateTime.Now.Ticks - startTime) / (decimal)10000000).ToString("f2") + " seconds\r\n"); } }