diff options
Diffstat (limited to 'mailnews/mime/src/nsStreamConverter.cpp')
-rw-r--r-- | mailnews/mime/src/nsStreamConverter.cpp | 1157 |
1 files changed, 1157 insertions, 0 deletions
diff --git a/mailnews/mime/src/nsStreamConverter.cpp b/mailnews/mime/src/nsStreamConverter.cpp new file mode 100644 index 000000000..0d1781498 --- /dev/null +++ b/mailnews/mime/src/nsStreamConverter.cpp @@ -0,0 +1,1157 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include <stdio.h> +#include "mimecom.h" +#include "modmimee.h" +#include "nscore.h" +#include "nsStreamConverter.h" +#include "prmem.h" +#include "prprf.h" +#include "prlog.h" +#include "plstr.h" +#include "mimemoz2.h" +#include "nsMimeTypes.h" +#include "nsIComponentManager.h" +#include "nsIURL.h" +#include "nsStringGlue.h" +#include "nsUnicharUtils.h" +#include "nsIServiceManager.h" +#include "nsMemory.h" +#include "nsIPipe.h" +#include "nsMimeStringResources.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsNetUtil.h" +#include "nsIMsgQuote.h" +#include "nsIScriptSecurityManager.h" +#include "nsNetUtil.h" +#include "mozITXTToHTMLConv.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgWindow.h" +#include "nsICategoryManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsMsgUtils.h" +#include "mozilla/ArrayUtils.h" + +#define PREF_MAIL_DISPLAY_GLYPH "mail.display_glyph" +#define PREF_MAIL_DISPLAY_STRUCT "mail.display_struct" + +//////////////////////////////////////////////////////////////// +// Bridge routines for new stream converter XP-COM interface +//////////////////////////////////////////////////////////////// + +extern "C" void * +mime_bridge_create_draft_stream(nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out); + +extern "C" void * +bridge_create_stream(nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out, + uint32_t whattodo, + nsIChannel *aChannel) +{ + if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (format_out == nsMimeOutput::nsMimeMessageEditorTemplate) ) + return mime_bridge_create_draft_stream(newEmitter, newPluginObj2, uri, format_out); + else + return mime_bridge_create_display_stream(newEmitter, newPluginObj2, uri, format_out, whattodo, + aChannel); +} + +void +bridge_destroy_stream(void *newStream) +{ + nsMIMESession *stream = (nsMIMESession *)newStream; + if (!stream) + return; + + PR_FREEIF(stream); +} + +void +bridge_set_output_type(void *bridgeStream, nsMimeOutputType aType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + + if (session) + { + // BAD ASSUMPTION!!!! NEED TO CHECK aType + mime_stream_data *msd = (mime_stream_data *)session->data_object; + if (msd) + msd->format_out = aType; // output format type + } +} + +nsresult +bridge_new_new_uri(void *bridgeStream, nsIURI *aURI, int32_t aOutputType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + const char **fixup_pointer = nullptr; + + if (session) + { + if (session->data_object) + { + bool *override_charset = nullptr; + char **default_charset = nullptr; + char **url_name = nullptr; + + if ( (aOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (aOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)session->data_object; + if (mdd->options) + { + default_charset = &(mdd->options->default_charset); + override_charset = &(mdd->options->override_charset); + url_name = &(mdd->url_name); + } + } + else + { + mime_stream_data *msd = (mime_stream_data *)session->data_object; + + if (msd->options) + { + default_charset = &(msd->options->default_charset); + override_charset = &(msd->options->override_charset); + url_name = &(msd->url_name); + fixup_pointer = &(msd->options->url); + } + } + + if ( (default_charset) && (override_charset) && (url_name) ) + { + // + // set the default charset to be the folder charset if we have one associated with + // this url... + nsCOMPtr<nsIMsgI18NUrl> i18nUrl (do_QueryInterface(aURI)); + if (i18nUrl) + { + nsCString charset; + + // check to see if we have a charset override...and if we do, set that field appropriately too... + nsresult rv = i18nUrl->GetCharsetOverRide(getter_Copies(charset)); + if (NS_SUCCEEDED(rv) && !charset.IsEmpty() ) { + *override_charset = true; + *default_charset = ToNewCString(charset); + } + else + { + i18nUrl->GetFolderCharset(getter_Copies(charset)); + if (!charset.IsEmpty()) + *default_charset = ToNewCString(charset); + } + + // if there is no manual override and a folder charset exists + // then check if we have a folder level override + if (!(*override_charset) && *default_charset && **default_charset) + { + bool folderCharsetOverride; + rv = i18nUrl->GetFolderCharsetOverride(&folderCharsetOverride); + if (NS_SUCCEEDED(rv) && folderCharsetOverride) + *override_charset = true; + + // notify the default to msgWindow (for the menu check mark) + // do not set the default in case of nsMimeMessageDraftOrTemplate + // or nsMimeMessageEditorTemplate because it is already set + // when the message is displayed and doing it again may overwrite + // the correct MIME charset parsed from the message header + if (aOutputType != nsMimeOutput::nsMimeMessageDraftOrTemplate && + aOutputType != nsMimeOutput::nsMimeMessageEditorTemplate) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(aURI)); + if (msgurl) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + msgWindow->SetMailCharacterSet(nsDependentCString(*default_charset)); + msgWindow->SetCharsetOverride(*override_charset); + } + } + } + + // if the pref says always override and no manual override then set the folder charset to override + if (!*override_charset) { + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + bool force_override; + rv = pPrefBranch->GetBoolPref("mailnews.force_charset_override", &force_override); + if (NS_SUCCEEDED(rv) && force_override) + { + *override_charset = true; + } + } + } + } + } + nsAutoCString urlString; + if (NS_SUCCEEDED(aURI->GetSpec(urlString))) + { + if (!urlString.IsEmpty()) + { + NS_Free(*url_name); + *url_name = ToNewCString(urlString); + if (!(*url_name)) + return NS_ERROR_OUT_OF_MEMORY; + + // rhp: Ugh, this is ugly...but it works. + if (fixup_pointer) + *fixup_pointer = (const char *)*url_name; + } + } + } + } + } + + return NS_OK; +} + +static int +mime_headers_callback ( void *closure, MimeHeaders *headers ) +{ + // We get away with this because this doesn't get called on draft operations. + mime_stream_data *msd = (mime_stream_data *)closure; + + NS_ASSERTION(msd && headers, "null mime stream data or headers"); + if ( !msd || ! headers ) + return 0; + + NS_ASSERTION(!msd->headers, "non-null mime stream data headers"); + msd->headers = MimeHeaders_copy ( headers ); + return 0; +} + +nsresult +bridge_set_mime_stream_converter_listener(void *bridgeStream, nsIMimeStreamConverterListener* listener, + nsMimeOutputType aOutputType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + + if ( (session) && (session->data_object) ) + { + if ( (aOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (aOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)session->data_object; + if (mdd->options) + { + if (listener) + { + mdd->options->caller_need_root_headers = true; + mdd->options->decompose_headers_info_fn = mime_headers_callback; + } + else + { + mdd->options->caller_need_root_headers = false; + mdd->options->decompose_headers_info_fn = nullptr; + } + } + } + else + { + mime_stream_data *msd = (mime_stream_data *)session->data_object; + + if (msd->options) + { + if (listener) + { + msd->options->caller_need_root_headers = true; + msd->options->decompose_headers_info_fn = mime_headers_callback; + } + else + { + msd->options->caller_need_root_headers = false; + msd->options->decompose_headers_info_fn = nullptr; + } + } + } + } + + return NS_OK; +} + +// find a query element in a url and return a pointer to its data +// (query must be in the form "query=") +static const char * +FindQueryElementData(const char * aUrl, const char * aQuery) +{ + if (aUrl && aQuery) + { + size_t queryLen = 0; // we don't call strlen until we need to + aUrl = PL_strcasestr(aUrl, aQuery); + while (aUrl) + { + if (!queryLen) + queryLen = strlen(aQuery); + if (*(aUrl-1) == '&' || *(aUrl-1) == '?') + return aUrl + queryLen; + aUrl = PL_strcasestr(aUrl + queryLen, aQuery); + } + } + return nullptr; +} + +// case-sensitive test for string prefixing. If |string| is prefixed +// by |prefix| then a pointer to the next character in |string| following +// the prefix is returned. If it is not a prefix then |nullptr| is returned. +static const char * +SkipPrefix(const char *aString, const char *aPrefix) +{ + while (*aPrefix) + if (*aPrefix++ != *aString++) + return nullptr; + return aString; +} + +// +// Utility routines needed by this interface +// +nsresult +nsStreamConverter::DetermineOutputFormat(const char *aUrl, nsMimeOutputType *aNewType) +{ + // sanity checking + NS_ENSURE_ARG_POINTER(aNewType); + if (!aUrl || !*aUrl) + { + // default to html for the entire document + *aNewType = nsMimeOutput::nsMimeMessageQuoting; + mOutputFormat = "text/html"; + return NS_OK; + } + + // shorten the url that we test for the query strings by skipping directly + // to the part where the query strings begin. + const char *queryPart = PL_strchr(aUrl, '?'); + + // First, did someone pass in a desired output format. They will be able to + // pass in any content type (i.e. image/gif, text/html, etc...but the "/" will + // have to be represented via the "%2F" value + const char *format = FindQueryElementData(queryPart, "outformat="); + if (format) + { + //NOTE: I've done a file contents search of every file (*.*) in the mozilla + // directory tree and there is not a single location where the string "outformat" + // is added to any URL. It appears that this code has been orphaned off by a change + // elsewhere and is no longer required. It will be removed in the future unless + // someone complains. + MOZ_ASSERT(false, "Is this code actually being used?"); + + while (*format == ' ') + ++format; + + if (*format) + { + mOverrideFormat = "raw"; + + // set mOutputFormat to the supplied format, ensure that we replace any + // %2F strings with the slash character + const char *nextField = PL_strpbrk(format, "&; "); + mOutputFormat.Assign(format, nextField ? nextField - format : -1); + MsgReplaceSubstring(mOutputFormat, "%2F", "/"); + MsgReplaceSubstring(mOutputFormat, "%2f", "/"); + + // Don't muck with this data! + *aNewType = nsMimeOutput::nsMimeMessageRaw; + return NS_OK; + } + } + + // is this is a part that should just come out raw + const char *part = FindQueryElementData(queryPart, "part="); + if (part && !mToType.Equals("application/vnd.mozilla.xul+xml")) + { + // default for parts + mOutputFormat = "raw"; + *aNewType = nsMimeOutput::nsMimeMessageRaw; + + // if we are being asked to fetch a part....it should have a + // content type appended to it...if it does, we want to remember + // that as mOutputFormat + const char * typeField = FindQueryElementData(queryPart, "type="); + if (typeField && !strncmp(typeField, "application/x-message-display", sizeof("application/x-message-display") - 1)) + { + const char *secondTypeField = FindQueryElementData(typeField, "type="); + if (secondTypeField) + typeField = secondTypeField; + } + if (typeField) + { + // store the real content type...mOutputFormat gets deleted later on... + // and make sure we only get our own value. + char *nextField = PL_strchr(typeField, '&'); + mRealContentType.Assign(typeField, nextField ? nextField - typeField : -1); + if (mRealContentType.Equals("message/rfc822")) + { + mRealContentType = "application/x-message-display"; + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + } + else if (mRealContentType.Equals("application/x-message-display")) + { + mRealContentType = ""; + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + } + } + + return NS_OK; + } + + const char *emitter = FindQueryElementData(queryPart, "emitter="); + if (emitter) + { + const char *remainder = SkipPrefix(emitter, "js"); + if (remainder && (!*remainder || *remainder == '&')) + mOverrideFormat = "application/x-js-mime-message"; + } + + // if using the header query + const char *header = FindQueryElementData(queryPart, "header="); + if (header) + { + struct HeaderType { + const char * headerType; + const char * outputFormat; + nsMimeOutputType mimeOutputType; + }; + + // place most commonly used options at the top + static const struct HeaderType rgTypes[] = + { + { "filter", "text/html", nsMimeOutput::nsMimeMessageFilterSniffer }, + { "quotebody", "text/html", nsMimeOutput::nsMimeMessageBodyQuoting }, + { "print", "text/html", nsMimeOutput::nsMimeMessagePrintOutput }, + { "only", "text/xml", nsMimeOutput::nsMimeMessageHeaderDisplay }, + { "none", "text/html", nsMimeOutput::nsMimeMessageBodyDisplay }, + { "quote", "text/html", nsMimeOutput::nsMimeMessageQuoting }, + { "saveas", "text/html", nsMimeOutput::nsMimeMessageSaveAs }, + { "src", "text/plain", nsMimeOutput::nsMimeMessageSource }, + { "attach", "raw", nsMimeOutput::nsMimeMessageAttach } + }; + + // find the requested header in table, ensure that we don't match on a prefix + // by checking that the following character is either null or the next query element + const char * remainder; + for (uint32_t n = 0; n < MOZ_ARRAY_LENGTH(rgTypes); ++n) + { + remainder = SkipPrefix(header, rgTypes[n].headerType); + if (remainder && (*remainder == '\0' || *remainder == '&')) + { + mOutputFormat = rgTypes[n].outputFormat; + *aNewType = rgTypes[n].mimeOutputType; + return NS_OK; + } + } + } + + // default to html for just the body + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + + return NS_OK; +} + +nsresult +nsStreamConverter::InternalCleanup(void) +{ + if (mBridgeStream) + { + bridge_destroy_stream(mBridgeStream); + mBridgeStream = nullptr; + } + + return NS_OK; +} + +/* + * Inherited methods for nsMimeConverter + */ +nsStreamConverter::nsStreamConverter() +{ + // Init member variables... + mWrapperOutput = false; + mBridgeStream = nullptr; + mOutputFormat = "text/html"; + mAlreadyKnowOutputType = false; + mForwardInline = false; + mForwardInlineFilter = false; + mOverrideComposeFormat = false; + + mPendingRequest = nullptr; + mPendingContext = nullptr; +} + +nsStreamConverter::~nsStreamConverter() +{ + InternalCleanup(); +} + +NS_IMPL_ISUPPORTS(nsStreamConverter, nsIStreamListener, nsIRequestObserver, + nsIStreamConverter, nsIMimeStreamConverter) + +/////////////////////////////////////////////////////////////// +// nsStreamConverter definitions.... +/////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsStreamConverter::Init(nsIURI *aURI, nsIStreamListener * aOutListener, nsIChannel *aChannel) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsresult rv = NS_OK; + mOutListener = aOutListener; + + // mscott --> we need to look at the url and figure out what the correct output type is... + nsMimeOutputType newType = mOutputType; + if (!mAlreadyKnowOutputType) + { + nsAutoCString urlSpec; + rv = aURI->GetSpec(urlSpec); + DetermineOutputFormat(urlSpec.get(), &newType); + mAlreadyKnowOutputType = true; + mOutputType = newType; + } + + switch (newType) + { + case nsMimeOutput::nsMimeMessageSplitDisplay: // the wrapper HTML output to produce the split header/body display + mWrapperOutput = true; + mOutputFormat = "text/html"; + break; + case nsMimeOutput::nsMimeMessageHeaderDisplay: // the split header/body display + mOutputFormat = "text/xml"; + break; + case nsMimeOutput::nsMimeMessageBodyDisplay: // the split header/body display + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted output + case nsMimeOutput::nsMimeMessageSaveAs: // Save as operation + case nsMimeOutput::nsMimeMessageBodyQuoting: // only HTML body quoted output + case nsMimeOutput::nsMimeMessagePrintOutput: // all Printing output + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageAttach: + case nsMimeOutput::nsMimeMessageDecrypt: + case nsMimeOutput::nsMimeMessageRaw: // the raw RFC822 data and attachments + mOutputFormat = "raw"; + break; + + case nsMimeOutput::nsMimeMessageSource: // the raw RFC822 data (view source) and attachments + mOutputFormat = "text/plain"; + mOverrideFormat = "raw"; + break; + + case nsMimeOutput::nsMimeMessageDraftOrTemplate: // Loading drafts & templates + mOutputFormat = "message/draft"; + break; + + case nsMimeOutput::nsMimeMessageEditorTemplate: // Loading templates into editor + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageFilterSniffer: // output all displayable part as raw + mOutputFormat = "text/html"; + break; + + default: + NS_ERROR("this means I made a mistake in my assumptions"); + } + + + // the following output channel stream is used to fake the content type for people who later + // call into us.. + nsCString contentTypeToUse; + GetContentType(getter_Copies(contentTypeToUse)); + // mscott --> my theory is that we don't need this fake outgoing channel. Let's use the + // original channel and just set our content type ontop of the original channel... + + aChannel->SetContentType(contentTypeToUse); + + //rv = NS_NewInputStreamChannel(getter_AddRefs(mOutgoingChannel), aURI, nullptr, contentTypeToUse, -1); + //if (NS_FAILED(rv)) + // return rv; + + // Set system principal for this document, which will be dynamically generated + + // We will first find an appropriate emitter in the repository that supports + // the requested output format...note, the special exceptions are nsMimeMessageDraftOrTemplate + // or nsMimeMessageEditorTemplate where we don't need any emitters + // + + if ( (newType != nsMimeOutput::nsMimeMessageDraftOrTemplate) && + (newType != nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + nsAutoCString categoryName ("@mozilla.org/messenger/mimeemitter;1?type="); + if (!mOverrideFormat.IsEmpty()) + categoryName += mOverrideFormat; + else + categoryName += mOutputFormat; + + nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCString contractID; + catman->GetCategoryEntry("mime-emitter", categoryName.get(), getter_Copies(contractID)); + if (!contractID.IsEmpty()) + categoryName = contractID; + } + + mEmitter = do_CreateInstance(categoryName.get(), &rv); + + if ((NS_FAILED(rv)) || (!mEmitter)) + { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // initialize our emitter + if (mEmitter) + { + // Now we want to create a pipe which we'll use for converting the data. + nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1"); + rv = pipe->Init(true, true, 4096, 8); + NS_ENSURE_SUCCESS(rv, rv); + + // These always succeed because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(mInputStream))); + MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(mOutputStream))); + + mEmitter->Initialize(aURI, aChannel, newType); + mEmitter->SetPipe(mInputStream, mOutputStream); + mEmitter->SetOutputListener(aOutListener); + } + + uint32_t whattodo = mozITXTToHTMLConv::kURLs; + bool enable_emoticons = true; + bool enable_structs = true; + + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + rv = pPrefBranch->GetBoolPref(PREF_MAIL_DISPLAY_GLYPH,&enable_emoticons); + if (NS_FAILED(rv) || enable_emoticons) + { + whattodo = whattodo | mozITXTToHTMLConv::kGlyphSubstitution; + } + rv = pPrefBranch->GetBoolPref(PREF_MAIL_DISPLAY_STRUCT,&enable_structs); + if (NS_FAILED(rv) || enable_structs) + { + whattodo = whattodo | mozITXTToHTMLConv::kStructPhrase; + } + } + + if (mOutputType == nsMimeOutput::nsMimeMessageSource) + return NS_OK; + else + { + mBridgeStream = bridge_create_stream(mEmitter, this, aURI, newType, whattodo, aChannel); + if (!mBridgeStream) + return NS_ERROR_OUT_OF_MEMORY; + else + { + SetStreamURI(aURI); + + //Do we need to setup an Mime Stream Converter Listener? + if (mMimeStreamConverterListener) + bridge_set_mime_stream_converter_listener((nsMIMESession *)mBridgeStream, mMimeStreamConverterListener, mOutputType); + + return NS_OK; + } + } +} + +NS_IMETHODIMP nsStreamConverter::GetContentType(char **aOutputContentType) +{ + if (!aOutputContentType) + return NS_ERROR_NULL_POINTER; + + // since this method passes a string through an IDL file we need to use nsMemory to allocate it + // and not strdup! + // (1) check to see if we have a real content type...use it first... + if (!mRealContentType.IsEmpty()) + *aOutputContentType = ToNewCString(mRealContentType); + else if (mOutputFormat.Equals("raw")) + *aOutputContentType = (char *) nsMemory::Clone(UNKNOWN_CONTENT_TYPE, sizeof(UNKNOWN_CONTENT_TYPE)); + else + *aOutputContentType = ToNewCString(mOutputFormat); + return NS_OK; +} + +// +// This is the type of output operation that is being requested by libmime. The types +// of output are specified by nsIMimeOutputType enum +// +nsresult +nsStreamConverter::SetMimeOutputType(nsMimeOutputType aType) +{ + mAlreadyKnowOutputType = true; + mOutputType = aType; + if (mBridgeStream) + bridge_set_output_type(mBridgeStream, aType); + return NS_OK; +} + +NS_IMETHODIMP nsStreamConverter::GetMimeOutputType(nsMimeOutputType *aOutFormat) +{ + nsresult rv = NS_OK; + if (aOutFormat) + *aOutFormat = mOutputType; + else + rv = NS_ERROR_NULL_POINTER; + + return rv; +} + +// +// This is needed by libmime for MHTML link processing...this is the URI associated +// with this input stream +// +nsresult +nsStreamConverter::SetStreamURI(nsIURI *aURI) +{ + mURI = aURI; + if (mBridgeStream) + return bridge_new_new_uri((nsMIMESession *)mBridgeStream, aURI, mOutputType); + else + return NS_OK; +} + +nsresult +nsStreamConverter::SetMimeHeadersListener(nsIMimeStreamConverterListener *listener, nsMimeOutputType aType) +{ + mMimeStreamConverterListener = listener; + bridge_set_mime_stream_converter_listener((nsMIMESession *)mBridgeStream, listener, aType); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardInline(bool aForwardInline) +{ + mForwardInline = aForwardInline; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardToAddress(nsAString &aAddress) +{ + aAddress = mForwardToAddress; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardToAddress(const nsAString &aAddress) +{ + mForwardToAddress = aAddress; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOverrideComposeFormat(bool *aResult) +{ + if (!aResult) + return NS_ERROR_NULL_POINTER; + *aResult = mOverrideComposeFormat; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOverrideComposeFormat(bool aOverrideComposeFormat) +{ + mOverrideComposeFormat = aOverrideComposeFormat; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardInline(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mForwardInline; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardInlineFilter(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mForwardInlineFilter; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardInlineFilter(bool aForwardInlineFilter) +{ + mForwardInlineFilter = aForwardInlineFilter; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetIdentity(nsIMsgIdentity * *aIdentity) +{ + if (!aIdentity) return NS_ERROR_NULL_POINTER; + /* + We don't have an identity for the local folders account, + we will return null but it is not an error! + */ + *aIdentity = mIdentity; + NS_IF_ADDREF(*aIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetIdentity(nsIMsgIdentity * aIdentity) +{ + mIdentity = aIdentity; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOriginalMsgURI(const char * originalMsgURI) +{ + mOriginalMsgURI = originalMsgURI; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOriginalMsgURI(char ** result) +{ + if (!result) return NS_ERROR_NULL_POINTER; + *result = ToNewCString(mOriginalMsgURI); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOrigMsgHdr(nsIMsgDBHdr *aMsgHdr) +{ + mOrigMsgHdr = aMsgHdr; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOrigMsgHdr(nsIMsgDBHdr * *aMsgHdr) +{ + if (!aMsgHdr) return NS_ERROR_NULL_POINTER; + NS_IF_ADDREF(*aMsgHdr = mOrigMsgHdr); + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Methods for nsIStreamListener... +///////////////////////////////////////////////////////////////////////////// +// +// Notify the client that data is available in the input stream. This +// method is called whenver data is written into the input stream by the +// networking library... +// +nsresult +nsStreamConverter::OnDataAvailable(nsIRequest *request, + nsISupports *ctxt, + nsIInputStream *aIStream, + uint64_t sourceOffset, + uint32_t aLength) +{ + nsresult rc=NS_OK; // should this be an error instead? + uint32_t readLen = aLength; + uint32_t written; + + // If this is the first time through and we are supposed to be + // outputting the wrapper two pane URL, then do it now. + if (mWrapperOutput) + { + char outBuf[1024]; +const char output[] = "\ +<HTML>\ +<FRAMESET ROWS=\"30%%,70%%\">\ +<FRAME NAME=messageHeader SRC=\"%s?header=only\">\ +<FRAME NAME=messageBody SRC=\"%s?header=none\">\ +</FRAMESET>\ +</HTML>"; + + nsAutoCString url; + if (NS_FAILED(mURI->GetSpec(url))) + return NS_ERROR_FAILURE; + + PR_snprintf(outBuf, sizeof(outBuf), output, url.get(), url.get()); + + if (mEmitter) + mEmitter->Write(nsDependentCString(outBuf), &written); + + // rhp: will this stop the stream???? Not sure. + return NS_ERROR_FAILURE; + } + + char *buf = (char *)PR_Malloc(aLength); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */ + + readLen = aLength; + aIStream->Read(buf, aLength, &readLen); + + // We need to filter out any null characters else we will have a lot of trouble + // as we use c string everywhere in mime + char * readPtr; + char * endPtr = buf + readLen; + + // First let see if the stream contains null characters + for (readPtr = buf; readPtr < endPtr && *readPtr; readPtr ++) + ; + + // Did we find a null character? Then, we need to cleanup the stream + if (readPtr < endPtr) + { + char * writePtr = readPtr; + for (readPtr ++; readPtr < endPtr; readPtr ++) + { + if (!*readPtr) + continue; + + *writePtr = *readPtr; + writePtr ++; + } + readLen = writePtr - buf; + } + + if (mOutputType == nsMimeOutput::nsMimeMessageSource) + { + rc = NS_OK; + if (mEmitter) + { + rc = mEmitter->Write(Substring(buf, buf+readLen), &written); + } + } + else if (mBridgeStream) + { + nsMIMESession *tSession = (nsMIMESession *) mBridgeStream; + // XXX Casting int to nsresult + rc = static_cast<nsresult>( + tSession->put_block((nsMIMESession *)mBridgeStream, buf, readLen)); + } + + PR_FREEIF(buf); + return rc; +} + +///////////////////////////////////////////////////////////////////////////// +// Methods for nsIRequestObserver +///////////////////////////////////////////////////////////////////////////// +// +// Notify the observer that the URL has started to load. This method is +// called only once, at the beginning of a URL load. +// +nsresult +nsStreamConverter::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ +#ifdef DEBUG_rhp + printf("nsStreamConverter::OnStartRequest()\n"); +#endif + +#ifdef DEBUG_mscott + mConvertContentTime = PR_IntervalNow(); +#endif + + // here's a little bit of hackery.... + // since the mime converter is now between the channel + // and the + if (request) + { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (channel) + { + nsCString contentType; + GetContentType(getter_Copies(contentType)); + + channel->SetContentType(contentType); + } + } + + // forward the start request to any listeners + if (mOutListener) + { + if (mOutputType == nsMimeOutput::nsMimeMessageRaw) + { + //we need to delay the on start request until we have figure out the real content type + mPendingRequest = request; + mPendingContext = ctxt; + } + else + mOutListener->OnStartRequest(request, ctxt); + } + + return NS_OK; +} + +// +// Notify the observer that the URL has finished loading. This method is +// called once when the networking library has finished processing the +// +nsresult +nsStreamConverter::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) +{ + // Make sure we fire any pending OnStartRequest before we do OnStop. + FirePendingStartRequest(); +#ifdef DEBUG_rhp + printf("nsStreamConverter::OnStopRequest()\n"); +#endif + + // + // Now complete the stream! + // + if (mBridgeStream) + { + nsMIMESession *tSession = (nsMIMESession *) mBridgeStream; + + if (mMimeStreamConverterListener) + { + + MimeHeaders **workHeaders = nullptr; + + if ( (mOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (mOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)tSession->data_object; + if (mdd) + workHeaders = &(mdd->headers); + } + else + { + mime_stream_data *msd = (mime_stream_data *)tSession->data_object; + if (msd) + workHeaders = &(msd->headers); + } + + if (workHeaders) + { + nsresult rv; + nsCOMPtr<nsIMimeHeaders> mimeHeaders = do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + { + if (*workHeaders) + mimeHeaders->Initialize(Substring((*workHeaders)->all_headers, + (*workHeaders)->all_headers_fp)); + mMimeStreamConverterListener->OnHeadersReady(mimeHeaders); + } + else + mMimeStreamConverterListener->OnHeadersReady(nullptr); + } + + mMimeStreamConverterListener = nullptr; // release our reference + } + + tSession->complete((nsMIMESession *)mBridgeStream); + } + + // + // Now complete the emitter and do necessary cleanup! + // + if (mEmitter) + { + mEmitter->Complete(); + } + + // First close the output stream... + if (mOutputStream) + mOutputStream->Close(); + + // Make sure to do necessary cleanup! + InternalCleanup(); + +#if 0 + // print out the mime timing information BEFORE we flush to layout + // otherwise we'll be including layout information. + printf("Time Spent in mime: %d ms\n", PR_IntervalToMilliseconds(PR_IntervalNow() - mConvertContentTime)); +#endif + + // forward on top request to any listeners + if (mOutListener) + mOutListener->OnStopRequest(request, ctxt, status); + + + mAlreadyKnowOutputType = false; + + // since we are done converting data, lets close all the objects we own... + // this helps us fix some circular ref counting problems we are running into... + Close(); + + // Time to return... + return NS_OK; +} + +nsresult nsStreamConverter::Close() +{ + mOutgoingChannel = nullptr; + mEmitter = nullptr; + mOutListener = nullptr; + return NS_OK; +} + +// nsIStreamConverter implementation + +// No syncronous conversion at this time. +NS_IMETHODIMP nsStreamConverter::Convert(nsIInputStream *aFromStream, + const char *aFromType, + const char *aToType, + nsISupports *aCtxt, + nsIInputStream **_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +// Stream converter service calls this to initialize the actual stream converter (us). +NS_IMETHODIMP nsStreamConverter::AsyncConvertData(const char *aFromType, + const char *aToType, + nsIStreamListener *aListener, + nsISupports *aCtxt) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgQuote> aMsgQuote = do_QueryInterface(aCtxt, &rv); + nsCOMPtr<nsIChannel> aChannel; + + if (aMsgQuote) + { + nsCOMPtr<nsIMimeStreamConverterListener> quoteListener; + rv = aMsgQuote->GetQuoteListener(getter_AddRefs(quoteListener)); + if (quoteListener) + SetMimeHeadersListener(quoteListener, nsMimeOutput::nsMimeMessageQuoting); + rv = aMsgQuote->GetQuoteChannel(getter_AddRefs(aChannel)); + } + else + { + aChannel = do_QueryInterface(aCtxt, &rv); + } + + mFromType = aFromType; + mToType = aToType; + + NS_ASSERTION(aChannel && NS_SUCCEEDED(rv), "mailnews mime converter has to have the channel passed in..."); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIURI> aUri; + aChannel->GetURI(getter_AddRefs(aUri)); + return Init(aUri, aListener, aChannel); +} + +NS_IMETHODIMP nsStreamConverter::FirePendingStartRequest() +{ + if (mPendingRequest && mOutListener) + { + mOutListener->OnStartRequest(mPendingRequest, mPendingContext); + mPendingRequest = nullptr; + mPendingContext = nullptr; + } + return NS_OK; +} |