/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2010 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/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 System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using Greenshot.Capturing; using Greenshot.Configuration; using Greenshot.Plugin; using Microsoft.Win32; /// /// Author: Andrew Baker /// Datum: 10.03.2006 /// Available from: http://www.vbusers.com/codecsharp/codeget.asp?ThreadID=71&PostID=1 /// namespace Greenshot.Helpers { #region Public MapiMailMessage Class /// /// Represents an email message to be sent through MAPI. /// public class MapiMailMessage { private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(MapiMailMessage)); private const string MAPI_LOCATION = @"SOFTWARE\Microsoft\Windows Messaging Subsystem"; private const string MAPI_KEY = @"MAPI"; public static bool HasMAPI() { using (RegistryKey key = Registry.LocalMachine.OpenSubKey(MAPI_LOCATION, false)) { if (key != null) { return "1".Equals(key.GetValue(MAPI_KEY, "0")); } else { return false; } } } /// /// Helper Method for creating an Email with Attachment /// /// Path to file /// public static void SendImage(string fullPath, string title, bool deleteFileOnExit) { MapiMailMessage message = new MapiMailMessage(title, null); message.Files.Add(fullPath); message.ShowDialog(deleteFileOnExit); } /// /// Helper Method for creating an Email with Image Attachment /// /// The image to send /// ICaptureDetails public static void SendImage(Image image, ICaptureDetails captureDetails) { AppConfig conf = AppConfig.GetInstance(); string filename = FilenameHelper.GetFilenameFromPattern(conf.Output_File_FilenamePattern, conf.Output_File_Format, captureDetails); string tmpFile = Path.Combine(Path.GetTempPath(),filename); LOG.Debug("Creating TMP File for Email: " + tmpFile); // Catching any exception to prevent that the user can't write in the directory. // This is done for e.g. bugs #2974608, #2963943, #2816163, #2795317, #2789218 try { ImageOutput.Save(image, tmpFile, captureDetails); } catch (Exception e) { // Show the problem MessageBox.Show(e.Message, "Error"); // when save failed we present a SaveWithDialog tmpFile = ImageOutput.SaveWithDialog(image, captureDetails); } if (tmpFile != null) { // Send the email and Cleanup the tmp files on exit SendImage(tmpFile, captureDetails.Title, true); } } #region Private MapiFileDescriptor Class [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] private class MapiFileDescriptor { public int reserved = 0; public int flags = 0; public int position = 0; public string path = null; public string name = null; public IntPtr type = IntPtr.Zero; } #endregion Private MapiFileDescriptor Class #region Enums /// /// Specifies the valid RecipientTypes for a Recipient. /// public enum RecipientType : int { /// /// 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 string _subject; private string _body; private RecipientCollection _recipientCollection; private List _files; private ManualResetEvent _manualResetEvent; #endregion Member Variables #region Constructors /// /// Creates a blank mail message. /// public MapiMailMessage() { _files = new List(); _recipientCollection = 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 { return _subject; } set { _subject = value; } } /// /// Gets or sets the body of this mail message. /// public string Body { get { return _body; } set { _body = value; } } /// /// Gets the recipient list for this mail message. /// public RecipientCollection Recipients { get { return _recipientCollection; } } /// /// Gets the file list for this mail message. /// public List Files { get { return _files; } } #endregion Public Properties #region Public Methods /// /// Displays the mail message dialog asynchronously. /// public void ShowDialog(bool deleteFilesOnExit) { // Create the mail message in an STA thread Thread t = new Thread(new ParameterizedThreadStart(_ShowMail)); t.IsBackground = true; t.Name = Application.ProductName; t.SetApartmentState(ApartmentState.STA); t.Start(deleteFilesOnExit); // only return when the new thread has built it's interop representation _manualResetEvent.WaitOne(); _manualResetEvent.Reset(); } #endregion Public Methods #region Private Methods /// /// Sends the mail message. /// private void _ShowMail(object deleteFilesOnExit) { MAPIHelperInterop.MapiMessage message = new MAPIHelperInterop.MapiMessage(); using (RecipientCollection.InteropRecipientCollection interopRecipients = _recipientCollection.GetInteropRepresentation()) { message.Subject = _subject; message.NoteText = _body; message.Recipients = interopRecipients.Handle; message.RecipientCount = _recipientCollection.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); if ((bool)deleteFilesOnExit) { foreach(string file in _files) { try { if (File.Exists(file)) { LOG.Debug("Deleting file " + file); File.Delete(file); } } catch (Exception e) { LOG.Error("Can't delete file " + file, e); } } } } MAPI_CODES errorCode = (MAPI_CODES)Enum.ToObject(typeof(MAPI_CODES), error); // Check for error if (errorCode != MAPI_CODES.SUCCESS && errorCode != MAPI_CODES.USER_ABORT) { _LogErrorMapi(errorCode); } } } /// /// 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 int runptr = (int)message.Files; // Release each file for (int i = 0; i < message.FileCount; i++) { Marshal.DestroyStructure((IntPtr)runptr, fileDescType); runptr += 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(); mfd.position = -1; int runptr = (int)ptra; for (int i = 0; i < _files.Count; i++) { string path = _files[i] as string; mfd.name = Path.GetFileName(path); mfd.path = path; Marshal.StructureToPtr(mfd, (IntPtr)runptr, false); runptr += 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 void _LogErrorMapi(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; } LOG.Error("Error sending MAPI Email. Error: " + error + " (code = " + errorCode + ")."); } #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 Constants public const int MAPI_LOGON_UI = 0x1; #endregion Constants #region APIs [DllImport("MAPI32.DLL", CharSet = CharSet.Ansi)] public static extern int MAPILogon(IntPtr hwnd, string prf, string pw, int flg, int rsv, ref IntPtr sess); #endregion APIs #region Structs [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class MapiMessage { public int Reserved = 0; public string Subject = null; public string NoteText = null; public string MessageType = null; public string DateReceived = null; public string ConversationID = null; public int Flags = 0; public IntPtr Originator = IntPtr.Zero; public int RecipientCount = 0; public IntPtr Recipients = IntPtr.Zero; public int FileCount = 0; public IntPtr Files = IntPtr.Zero; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class MapiRecipDesc { public int Reserved = 0; public int RecipientClass = 0; public string Name = null; public string Address = null; public int eIDSize = 0; public IntPtr EntryID = IntPtr.Zero; } [DllImport("MAPI32.DLL")] 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 = null; /// /// The display name of this recipient. /// public string DisplayName = null; /// /// 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) { this.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) { this.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) { this.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) { this.Add(new Recipient(address, displayName, recipientType)); } /// /// Returns the recipient stored in this collection at the specified index. /// public Recipient this[int index] { get { return (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 IntPtr _handle; 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 int ptr = (int)_handle; foreach (Recipient native in outer) { MapiMailMessage.MAPIHelperInterop.MapiRecipDesc interop = native.GetInteropRepresentation(); // stick it in the memory block Marshal.StructureToPtr(interop, (IntPtr)ptr, false); ptr += size; } } #endregion Costructors #region Public Properties public IntPtr Handle { get { return _handle; } } #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 int ptr = (int)_handle; for (int i = 0; i < _count; i++) { Marshal.DestroyStructure((IntPtr)ptr, type); ptr += size; } // free the memory Marshal.FreeHGlobal(_handle); _handle = IntPtr.Zero; _count = 0; } } #endregion Public Methods } } #endregion Public RecipientCollection Class }