/// <summary> /// Copies the specified message to the specified folder /// </summary> /// <param name="msg">The message to copy</param> /// <param name="destFolder">The folder to copy the message to</param> public void CopyMessageToFolder(IMAPMessage msg, IMAPFolder destFolder) { if (_client.OfflineMode) { _client.Log(IMAPBase.LogTypeEnum.WARN, "Cannot copy messages in offline mode."); return; } string cmd = "UID COPY {0} \"{1}\"\r\n"; ArrayList result = new ArrayList(); _client._imap.SendAndReceive(String.Format(cmd, msg.Uid, destFolder.FolderPath), ref result); foreach (string s in result) { if (s.Contains("OK")) { // if the copy was successful, tell the destination folder to refresh its message UID list. destFolder.GetMessageIDs(false); int msgCount = destFolder.Messages.Count; // the copy function puts the new message at the end of the folder so lets automatically // load the data for the copy. If for some reason during the folder refresh another new message // was found and added making the copied message not the last one in the folder, thats ok // because as soon as the content is accessed the data will be loaded automatically if (msgCount > 0) { destFolder.Messages[msgCount - 1].RefreshData(msg.ContentLoaded, true); } _client.UpdateCache(true); _client.Log(IMAPBase.LogTypeEnum.INFO, String.Format("Message with UID {0} successfully copied to folder \"{1}\"", msg.Uid, destFolder.FolderName)); break; } } }
/// <summary> /// Moves the specified message to the specified folder /// </summary> /// <param name="msg">The message to move</param> /// <param name="destFolder">The folder to move the message to</param> public void MoveMessageToFolder(IMAPMessage msg, IMAPFolder destFolder) { if (_client.OfflineMode) { _client.Log(IMAPBase.LogTypeEnum.WARN, "Cannot move messages in offline mode."); return; } CopyMessageToFolder(msg, destFolder); DeleteMessage(msg); }
/// <summary> /// Appends new message to end of this folder /// </summary> /// <param name="msg"></param> /// <param name="content">The content of the message</param> public void AppendMessage(IMAPMessage msg, string content) { this.Select(); // first lets determine what the UID of the new message should be int uid = _messages.Count > 0 ? _messages[_messages.Count - 1].Uid : 0; uid++; msg.Uid = uid; //string cmd = "APPEND \"{0}\" (\\Seen) {0}\r\n"; ArrayList result = new ArrayList(); _client._imap.SendRaw("APPEND \"" + FolderPath + "\" (\\Seen) {" + uid + "}\r\n", true); //if (!result[0].ToString().StartsWith("+")) //{ // _client.Log(IMAPBase.LogTypeEnum.ERROR, "Invalid response from server"); // return; //} //_client._imap.ReadLine(); StringBuilder sb = new StringBuilder(); sb.AppendFormat("Date: {0}{1}", "Mon, 7 Feb 1994 21:52:25 -0800 (PST)", Environment.NewLine); sb.AppendFormat("From: {0}{1}", msg.From[0], Environment.NewLine); sb.AppendFormat("Subject: {0}{1}", msg.Subject, Environment.NewLine); sb.Append("To: "); foreach (IMAPMailAddress addr in msg.To) { sb.AppendFormat("{0}, ", addr); } sb.Remove(sb.Length - 2, 1); sb.Append(Environment.NewLine); sb.AppendFormat("Message-Id: <{0}@{1}>{2}", msg.Date.Ticks, msg.From[0].ToString().Substring(msg.From[0].ToString().IndexOf("@") + 1).Replace(">", ""), Environment.NewLine); sb.AppendLine("MIME-Version: 1.0"); sb.AppendLine("Content-Type: TEXT/PLAIN; CHARSET=US-ASCII"); sb.AppendLine(); sb.AppendLine(content); sb.AppendLine("\r\n"); result.Clear(); _client.Log(IMAPBase.LogTypeEnum.INFO, sb.ToString()); _client._imap.SendRaw(sb.ToString(), false); }
/// <summary> /// Deletes the specified message /// </summary> /// <param name="msg">The message to delete</param> public void DeleteMessage(IMAPMessage msg) { if (_client.OfflineMode) { _client.Log(IMAPBase.LogTypeEnum.WARN, "Cannot delete messages in offline mode."); return; } string cmd = "UID STORE {0} +FLAGS (\\Deleted)\r\n"; ArrayList result = new ArrayList(); // first we need to put the folder in read/write mode by SELECTing it this.Select(); // mark the specified message as deleted _client._imap.SendAndReceive(String.Format(cmd, msg.Uid), ref result); // EXPUNGE the \Deleted messages _client._imap.SendAndReceive("EXPUNGE\r\n", ref result); // remove the message object from the collection _messages.Remove(msg); // set the folder back to read-only mode this.Examine(); _client.UpdateCache(true); _client.Log(IMAPBase.LogTypeEnum.INFO, String.Format("Message wih UID {0} in folder \"{1}\" successfully deleted.", msg.Uid, this.FolderName)); }
/// <summary> /// Copies the specified message to the specified folder /// </summary> /// <param name="msg">The message to copy</param> /// <param name="destFolder">The folder to copy the message to</param> public void CopyMessageToFolder(IMAPMessage msg, IMAPFolder destFolder) { if (_client.OfflineMode) { _client.Log(IMAPBase.LogTypeEnum.WARN, "Cannot copy messages in offline mode."); return; } string cmd = "UID COPY {0} \"{1}\"\r\n"; ArrayList result = new ArrayList(); _client._imap.SendAndReceive(String.Format(cmd, msg.Uid, destFolder.FolderPath), ref result); foreach (string s in result) { if (s.Contains("OK")) { // if the copy was successful, tell the destination folder to refresh its message UID list. destFolder.GetMessageIDs(false); int msgCount = destFolder.Messages.Count; // the copy function puts the new message at the end of the folder so lets automatically // load the data for the copy. If for some reason during the folder refresh another new message // was found and added making the copied message not the last one in the folder, thats ok // because as soon as the content is accessed the data will be loaded automatically if (msgCount > 0) destFolder.Messages[msgCount - 1].RefreshData(msg.ContentLoaded, true); _client.UpdateCache(true); _client.Log(IMAPBase.LogTypeEnum.INFO, String.Format("Message with UID {0} successfully copied to folder \"{1}\"", msg.Uid, destFolder.FolderName)); break; } } }
/// <summary> /// Appends new message to end of this folder /// </summary> /// <param name="msg"></param> /// <param name="content">The content of the message</param> public void AppendMessage(IMAPMessage msg, string content) { this.Select(); // first lets determine what the UID of the new message should be int uid = _messages.Count > 0 ? _messages[_messages.Count - 1].Uid : 0; uid++; msg.Uid = uid; //string cmd = "APPEND \"{0}\" (\\Seen) {0}\r\n"; ArrayList result = new ArrayList(); _client._imap.SendRaw("APPEND \""+FolderPath+"\" (\\Seen) {"+uid+"}\r\n", true); //if (!result[0].ToString().StartsWith("+")) //{ // _client.Log(IMAPBase.LogTypeEnum.ERROR, "Invalid response from server"); // return; //} //_client._imap.ReadLine(); StringBuilder sb = new StringBuilder(); sb.AppendFormat("Date: {0}{1}", "Mon, 7 Feb 1994 21:52:25 -0800 (PST)", Environment.NewLine); sb.AppendFormat("From: {0}{1}", msg.From[0], Environment.NewLine); sb.AppendFormat("Subject: {0}{1}", msg.Subject, Environment.NewLine); sb.Append("To: "); foreach (IMAPMailAddress addr in msg.To) sb.AppendFormat("{0}, ", addr); sb.Remove(sb.Length - 2, 1); sb.Append(Environment.NewLine); sb.AppendFormat("Message-Id: <{0}@{1}>{2}", msg.Date.Ticks, msg.From[0].ToString().Substring(msg.From[0].ToString().IndexOf("@")+1).Replace(">",""),Environment.NewLine); sb.AppendLine("MIME-Version: 1.0"); sb.AppendLine("Content-Type: TEXT/PLAIN; CHARSET=US-ASCII"); sb.AppendLine(); sb.AppendLine(content); sb.AppendLine("\r\n"); result.Clear(); _client.Log(IMAPBase.LogTypeEnum.INFO, sb.ToString()); _client._imap.SendRaw(sb.ToString(), false); }
/// <summary> /// Gets the UIDs for each message in this folder, and populates the Messages collection with IMAPMessage objects /// </summary> internal int[] GetMessageIDs(bool newOnly) { List<int> newMsgIDs = new List<int>(); if (this._client == null) return null; if (this._client.OfflineMode) return null; IMAPClient c = this._client; if (!String.IsNullOrEmpty(_folderPath) || !_folderPath.Equals("\"\"")) { string path = ""; if (_folderPath.Contains(" ")) path = "\"" + _folderPath + "\""; else path = _folderPath; //if (!this.IsCurrentlyExamined) c._imap.ExamineFolder(this); List<int> ids = c._imap.GetSelectedFolderMessageIDs(newOnly); //_messages.Clear(); foreach (int id in ids) { bool found = false; foreach (IMAPMessage m in _messages) { if (m.Uid == id) found = true; } if (!found) { IMAPMessage msg = new IMAPMessage(); msg.Uid = id; msg.Folder = this; msg._client = c; _messages.Add(msg); newMsgIDs.Add(id); c.Log(IMAPBase.LogTypeEnum.INFO, String.Format("Added message UID {0} to folder {1}", id, this.FolderPath)); } } } if (_client.Config.AutoGetMsgID) { foreach (IMAPFolder f in _subFolders) { f.GetMessageIDs(newOnly); } } //_client._messageCount += _messages.Count; //foreach (IMAPMessage msg in _messages) //{ // //ArrayList headerResults = new ArrayList(); // //c._imap.FetchPartHeader(msg.Uid.ToString(), "0", headerResults); // c._imap.FetchMessageObject(msg, false); //} return newMsgIDs.ToArray(); }
/// <summary> /// Gets the UIDs for each message in this folder, and populates the Messages collection with IMAPMessage objects /// </summary> internal int[] GetMessageIDs(bool newOnly) { List <int> newMsgIDs = new List <int>(); if (this._client == null) { return(null); } if (this._client.OfflineMode) { return(null); } IMAPClient c = this._client; if (!String.IsNullOrEmpty(_folderPath) || !_folderPath.Equals("\"\"")) { string path = ""; if (_folderPath.Contains(" ")) { path = "\"" + _folderPath + "\""; } else { path = _folderPath; } //if (!this.IsCurrentlyExamined) c._imap.ExamineFolder(this); List <int> ids = c._imap.GetSelectedFolderMessageIDs(newOnly); //_messages.Clear(); foreach (int id in ids) { bool found = false; foreach (IMAPMessage m in _messages) { if (m.Uid == id) { found = true; } } if (!found) { IMAPMessage msg = new IMAPMessage(); msg.Uid = id; msg.Folder = this; msg._client = c; _messages.Add(msg); newMsgIDs.Add(id); c.Log(IMAPBase.LogTypeEnum.INFO, String.Format("Added message UID {0} to folder {1}", id, this.FolderPath)); } } } if (_client.Config.AutoGetMsgID) { foreach (IMAPFolder f in _subFolders) { f.GetMessageIDs(newOnly); } } //_client._messageCount += _messages.Count; //foreach (IMAPMessage msg in _messages) //{ // //ArrayList headerResults = new ArrayList(); // //c._imap.FetchPartHeader(msg.Uid.ToString(), "0", headerResults); // c._imap.FetchMessageObject(msg, false); //} return(newMsgIDs.ToArray()); }
/// <summary> /// Retrieves the header information from the server and populates the IMAPMessage objects properties /// </summary> /// <param name="msg"></param> /// <param name="partID"></param> /// <returns></returns> public bool ProcessMessageHeader(IMAPMessage msg, int partID) { Dictionary<string, string> headerData = new Dictionary<string, string>(); string cmd = "UID FETCH {0} BODY[{1}]\r\n"; cmd = String.Format(cmd, msg.Uid, partID > 0 ? partID + ".MIME" : "HEADER"); ArrayList result = new ArrayList(); ArrayList lineToProcess = new ArrayList(); SendAndReceive(cmd, ref result); string temp = ""; for (int i = 0; i < result.Count; i++) { // start of response, just skip it if (result[i].ToString().StartsWith("*") || result[i].ToString() == String.Empty || result[i].ToString().StartsWith(" ") || result[i].ToString().StartsWith("IMAP")) continue; temp = result[i].ToString(); // check each line after this line, looking for a tab or space. this indicates that those lines // are associated with the line in temp and should be appended. The loop ends when neither a tab or a space // is found, and the loop should not even be entered if one of those characters are not found. string currentLine = (i + 1 < result.Count - 1) ? result[i + 1].ToString() : ""; while (currentLine.StartsWith("\t") || currentLine.StartsWith(" ")) { if (String.IsNullOrEmpty(currentLine)) break; if (currentLine.StartsWith(" ")) temp += currentLine.TrimEnd(); else temp += currentLine.Trim(); i++; currentLine = (i + 1 < result.Count - 1) ? result[i + 1].ToString() : ""; } lineToProcess.Add(temp); } // now we process each data line into its name and value foreach (string line in lineToProcess) { int idx = line.IndexOf(":"); if (idx == -1) continue; string name = line.Substring(0, idx).Replace("-",""); int len = line.Length - idx; string value = line.Substring(idx + 1, line.Length - idx-1); // if a certain data item is already there then we just append the additional data. // this usually occurs with the Received: field if (headerData.ContainsKey(name)) { headerData[name] += value; } else { headerData.Add(name, value); } } foreach (string name in headerData.Keys) { // process special cases first if (name.ToLower().Equals("to")) { msg.SetPropValue(name,ParseAddresses(headerData[name].ToString())); continue; } if (name.ToLower().Equals("from")) { msg.SetPropValue(name, ParseAddresses(headerData[name].ToString())); continue; } if (name.ToLower().Equals("cc")) { msg.SetPropValue(name, ParseAddresses(headerData[name].ToString())); continue; } if (name.ToLower().Equals("bcc")) { msg.SetPropValue(name, ParseAddresses(headerData[name].ToString())); continue; } if (name.ToLower().Equals("date")) { string date = headerData[name].ToString(); // special processing needed for gmail. the format they send the date in does not automatically convert if (date.IndexOf(" (") > 0) { date = date.Substring(0, headerData[name].IndexOf("(")); } DateTime dt = new DateTime(); DateTime.TryParse(date, out dt); msg.SetPropValue(name, dt); continue; } if (!msg.SetPropValue(name, headerData[name].ToString().Trim())) { //if (!name.StartsWith("X")) // Log(LogTypeEnum.WARN, String.Format("IMAPMessage does not contain property for {0}", name)); } } msg.HeaderLoaded = true; if (msg.ContentType == null) msg.ContentType = "text/plain"; return true; }
/// <summary> /// Retreive the flags for the specified message /// </summary> /// <param name="msg"></param> public void ProcessMessageFlags(IMAPMessage msg) { string cmd = "UID FETCH {0} FLAGS\r\n"; ArrayList result = new ArrayList(); SendAndReceive(String.Format(cmd, msg.Uid), ref result); foreach (string s in result) { if (s.StartsWith("*")) { if (s.ToLower().Contains(@"\seen")) { msg.Flags.New = false; } else if (s.ToLower().Contains(@"\unseen")) msg.Flags.New = true; if (s.ToLower().Contains(@"\answered")) msg.Flags.Answered = true; else msg.Flags.Answered = false; if (s.ToLower().Contains(@"\deleted")) msg.Flags.Deleted = true; else msg.Flags.Deleted = false; if (s.ToLower().Contains(@"\draft")) msg.Flags.Draft = true; else msg.Flags.Draft = false; if (s.ToLower().Contains(@"\recent")) msg.Flags.Recent = true; else msg.Flags.Recent = false; } } }
/// <summary> /// Takes the IMAPMessageContent objects created in ProcessBodyStructure and downloads the content /// </summary> /// <param name="msg"></param> public void ProcessBodyParts(IMAPMessage msg) { List<IMAPMessageContent> processedContent = new List<IMAPMessageContent>(); foreach (IMAPMessageContent content in msg._bodyParts) { if (content.ContentDisposition != null || content.ContentDescription != null) { string disp = content.ContentDisposition == null ? "" : content.ContentDisposition; if (disp.ToLower().Contains("inline") && (content.ContentId != null || content.ContentDescription !=null)) { // this is an embedded image. IMAPFileAttachment image = new IMAPFileAttachment(); image.FileData = content.BinaryData; image.FileEncoding = content.ContentTransferEncoding; image.FileName = content.ContentId == null ? "" : content.ContentId.Replace("<", "").Replace(">", ""); if (content.ContentId == null && content.ContentDescription != null) image.FileName = content.ContentDescription; image.FileSize = content.BinaryData.Length; image.FileType = content.ContentType.Substring(0, content.ContentType.IndexOf(";") > -1 ? content.ContentType.IndexOf(";") : content.ContentType.Length); msg._embedded.Add(image); processedContent.Add(content); } else if (content.ContentId != null || content.ContentDescription != null) { // this is an attached file IMAPFileAttachment file = new IMAPFileAttachment(); file.FileData = content.BinaryData; file.FileEncoding = content.ContentTransferEncoding; file.FileName = content.ContentId== null ? "" : content.ContentId.Replace("<", "").Replace(">", ""); if (content.ContentId == null && content.ContentDescription != null) file.FileName = content.ContentDescription; file.FileSize = content.BinaryData.Length; file.FileType = content.ContentType.Substring(0, content.ContentType.IndexOf(";") > -1 ? content.ContentType.IndexOf(";") : content.ContentType.Length); msg._attachments.Add(file); processedContent.Add(content); } } } foreach (IMAPMessageContent content in processedContent) msg._bodyParts.Remove(content); msg.ContentLoaded = true; }
/// <summary> /// Downloads the entire body of the message from the server. includes content and attachments. /// Structure is then parsed and the various data items are seperated and stored in the message object. /// </summary> /// <param name="msg"></param> public void ProcessBodyContent(IMAPMessage msg) { string cmd = "UID FETCH {0} BODY[]\r\n"; ArrayList result = new ArrayList(); Log(LogTypeEnum.INFO, String.Format("Downloading body of message {0} in folder {1}", msg.Uid, msg.Folder.FolderName)); // pausing the logger because otherwise we would be logging all of the binary data for attachments which slows // down the processing quite noticably. Can be unpaused for debugging purposes, but make sure to re-pause it. _logger.Paused = true; SendAndReceive(String.Format(cmd, msg.Uid), ref result); _logger.Paused = false; Log(LogTypeEnum.INFO, "Download complete"); bool withinSection = false; string boundary = ""; List<string> boundaryList = new List<string>(); boundaryList.Add(""); // first we need to extract the boundary string from the header // we use a collection because there might be other boundary demarcators in the content of the message and we // need to capture all of them if (msg.ContentType != null) { if (msg.ContentType.ToLower().Contains("boundary")) { boundaryList.Clear(); boundary = "--"; string t = msg.ContentType; int idx = t.ToLower().IndexOf("boundary"); int idx2 = t.IndexOf("=", idx); boundary += t.Substring(idx2 + 1).Replace("\"","").TrimStart(); int idx3 = boundary.IndexOf(";"); if (idx3 > -1) boundary = boundary.Substring(0, idx3); boundaryList.Add(boundary); if (boundary.Length > 10) boundary = boundary.Substring(0, 10); } } bool plainText = false; for (int i = 0; i < result.Count; i++) { string line = result[i].ToString(); //if (line.ToLower().Contains("content-type")) //{ // if (!line.ToLower().Contains("multipart")) // plainText = true; //} if (String.IsNullOrEmpty(msg.ContentType)) plainText = true; else if (msg.ContentType.ToLower().Contains("multipart")) plainText = false; else plainText = true; if (line.Contains("OK Success") || line.Contains("OK FETCH completed")) break; if (!ListContainsString(boundaryList, line) && !withinSection) continue; if (ListContainsString(boundaryList, line) && BoundaryAllDashes(boundaryList.ToArray()) ? true : !line.EndsWith("--")) withinSection = true; if (withinSection) { IMAPMessageContent content = new IMAPMessageContent(); // first we process the section header data, stopping when we hit an empty line if (!plainText) { while (!line.Equals(String.Empty)) { line = result[++i].ToString(); if (line.ToLower().Contains("boundary")) { string newBoundary = "--"; string t = line; int idx = t.ToLower().IndexOf("boundary"); int idx2 = t.IndexOf("=", idx); newBoundary += t.Substring(idx2 + 1).Replace("\"", ""); boundaryList.Add(newBoundary); } string[] headerField = GetNameValue(line); if (headerField.Length == 2) { PropertyInfo[] props = content.GetType().GetProperties(); foreach (PropertyInfo pinfo in props) { if (pinfo.Name.ToLower().Equals(headerField[0].ToLower())) { pinfo.SetValue(content, headerField[1].Trim(), null); break; } } } if (i + 2 < result.Count - 1) { if (result[i + 1].ToString().Equals("") && result[i + 2].ToString().Equals("")) break; } else { break; } } } else { content.ContentType = msg.ContentType; } bool isGarbageSection = content.ContentType != null ? content.ContentType.ToLower().Contains("multipart") : false; StringBuilder contentString = new StringBuilder(); bool hardEndOfMsg = false; // now we are in the section body. continue until we hit the next section while (true) { if (i + 1 > result.Count - 1) { hardEndOfMsg = true; break; } line = result[++i].ToString(); if (boundary.Equals("")) { if (line.StartsWith(")") || line.StartsWith(String.Format(" UID {0})",msg.Uid))) { // lets make sure this is the last ')' in the message, just to be safe int endOfMessage = 0; for (int k = result.Count - 1; k > 0; k--) { if (result[k].ToString().StartsWith(")") || result[k].ToString().StartsWith(String.Format(" UID {0})", msg.Uid))) { endOfMessage = k; break; } } if (i == endOfMessage) break; } } else if (ListContainsString(boundaryList, line)) { i--; break; } contentString.AppendLine(line); } if (!isGarbageSection && !hardEndOfMsg) { if (content.ContentTransferEncoding != null ? content.ContentTransferEncoding.ToLower().Equals("base64"): false) { content.BinaryData = Convert.FromBase64String(contentString.ToString()); if (content.ContentType.Contains("text/plain;") && content.ContentDisposition != "attachment;") { content.TextData = System.Text.Encoding.UTF8.GetString(content.BinaryData); } } else { content.TextData = contentString.ToString(); } msg._bodyParts.Add(content); Log(LogTypeEnum.INFO, String.Format("Added content section of type {0}", content.ContentType)); } withinSection = false; } } if (msg._bodyParts.Count == 0) { Log(LogTypeEnum.ERROR, "No body parts added for this message"); } else { foreach (IMAPMessageContent content in msg._bodyParts) { if (String.IsNullOrEmpty(content.ContentType)) Log(LogTypeEnum.ERROR, "No content type found for this part"); } } }
/// <summary> /// Marks the specified message as \Seen on the server /// </summary> /// <param name="msg"></param> public void MarkMessageAsRead(IMAPMessage msg) { string cmd = "UID STORE {0} +FLAGS (\\Seen)\r\n"; ArrayList result = new ArrayList(); SendAndReceive(String.Format(cmd, msg.Uid), ref result); if (result[0].ToString().ToLower().Contains("ok")) msg.Flags.New = false; }