/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Greenshot.IniFile; using Greenshot.Plugin; using GreenshotPlugin.Core; using log4net; using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; namespace Greenshot.Helpers { /// /// Author: Andrew Baker /// Datum: 10.03.2006 /// Available from here /// #region Public MapiMailMessage Class /// /// Represents an email message to be sent through MAPI. /// public class MapiMailMessage : IDisposable { private static readonly ILog Log = LogManager.GetLogger(typeof(MapiMailMessage)); private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection(); /// /// Helper Method for creating an Email with Attachment /// /// Path to file /// public static void SendImage(string fullPath, string title) { using (MapiMailMessage message = new MapiMailMessage(title, null)) { message.Files.Add(fullPath); if (!string.IsNullOrEmpty(CoreConfig.MailApiTo)) { message.Recipients.Add(new Recipient(CoreConfig.MailApiTo, RecipientType.To)); } if (!string.IsNullOrEmpty(CoreConfig.MailApiCC)) { message.Recipients.Add(new Recipient(CoreConfig.MailApiCC, RecipientType.CC)); } if (!string.IsNullOrEmpty(CoreConfig.MailApiBCC)) { message.Recipients.Add(new Recipient(CoreConfig.MailApiBCC, RecipientType.BCC)); } message.ShowDialog(); } } /// /// Helper Method for creating an Email with Image Attachment /// /// The image to send /// ICaptureDetails public static void SendImage(ISurface surface, ICaptureDetails captureDetails) { string tmpFile = ImageOutput.SaveNamedTmpFile(surface, captureDetails, new SurfaceOutputSettings()); if (tmpFile != null) { // Store the list of currently active windows, so we can make sure we show the email window later! var windowsBefore = WindowDetails.GetVisibleWindows(); //bool isEmailSend = false; //if (EmailConfigHelper.HasOutlook() && (CoreConfig.OutputEMailFormat == EmailFormat.OUTLOOK_HTML || CoreConfig.OutputEMailFormat == EmailFormat.OUTLOOK_TXT)) { // isEmailSend = OutlookExporter.ExportToOutlook(tmpFile, captureDetails); //} if (/*!isEmailSend &&*/ EmailConfigHelper.HasMapi()) { // Fallback to MAPI // Send the email SendImage(tmpFile, captureDetails.Title); } WindowDetails.ActiveNewerWindows(windowsBefore); } } #region Private MapiFileDescriptor Class [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] private class MapiFileDescriptor { public int reserved = 0; public int flags = 0; public int position; public string path; public string name; public IntPtr type = IntPtr.Zero; } #endregion Private MapiFileDescriptor Class #region Enums /// /// Specifies the valid RecipientTypes for a Recipient. /// public enum RecipientType { /// /// Recipient will be in the TO list. /// To = 1, /// /// Recipient will be in the CC list. /// CC = 2, /// /// Recipient will be in the BCC list. /// BCC = 3 }; #endregion Enums #region Member Variables private readonly ManualResetEvent _manualResetEvent; #endregion Member Variables #region Constructors /// /// Creates a blank mail message. /// public MapiMailMessage() { Files = new List(); Recipients = new RecipientCollection(); _manualResetEvent = new ManualResetEvent(false); } /// /// Creates a new mail message with the specified subject. /// public MapiMailMessage(string subject) : this() { Subject = subject; } /// /// Creates a new mail message with the specified subject and body. /// public MapiMailMessage(string subject, string body) : this() { Subject = subject; Body = body; } #endregion Constructors #region Public Properties /// /// Gets or sets the subject of this mail message. /// public string Subject { get; set; } /// /// Gets or sets the body of this mail message. /// public string Body { get; set; } /// /// Gets the recipient list for this mail message. /// public RecipientCollection Recipients { get; private set; } /// /// Gets the file list for this mail message. /// public List Files { get; } #endregion Public Properties #region Public Methods /// /// Displays the mail message dialog asynchronously. /// public void ShowDialog() { // Create the mail message in an STA thread var thread = new Thread(_ShowMail) { IsBackground = true, Name = "Create MAPI mail" }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); // only return when the new thread has built it's interop representation _manualResetEvent.WaitOne(); _manualResetEvent.Reset(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion Public Methods #region Private Methods protected virtual void Dispose(bool disposing) { if (!disposing) { return; } _manualResetEvent?.Close(); } /// /// Sends the mail message. /// private void _ShowMail() { var message = new MapiHelperInterop.MapiMessage(); using (var interopRecipients = Recipients.GetInteropRepresentation()) { message.Subject = Subject; message.NoteText = Body; message.Recipients = interopRecipients.Handle; message.RecipientCount = Recipients.Count; // Check if we need to add attachments if (Files.Count > 0) { // Add attachments message.Files = _AllocAttachments(out message.FileCount); } // Signal the creating thread (make the remaining code async) _manualResetEvent.Set(); const int MAPI_DIALOG = 0x8; //const int MAPI_LOGON_UI = 0x1; int error = MapiHelperInterop.MAPISendMail(IntPtr.Zero, IntPtr.Zero, message, MAPI_DIALOG, 0); if (Files.Count > 0) { // Deallocate the files _DeallocFiles(message); } MAPI_CODES errorCode = (MAPI_CODES)Enum.ToObject(typeof(MAPI_CODES), error); // Check for error if (errorCode == MAPI_CODES.SUCCESS || errorCode == MAPI_CODES.USER_ABORT) { return; } string errorText = GetMapiError(errorCode); Log.Error("Error sending MAPI Email. Error: " + errorText + " (code = " + errorCode + ")."); MessageBox.Show(errorText, "Mail (MAPI) destination", MessageBoxButtons.OK, MessageBoxIcon.Error); // Recover from bad settings, show again if (errorCode != MAPI_CODES.INVALID_RECIPS) { return; } Recipients = new RecipientCollection(); _ShowMail(); } } /// /// Deallocates the files in a message. /// /// The message to deallocate the files from. private void _DeallocFiles(MapiHelperInterop.MapiMessage message) { if (message.Files != IntPtr.Zero) { Type fileDescType = typeof(MapiFileDescriptor); int fsize = Marshal.SizeOf(fileDescType); // Get the ptr to the files IntPtr runptr = message.Files; // Release each file for (int i = 0; i < message.FileCount; i++) { Marshal.DestroyStructure(runptr, fileDescType); runptr = new IntPtr(runptr.ToInt64() + fsize); } // Release the file Marshal.FreeHGlobal(message.Files); } } /// /// Allocates the file attachments /// /// /// private IntPtr _AllocAttachments(out int fileCount) { fileCount = 0; if (Files == null) { return IntPtr.Zero; } if ((Files.Count <= 0) || (Files.Count > 100)) { return IntPtr.Zero; } Type atype = typeof(MapiFileDescriptor); int asize = Marshal.SizeOf(atype); IntPtr ptra = Marshal.AllocHGlobal(Files.Count * asize); MapiFileDescriptor mfd = new MapiFileDescriptor { position = -1 }; IntPtr runptr = ptra; foreach (string path in Files) { mfd.name = Path.GetFileName(path); mfd.path = path; Marshal.StructureToPtr(mfd, runptr, false); runptr = new IntPtr(runptr.ToInt64() + asize); } fileCount = Files.Count; return ptra; } private enum MAPI_CODES { SUCCESS = 0, USER_ABORT = 1, FAILURE = 2, LOGIN_FAILURE = 3, DISK_FULL = 4, INSUFFICIENT_MEMORY = 5, BLK_TOO_SMALL = 6, TOO_MANY_SESSIONS = 8, TOO_MANY_FILES = 9, TOO_MANY_RECIPIENTS = 10, ATTACHMENT_NOT_FOUND = 11, ATTACHMENT_OPEN_FAILURE = 12, ATTACHMENT_WRITE_FAILURE = 13, UNKNOWN_RECIPIENT = 14, BAD_RECIPTYPE = 15, NO_MESSAGES = 16, INVALID_MESSAGE = 17, TEXT_TOO_LARGE = 18, INVALID_SESSION = 19, TYPE_NOT_SUPPORTED = 20, AMBIGUOUS_RECIPIENT = 21, MESSAGE_IN_USE = 22, NETWORK_FAILURE = 23, INVALID_EDITFIELDS = 24, INVALID_RECIPS = 25, NOT_SUPPORTED = 26, NO_LIBRARY = 999, INVALID_PARAMETER = 998 } /// /// Logs any Mapi errors. /// private string GetMapiError(MAPI_CODES errorCode) { string error = string.Empty; switch (errorCode) { case MAPI_CODES.USER_ABORT: error = "User Aborted."; break; case MAPI_CODES.FAILURE: error = "MAPI Failure."; break; case MAPI_CODES.LOGIN_FAILURE: error = "Login Failure."; break; case MAPI_CODES.DISK_FULL: error = "MAPI Disk full."; break; case MAPI_CODES.INSUFFICIENT_MEMORY: error = "MAPI Insufficient memory."; break; case MAPI_CODES.BLK_TOO_SMALL: error = "MAPI Block too small."; break; case MAPI_CODES.TOO_MANY_SESSIONS: error = "MAPI Too many sessions."; break; case MAPI_CODES.TOO_MANY_FILES: error = "MAPI too many files."; break; case MAPI_CODES.TOO_MANY_RECIPIENTS: error = "MAPI too many recipients."; break; case MAPI_CODES.ATTACHMENT_NOT_FOUND: error = "MAPI Attachment not found."; break; case MAPI_CODES.ATTACHMENT_OPEN_FAILURE: error = "MAPI Attachment open failure."; break; case MAPI_CODES.ATTACHMENT_WRITE_FAILURE: error = "MAPI Attachment Write Failure."; break; case MAPI_CODES.UNKNOWN_RECIPIENT: error = "MAPI Unknown recipient."; break; case MAPI_CODES.BAD_RECIPTYPE: error = "MAPI Bad recipient type."; break; case MAPI_CODES.NO_MESSAGES: error = "MAPI No messages."; break; case MAPI_CODES.INVALID_MESSAGE: error = "MAPI Invalid message."; break; case MAPI_CODES.TEXT_TOO_LARGE: error = "MAPI Text too large."; break; case MAPI_CODES.INVALID_SESSION: error = "MAPI Invalid session."; break; case MAPI_CODES.TYPE_NOT_SUPPORTED: error = "MAPI Type not supported."; break; case MAPI_CODES.AMBIGUOUS_RECIPIENT: error = "MAPI Ambiguous recipient."; break; case MAPI_CODES.MESSAGE_IN_USE: error = "MAPI Message in use."; break; case MAPI_CODES.NETWORK_FAILURE: error = "MAPI Network failure."; break; case MAPI_CODES.INVALID_EDITFIELDS: error = "MAPI Invalid edit fields."; break; case MAPI_CODES.INVALID_RECIPS: error = "MAPI Invalid Recipients."; break; case MAPI_CODES.NOT_SUPPORTED: error = "MAPI Not supported."; break; case MAPI_CODES.NO_LIBRARY: error = "MAPI No Library."; break; case MAPI_CODES.INVALID_PARAMETER: error = "MAPI Invalid parameter."; break; } return error; } #endregion Private Methods #region Private MAPIHelperInterop Class /// /// Internal class for calling MAPI APIs /// internal class MapiHelperInterop { #region Constructors /// /// Private constructor. /// private MapiHelperInterop() { // Intenationally blank } #endregion Constructors #region Structs [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class MapiMessage { public int Reserved = 0; public string Subject; public string NoteText; public string MessageType = null; public string DateReceived = null; public string ConversationID = null; public int Flags = 0; public IntPtr Originator = IntPtr.Zero; public int RecipientCount; public IntPtr Recipients = IntPtr.Zero; public int FileCount; public IntPtr Files = IntPtr.Zero; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class MapiRecipDesc { public int Reserved = 0; public int RecipientClass; public string Name; public string Address; public int eIDSize = 0; public IntPtr EntryID = IntPtr.Zero; } [DllImport("MAPI32.DLL", SetLastError = true, CharSet=CharSet.Ansi)] public static extern int MAPISendMail(IntPtr session, IntPtr hwnd, MapiMessage message, int flg, int rsv); #endregion Structs } #endregion Private MAPIHelperInterop Class } #endregion Public MapiMailMessage Class #region Public Recipient Class /// /// Represents a Recipient for a MapiMailMessage. /// public class Recipient { #region Public Properties /// /// The email address of this recipient. /// public string Address; /// /// The display name of this recipient. /// public string DisplayName; /// /// How the recipient will receive this message (To, CC, BCC). /// public MapiMailMessage.RecipientType RecipientType = MapiMailMessage.RecipientType.To; #endregion Public Properties #region Constructors /// /// Creates a new recipient with the specified address. /// public Recipient(string address) { Address = address; } /// /// Creates a new recipient with the specified address and display name. /// public Recipient(string address, string displayName) { Address = address; DisplayName = displayName; } /// /// Creates a new recipient with the specified address and recipient type. /// public Recipient(string address, MapiMailMessage.RecipientType recipientType) { Address = address; RecipientType = recipientType; } /// /// Creates a new recipient with the specified address, display name and recipient type. /// public Recipient(string address, string displayName, MapiMailMessage.RecipientType recipientType) { Address = address; DisplayName = displayName; RecipientType = recipientType; } #endregion Constructors #region Internal Methods /// /// Returns an interop representation of a recepient. /// /// internal MapiMailMessage.MapiHelperInterop.MapiRecipDesc GetInteropRepresentation() { MapiMailMessage.MapiHelperInterop.MapiRecipDesc interop = new MapiMailMessage.MapiHelperInterop.MapiRecipDesc(); if (DisplayName == null) { interop.Name = Address; } else { interop.Name = DisplayName; interop.Address = Address; } interop.RecipientClass = (int)RecipientType; return interop; } #endregion Internal Methods } #endregion Public Recipient Class #region Public RecipientCollection Class /// /// Represents a colleciton of recipients for a mail message. /// public class RecipientCollection : CollectionBase { /// /// Adds the specified recipient to this collection. /// public void Add(Recipient value) { List.Add(value); } /// /// Adds a new recipient with the specified address to this collection. /// public void Add(string address) { Add(new Recipient(address)); } /// /// Adds a new recipient with the specified address and display name to this collection. /// public void Add(string address, string displayName) { Add(new Recipient(address, displayName)); } /// /// Adds a new recipient with the specified address and recipient type to this collection. /// public void Add(string address, MapiMailMessage.RecipientType recipientType) { Add(new Recipient(address, recipientType)); } /// /// Adds a new recipient with the specified address, display name and recipient type to this collection. /// public void Add(string address, string displayName, MapiMailMessage.RecipientType recipientType) { Add(new Recipient(address, displayName, recipientType)); } /// /// Returns the recipient stored in this collection at the specified index. /// public Recipient this[int index] => (Recipient)List[index]; internal InteropRecipientCollection GetInteropRepresentation() { return new InteropRecipientCollection(this); } /// /// Struct which contains an interop representation of a colleciton of recipients. /// internal struct InteropRecipientCollection : IDisposable { #region Member Variables private int _count; #endregion Member Variables #region Constructors /// /// Default constructor for creating InteropRecipientCollection. /// /// public InteropRecipientCollection(RecipientCollection outer) { _count = outer.Count; if (_count == 0) { Handle = IntPtr.Zero; return; } // allocate enough memory to hold all recipients int size = Marshal.SizeOf(typeof(MapiMailMessage.MapiHelperInterop.MapiRecipDesc)); Handle = Marshal.AllocHGlobal(_count * size); // place all interop recipients into the memory just allocated IntPtr ptr = Handle; foreach (Recipient native in outer) { MapiMailMessage.MapiHelperInterop.MapiRecipDesc interop = native.GetInteropRepresentation(); // stick it in the memory block Marshal.StructureToPtr(interop, ptr, false); ptr = new IntPtr(ptr.ToInt64() + size); } } #endregion Costructors #region Public Properties public IntPtr Handle { get; private set; } #endregion Public Properties #region Public Methods /// /// Disposes of resources. /// public void Dispose() { if (Handle != IntPtr.Zero) { Type type = typeof(MapiMailMessage.MapiHelperInterop.MapiRecipDesc); int size = Marshal.SizeOf(type); // destroy all the structures in the memory area IntPtr ptr = Handle; for (int i = 0; i < _count; i++) { Marshal.DestroyStructure(ptr, type); ptr = new IntPtr(ptr.ToInt64() + size); } // free the memory Marshal.FreeHGlobal(Handle); Handle = IntPtr.Zero; _count = 0; } } #endregion Public Methods } } #endregion Public RecipientCollection Class }