text: //
  // WebCheck Mail Agent
  //
  // A user specifies that they are to be notified via email when a web checked
  // object (usually a page) changes.
  //
  // When the subscription is the delivery agent will call the mail agent upon completion
  //  with a temporary ISubscriptionItem
  // 
  // Julian Jiggins (julianj), January 8, 1997
  //
  
  #include "private.h"
  #include "mapi.h"
  #include "smtp.h"
  #include "mlang.h"
  
  #include <mluisupp.h>
  
  #undef TF_THISMODULE
  #define TF_THISMODULE   TF_MAILAGENT
  
  //
  // Global strings
  // REVIEW move to a better place
  //
  #define MAIL_HANDLER    TEXT("Software\\Clients\\Mail")
  #define MAIL_ATHENA     TEXT("Internet Mail and News")
  #define SUBJECT_LINE TEXT("Subscription delivered")
  #define MESSAGE_PREFIX TEXT(" \r\nThe website you requested ")
  #define MESSAGE_SUFFIX TEXT(" has been delivered")
  
  #define ATHENA_SMTP_SERVER \
      TEXT("Software\\Microsoft\\Internet Mail and News\\Mail\\SMTP")
  #define NETSCAPE_SMTP_SERVER \
      TEXT("Software\\Netscape\\netscape Navigator\\Services\\SMTP_Server")
  #define EUDORA_COMMANDLINE TEXT("Software\\Qualcomm\\Eudora\\CommandLine")
  
  #define NOTE_TEXT_LENGTH 4096
  
  #define ENCODING_STRLEN 32
  
  //////////////////////////////////////////////////////////////////////////
  //
  // Email helper functions
  //
  //////////////////////////////////////////////////////////////////////////
  
  //
  // Returns a MemAlloc'd string with HTMLBreak inserted in place of '\d'.
  //
  void AddHTMLBreakText(LPSTR szText, LPSTR szHTMLBreak, LPSTR *lpHTMLText)
  {
      ASSERT(szText);
      ASSERT(szHTMLBreak);
      ASSERT(!*lpHTMLText);
  
      LPSTR lpTmp = NULL, lpTmp2 = NULL, lpHTMLAbstract = NULL;
      int cbCRs = 0;
      int cbLFs = 0;
      DWORD dwExtra = 0;
      
      //
      // Count number of carriage returns
      //
      for (lpTmp = szText; *lpTmp; lpTmp++)
      {
          if (*lpTmp == 0x0d)
              cbCRs++;
          if (*lpTmp == 0x0a)
              cbLFs++;
      }
      
      dwExtra = lstrlenA(szText) - cbCRs - cbLFs + cbCRs * lstrlenA(szHTMLBreak) + 1;
  
      //
      // Allocate appropriate size string
      //
      *lpHTMLText = lpHTMLAbstract = (LPSTR)MemAlloc(LPTR, dwExtra);
      if (!lpHTMLAbstract)
          return;
  
      // 
      // Create new HTML abstract string.
      //
      for (lpTmp = szText; *lpTmp; lpTmp++)
      {
          if (*lpTmp == 0x0d)
          {
              for (lpTmp2 = szHTMLBreak; *lpTmp2; lpTmp2++, lpHTMLAbstract++)
                  *lpHTMLAbstract = *lpTmp2;
          }
          else if (*lpTmp != 0x0a)
          {
              *lpHTMLAbstract = *lpTmp;
              lpHTMLAbstract++;
          }
      }
  
      *lpHTMLAbstract = '\0';
  
  }
  
  #ifdef DEBUG
  void DBG_OUTPUT_MAPI_ERROR(ULONG ul)
  {
      switch(ul)
      {
      case MAPI_E_LOGON_FAILURE: 
          DBG("MailAgent: MAPI LOGON FAILURE"); break;
      case MAPI_E_FAILURE:
          DBG("MailAgent: MAPI_E_FAILURE"); break;
      default: 
          DBG("MailAgent: Failed to send mail message"); break;
      }
  }
  #else
  #define DBG_OUTPUT_MAPI_ERROR(ul)
  #endif
  
  //
  // Build an HTML message containing a frameset that effectively inlines
  // the requested URL
  //
  BOOL BuildHTMLMessage(LPSTR szEmailAddress, LPSTR szName, LPSTR szURL, 
                        CHAR **ppHTMLMessage,  LPSTR szTitle, LPSTR szAbstract,
                        LPSTR szSrcCharset)
  {
      *ppHTMLMessage = NULL; // clear out parameter
      
      CHAR * lpBuffer = NULL;
  
      CHAR szWrapper[NOTE_TEXT_LENGTH];
      CHAR szMessageFormat[NOTE_TEXT_LENGTH];
      CHAR szMessageFormat2[NOTE_TEXT_LENGTH];
      CHAR szMessageText[NOTE_TEXT_LENGTH];
      CHAR szMessageHTML[NOTE_TEXT_LENGTH];
      CHAR szTextBreak[10];
      CHAR szHTMLBreak[10];
  
      //
      // Load the wrapper for the HTML message. This is the header stuff 
      // and multipart MIME and HTML goop
      //
      int iRet = MLLoadStringA(IDS_AGNT_HTMLMESSAGEWRAPPER, szWrapper, NOTE_TEXT_LENGTH);
      ASSERT(iRet > 0);
  
      if (szTitle != NULL) {
  
          // NOTE: Size is probably slightly larger than necessary due to %1's.
  
          LPSTR lpHTMLAbstract = NULL, lpNewAbstract = NULL;
          DWORD dwTotalSize = 0;
          //
          // load string for single HTML line break as well as tag on for custom email
          //
  
          MLLoadStringA(IDS_AGNT_EMAILMESSAGE, szMessageText, ARRAYSIZE(szMessageText));
  
          MLLoadStringA(IDS_AGNT_HTMLBREAKSINGLE, szHTMLBreak, ARRAYSIZE(szHTMLBreak));
  
          // 
          // Create new abstract string (szAbstract + email tagger)
          //
          dwTotalSize = lstrlenA(szAbstract) + lstrlenA(szMessageText) + 1;
  
          LPSTR szNewAbstract = (LPSTR)MemAlloc(LPTR, dwTotalSize * sizeof(CHAR));
          if (!szNewAbstract)
              return FALSE;
  
          lstrcpynA(szNewAbstract, szAbstract, dwTotalSize);
          StrCatBuffA(szNewAbstract, szMessageText, dwTotalSize);
          
          AddHTMLBreakText(szNewAbstract, szHTMLBreak, &lpHTMLAbstract);
          if (!lpHTMLAbstract) 
          {
              MemFree(szNewAbstract);
              return FALSE;
          }
              
          dwTotalSize = lstrlenA(szWrapper) + lstrlenA(szEmailAddress) + 
                              2*lstrlenA(szTitle) + lstrlenA(szNewAbstract) + lstrlenA(szSrcCharset) +
                              lstrlenA(lpHTMLAbstract) + lstrlenA(szURL) + 1;
  
          lpBuffer = (CHAR *)MemAlloc(LPTR, dwTotalSize * sizeof(CHAR));
          if (!lpBuffer)
              return FALSE;
  
          LPSTR lpArguments[6];
          lpArguments[0] = szEmailAddress;
          lpArguments[1] = szTitle;
          lpArguments[2] = szNewAbstract;
          lpArguments[3] = szSrcCharset;    // the charset of the HTML page
          lpArguments[4] = szURL;
          lpArguments[5] = lpHTMLAbstract;
  
          //
          // Reason for FormatMessage is that wsprintf is limited up to 1024 bytes
          //
  
          FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                  szWrapper, 0, 0, lpBuffer, dwTotalSize, (va_list *)&lpArguments[0]);
  
          MemFree(szNewAbstract);
          MemFree(lpHTMLAbstract);
  
      } else {
  
          //
          // Load line breaks for the plaintext and html messages
          //
          iRet = MLLoadStringA(IDS_AGNT_TEXTBREAK, szTextBreak, ARRAYSIZE(szTextBreak));
          ASSERT(iRet > 0);
          iRet = MLLoadStringA(IDS_AGNT_HTMLBREAK, szHTMLBreak, ARRAYSIZE(szHTMLBreak));
          ASSERT(iRet > 0);
  
          //
          // Load the actual text message to put sent
          //
          iRet = MLLoadStringA(IDS_AGNT_HTMLMESSAGETEXT, szMessageFormat, NOTE_TEXT_LENGTH);
          ASSERT(iRet > 0);
  
          iRet = MLLoadStringA(IDS_AGNT_HTMLMESSAGETEXT2, szMessageFormat2, NOTE_TEXT_LENGTH);
          ASSERT(iRet > 0);
  
          //
          // Insert the text messages into the wrapper. Note two message get
          // Once in the mime section for text/ascii and once in the 
          // noframes section of the text/html frameset. This is a work around
          // for clients (like Outlook) that think they can render HTML
          // but cannot really. 
          // The second message IDS_AGNT_HTMLMESSAGETEXT2 should NOT be localized
          // this is only going to be seen by Exchange users. In the future exchange
          // will handle html mail correct, so it acceptable that for example
          // Japanese Exchange users see english in this message. Most Japanese
          // users will user Outlook Express and so will just see the html message
          //
  
          // First we format 2 text messages, one for text and one for HTML,
          // since message itself is relatively small we know its < 1024 bytes
  
          iRet = wnsprintfA(szMessageText, ARRAYSIZE(szMessageText), szMessageFormat, 
                           szName, szTextBreak, szURL, szTextBreak);
          ASSERT(iRet > lstrlenA(szMessageFormat));
  
          iRet = wnsprintfA(szMessageHTML, ARRAYSIZE(szMessageHTML), szMessageFormat2, 
                           szName, szHTMLBreak, szURL, szHTMLBreak);
          ASSERT(iRet > lstrlenA(szMessageFormat2));
  
          DWORD dwTotalSize = lstrlenA(szWrapper) + lstrlenA(szEmailAddress) +
                              lstrlenA(szName) + lstrlenA(szMessageText) + lstrlenA(szSrcCharset) +
                              lstrlenA(szMessageHTML) + lstrlenA(szURL) + 1;
  
          lpBuffer = (CHAR *)MemAlloc(LPTR, dwTotalSize * sizeof(CHAR));
          if (!lpBuffer)
              return FALSE;
  
          LPSTR lpArguments[6];
          lpArguments[0] = szEmailAddress;  // target email address
          lpArguments[1] = szName;          // the name of the page that goes in the subject line  
          lpArguments[2] = szMessageText;   // the plain text message
          lpArguments[3] = szSrcCharset;    // the charset of the HTML page
          lpArguments[4] = szURL;           // the href of the page that goes in the frame set
          lpArguments[5] = szMessageHTML;   // the plain text message that goes in the 
                                            // noframes part of the frameset
  
          DWORD dwRet;
          dwRet = FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
              szWrapper, 0, 0, lpBuffer, dwTotalSize, (va_list *)&lpArguments[0]);
          ASSERT(dwRet);            
      }
  
      *ppHTMLMessage = lpBuffer;
  
      return TRUE;
  }
  
  //
  // Build the actual text of the message to be sent via SMTP,
  // load format string from resource and insert URL and URL's friently name.
  //
  void BuildSMTPMessage(LPSTR szName, LPSTR szURL, LPSTR *szMessage,
                        LPSTR szTitle, LPSTR szAbstract)
  {
      CHAR szFormatText[NOTE_TEXT_LENGTH];
      int i;
      ASSERT(szMessage);
      
      if (!szMessage)
          return;
  
  
      *szMessage = NULL;
  
      if (szTitle != NULL) {
          i = MLLoadStringA(IDS_AGNT_SMTPMESSAGE_OTHER, szFormatText, NOTE_TEXT_LENGTH);
          ASSERT(i != 0);
          
          DWORD dwLen = lstrlenA(szFormatText) + lstrlenA(szTitle) + lstrlenA(szAbstract) + 1;
  
          *szMessage = (LPSTR) MemAlloc(LPTR, dwLen * sizeof(CHAR));
          if (!*szMessage)
              return;
  
          LPSTR lpArgs[2];
          lpArgs[0] = szTitle;
          lpArgs[1] = szAbstract;
  
          FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                  szFormatText, 0, 0, *szMessage, dwLen, (va_list *)&lpArgs[0]);
  
      } else {
          i = MLLoadStringA(IDS_AGNT_SMTPMESSAGE, szFormatText, NOTE_TEXT_LENGTH);
          ASSERT(i != 0);
          
          DWORD dwLen = lstrlenA(szFormatText) + 2*lstrlenA(szName) + lstrlenA(szURL) + 1;
  
          *szMessage = (LPSTR) MemAlloc(LPTR, dwLen * sizeof(CHAR));
          if (!*szMessage)
              return;
  
          LPSTR lpArgs[3];
          lpArgs[0] = lpArgs[1] = szName;
          lpArgs[2] = szURL;
  
          FormatMessageA(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                  szFormatText, 0, 0, *szMessage, dwLen, (va_list *)&lpArgs[0]);
      }
  
  }
  
  
  //
  // Use the MLANG apis to translate the string
  //
  // Returns success if translation occurred, fails otherwise
  //
  // Note if lpszSrcCharSet is NULL then use CP_ACP as the codepage
  //
  
  HRESULT TranslateCharset(
      LPSTR lpszSrcString, LPSTR lpszDstString, UINT uiDstSize,
      LPSTR lpszSrcCharset, LPSTR lpszDstCharset
      )
  {
      HRESULT hr = E_FAIL;
  
      WCHAR wszSrcCharset[ENCODING_STRLEN];
      WCHAR wszDstCharset[ENCODING_STRLEN];
  
      if (lpszSrcString  == NULL || lpszDstString  == NULL || 
          lpszDstCharset == NULL)
      {
          return E_INVALIDARG;
      }
  
      SHAnsiToUnicode(lpszDstCharset, wszDstCharset, ARRAYSIZE(wszDstCharset));
      if (lpszSrcCharset)
          SHAnsiToUnicode(lpszSrcCharset, wszSrcCharset, ARRAYSIZE(wszSrcCharset));
  
      LPMULTILANGUAGE2 pIML2 = NULL;
  
      //
      // Create the MLANG object
      //
      if (SUCCEEDED(CoCreateInstance (CLSID_CMultiLanguage, NULL,
          CLSCTX_INPROC_SERVER, IID_IMultiLanguage2, (void**)&pIML2)))
      {
          UINT srcCodePage = (UINT)-1, dstCodePage;
          MIMECSETINFO mcsi = {0};
  
          //
          // First get the source code page either from the passed in string
          // name of source Charset or from the default one if null if passed in
          //
          if (lpszSrcCharset == NULL)
          {
              srcCodePage = GetACP();
              hr = S_OK;
          }
          else
          {
              //
              // Use the mlang object to get the codepages
              //
              hr = pIML2->GetCharsetInfo(wszSrcCharset, &mcsi);
              if (SUCCEEDED(hr))
              {
                  srcCodePage = mcsi.uiInternetEncoding;
              }
          }
          
          if (SUCCEEDED(hr))
          {
              hr = pIML2->GetCharsetInfo(wszDstCharset, &mcsi);
  
              if (SUCCEEDED(hr))
              {
                  dstCodePage = mcsi.uiInternetEncoding;
  
                  if (srcCodePage != dstCodePage)
                  {
                      //
                      // To work around a bug in the Mlang::ConvertString api
                      // have to pass in a ptr to length of the src string
                      //
                      UINT uiSrcSize = lstrlenA(lpszSrcString) + 1;
  
                      DWORD dwMode = 0;
                      hr = pIML2->ConvertString(
                          &dwMode, 
                          srcCodePage, 
                          dstCodePage,
                          (LPBYTE)lpszSrcString,
                          &uiSrcSize,
                          (LPBYTE)lpszDstString,
                          &uiDstSize);
                  }
                  else
                  {
                      lstrcpynA(lpszDstString, lpszSrcString, uiDstSize);
                  }
              }
          }
          pIML2->Release();
      }
      return hr;
  }
  
  
  
  //////////////////////////////////////////////////////////////////////////
  //
  // Mail notification implementation
  //
  //////////////////////////////////////////////////////////////////////////
  
  //
  // Notify via email that the pszURL has changed
  //
  // There are 3 ways to send via email -
  //
  // Use straight MAPI (IE Exchange or Outlook)
  //      Most people don't have Exchange in the real world.
  //
  // Use Athena's MAPI implementation
  //      It's broken and doesn't handle UI'less mode
  //
  // Use straight SMTP,
  //      Need to get the name of an SMTP server
  //
  HRESULT
  NotifyViaEMail(
      LPSTR lpszURL,             // url that was downloaded
      LPSTR lpszEmailAddress,    // email address to send notification to
      LPSTR lpszSMTPServer,      // SMTP server to use to deliver email
      LPSTR &lpszName,           // friendly name of url (probably page title)
      LPSTR lpszTitle,           // optional: NULL if not custom message
      LPSTR lpszAbstract,        // optional: NULL if not custom message
      LPSTR lpszCharSet,         // optional: charset of html page
      BOOL  fSendHTMLEmail )     // TRUE if registry allows it and check mode
                                 // supports it.
  {
      BOOL b;
      
      LPSTR lpszSMTPMessage;
  
      //
      // lpszName comes from the title of the web page. If the charset of the page
      // is not the same as the one that this version of IE has been localized to
      // then we need to use the MLANG api's to coerce the string into the correct
      // charset
      //
      CHAR szTargetEncoding[ENCODING_STRLEN];
      MLLoadStringA(IDS_TARGET_CHARSET_EMAIL, szTargetEncoding, ARRAYSIZE(szTargetEncoding));
  
      //
      // Allocate buffer for new name. This is a conversion from one dbcs charset 
      // to another so size shouldn't but to be safe use *2 multiplier.
      //
      UINT uiSize = lstrlenA(lpszName) * 2;
      LPSTR lpszNewName = (LPSTR) MemAlloc(LMEM_FIXED, uiSize * sizeof(CHAR));
  
      if (lpszNewName)
      {
          //
          // Note check for S_OK as will return S_FALSE if there is no appropriate
          // translation installed on this machine
          //
          if (S_OK == TranslateCharset(lpszName, lpszNewName, uiSize, lpszCharSet,
                                       szTargetEncoding))
          {
              //
              // if translation occurred alias new name to old name
              //
              SAFELOCALFREE(lpszName);
              lpszName = lpszNewName;
          }
          else
          {
              SAFELOCALFREE(lpszNewName); // don't need newname after all
          }
      }
      
      //
      // If we are requested to HTML mail and we successfully built the html
      //
      if (!(fSendHTMLEmail &&
           BuildHTMLMessage(lpszEmailAddress, lpszName, lpszURL, &lpszSMTPMessage,
                            lpszTitle, lpszAbstract, lpszCharSet)))
      {
          //
          // If sending a simple notification or BuildHTMLMessage failed
          // force fSendHTMLEmail to false and build simple smtp message
          //
          fSendHTMLEmail = FALSE;
          BuildSMTPMessage(lpszName, lpszURL, &lpszSMTPMessage, lpszTitle, lpszAbstract);
      }
  
      //
      // Send message to given address and from given address
      //
      if (lpszSMTPMessage)
      {
          b = SMTPSendMessage(lpszSMTPServer,  lpszEmailAddress, 
                              lpszEmailAddress, lpszSMTPMessage);
  
          MemFree(lpszSMTPMessage);
  
      }
      else
      {
          b = FALSE;
      }
  
      if (b)
          return S_OK;
  
      return E_FAIL;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  // Helper function to send email
  //
  //////////////////////////////////////////////////////////////////////////
  HRESULT SendEmailFromItem(ISubscriptionItem *pItem)
  {
      HRESULT hr = E_FAIL;
      LPSTR pszURL = NULL;
      LPSTR pszName = NULL;
      LPSTR pszTitle = NULL;
      LPSTR pszAbstract = NULL;
      LPSTR pszCharSet = NULL;
      
      // Get the Email URL to send.  Fall back to the download URL.
      ReadAnsiSTR(pItem, c_szPropEmailURL, &pszURL);
      if (!pszURL)
          ReadAnsiSTR(pItem, c_szPropURL, &pszURL);
      ASSERT(pszURL);
  
      // Get the friendly name.  Fall back to the download URL.
      ReadAnsiSTR(pItem, c_szPropName, &pszName);
      ASSERT(pszName);
      if (!pszName)
          ReadAnsiSTR(pItem, c_szPropURL, &pszName);
  
      // Get Email Title and Abstract if flag is set.
      DWORD dwEmailFlags = 0;
      ReadDWORD(pItem, c_szPropEmailFlags, &dwEmailFlags);
      if (dwEmailFlags & MAILAGENT_FLAG_CUSTOM_MSG)
      {
          ReadAnsiSTR(pItem, c_szPropEmailTitle, &pszTitle);
          ASSERT(pszTitle);
          ReadAnsiSTR(pItem, c_szPropEmailAbstract, &pszAbstract);
          ASSERT(pszAbstract);
      }
  
      //
      // Get the charset in the notification
      //
      ReadAnsiSTR(pItem, c_szPropCharSet, &pszCharSet);
  
      // Get Email address and SMTP server
      TCHAR tszBuf[MAX_PATH];
      CHAR szEmailAddress[MAX_PATH];
      CHAR szSMTPServer[MAX_PATH];
      
      ReadDefaultEmail(tszBuf, ARRAYSIZE(tszBuf));
      SHTCharToAnsi(tszBuf, szEmailAddress, ARRAYSIZE(szEmailAddress));
      ReadDefaultSMTPServer(tszBuf, ARRAYSIZE(tszBuf));
      SHTCharToAnsi(tszBuf, szSMTPServer, ARRAYSIZE(szSMTPServer));
  
      // Send the email
      if (pszURL && pszName)
      {
          //
          // Check if HTML Mail notification is enabled or disabled thru the registry
          //
          BOOL fSendHTMLEmail = FALSE;
  
          if (!ReadRegValue(HKEY_CURRENT_USER, c_szRegKey,
              TEXT("EnableHTMLMailNotification"),
              &fSendHTMLEmail, sizeof(fSendHTMLEmail)))
          {
              fSendHTMLEmail = TRUE; // default to on if not read from registry
          }
  
          // Now make sure our crawling mode supports HTML mail. We don't
          // want to send HTML if we're in check-for-change only.
          DWORD dwTemp = 0;
          ReadDWORD(pItem, c_szPropCrawlChangesOnly, &dwTemp);
          if (dwTemp != 0)
          {
              fSendHTMLEmail = FALSE;
          }
          // else, leave fSendHTMLEmail in its reg-based setting.
  
          hr = NotifyViaEMail(pszURL, szEmailAddress, szSMTPServer, 
                              pszName, pszTitle, pszAbstract, pszCharSet,
                              fSendHTMLEmail );
      }
  
      // Clean up.
      SAFELOCALFREE(pszURL);
      SAFELOCALFREE(pszName);
      SAFELOCALFREE(pszTitle);
      SAFELOCALFREE(pszAbstract);
      return hr;
  }
  
  
  