|
|
//
// 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; }
|