/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Pierre Phaneuf * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "prlog.h" #include "nsCOMPtr.h" #include "modlmime.h" #include "nsCRT.h" #include "mimeobj.h" #include "mimemsg.h" #include "mimetric.h" /* for MIME_RichtextConverter */ #include "mimethtm.h" #include "mimemsig.h" #include "mimemrel.h" #include "mimemalt.h" #include "mimebuf.h" #include "mimemapl.h" #include "prprf.h" #include "mimei.h" /* for moved MimeDisplayData struct */ #include "mimebuf.h" #include "prmem.h" #include "plstr.h" #include "prmem.h" #include "mimemoz2.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsIServiceManager.h" #include "nsFileSpec.h" #include "comi18n.h" #include "nsIStringBundle.h" #include "nsString.h" #include "nsReadableUtils.h" #include "nsMimeStringResources.h" #include "nsStreamConverter.h" #include "nsIMsgSend.h" #include "nsIMsgMailNewsUrl.h" #include "nsSpecialSystemDirectory.h" #include "mozITXTToHTMLConv.h" #include "nsCExternalHandlerService.h" #include "nsIMIMEService.h" #include "nsIImapUrl.h" #include "nsMsgI18N.h" #include "nsICharsetConverterManager.h" #include "nsICharsetAlias.h" #include "nsMimeTypes.h" #include "nsIIOService.h" #include "nsIURI.h" #include "nsNetCID.h" #include "nsIMsgWindow.h" #include "nsMsgUtils.h" #include "nsIChannel.h" #include "nsICacheEntryDescriptor.h" #include "nsICacheSession.h" #include "nsITransport.h" #include "mimeebod.h" #include "mimeeobj.h" // #include "nsXPCOM.h" #include "nsParserCIID.h" #include "nsIParser.h" #include "nsIHTMLContentSink.h" #include "nsIContentSerializer.h" #include "nsLayoutCID.h" #include "nsIComponentManager.h" #include "nsReadableUtils.h" #include "nsIHTMLToTextSink.h" #include "mozISanitizingSerializer.h" // static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID); // static NS_DEFINE_CID(kParserCID, NS_PARSER_CID); static NS_DEFINE_CID(kNavDTDCID, NS_CNAVDTD_CID); // #ifdef HAVE_MIME_DATA_SLOT #define LOCK_LAST_CACHED_MESSAGE #endif void ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs); static MimeHeadersState MIME_HeaderType; static PRBool MIME_WrapLongLines; static PRBool MIME_VariableWidthPlaintext; // For string bundle access routines... static nsCOMPtr stringBundle = nsnull; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Attachment handling routines //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MimeObject *mime_get_main_object(MimeObject* obj); nsresult ProcessBodyAsAttachment(MimeObject *obj, nsMsgAttachmentData **data) { nsMsgAttachmentData *tmp; PRInt32 n; char *disp = nsnull; char *charset = nsnull; // Ok, this is the special case when somebody sends an "attachment" as the body // of an RFC822 message...I really don't think this is the way this should be done. // I belive this should really be a multipart/mixed message with an empty body part, // but what can ya do...our friends to the North seem to do this. // MimeObject *child = obj; n = 1; *data = (nsMsgAttachmentData *)PR_Malloc( (n + 1) * sizeof(nsMsgAttachmentData)); if (!*data) return NS_ERROR_OUT_OF_MEMORY; tmp = *data; memset(*data, 0, (n + 1) * sizeof(nsMsgAttachmentData)); tmp->real_type = child->content_type ? nsCRT::strdup(child->content_type) : NULL; tmp->real_encoding = child->encoding ? nsCRT::strdup(child->encoding) : NULL; disp = MimeHeaders_get(child->headers, HEADER_CONTENT_DISPOSITION, PR_FALSE, PR_FALSE); tmp->real_name = MimeHeaders_get_parameter(disp, "name", &charset, NULL); if (tmp->real_name) { char *fname = NULL; fname = mime_decode_filename(tmp->real_name, charset, obj->options); nsMemory::Free(charset); if (fname && fname != tmp->real_name) { PR_Free(tmp->real_name); tmp->real_name = fname; } } else { tmp->real_name = MimeHeaders_get_name(child->headers, obj->options); } if ( (!tmp->real_name) && (tmp->real_type) && (nsCRT::strncasecmp(tmp->real_type, "text", 4)) ) ValidateRealName(tmp, child->headers); char *tmpURL = nsnull; char *id = nsnull; char *id_imap = nsnull; id = mime_part_address (obj); if (obj->options->missing_parts) id_imap = mime_imap_part_address (obj); if (! id) { PR_FREEIF(*data); PR_FREEIF(id_imap); return NS_ERROR_OUT_OF_MEMORY; } if (obj->options && obj->options->url) { const char *url = obj->options->url; nsresult rv; if (id_imap && id) { // if this is an IMAP part. tmpURL = mime_set_url_imap_part(url, id_imap, id); rv = nsMimeNewURI(&(tmp->url), tmpURL, nsnull); } else { // This is just a normal MIME part as usual. tmpURL = mime_set_url_part(url, id, PR_TRUE); rv = nsMimeNewURI(&(tmp->url), tmpURL, nsnull); } if ( (!tmp->url) || (NS_FAILED(rv)) ) { PR_FREEIF(*data); PR_FREEIF(id); PR_FREEIF(id_imap); return NS_ERROR_OUT_OF_MEMORY; } } PR_FREEIF(id); PR_FREEIF(id_imap); PR_FREEIF(tmpURL); tmp->description = MimeHeaders_get(child->headers, HEADER_CONTENT_DESCRIPTION, PR_FALSE, PR_FALSE); return NS_OK; } PRInt32 CountTotalMimeAttachments(MimeContainer *aObj) { PRInt32 i; PRInt32 rc = 0; if ( (!aObj) || (!aObj->children) || (aObj->nchildren <= 0) ) return 0; if (mime_typep((MimeObject *)aObj, (MimeObjectClass *)&mimeExternalBodyClass)) return 0; for (i=0; inchildren; i++) rc += CountTotalMimeAttachments((MimeContainer *)aObj->children[i]) + 1; return rc; } void ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs) { // Sanity. if (!aAttach) return; // Do we need to validate? if ( (aAttach->real_name) && (*(aAttach->real_name)) ) return; // Internal MIME structures need not be named! if ( (!aAttach->real_type) || (aAttach->real_type && !nsCRT::strncasecmp(aAttach->real_type, "multipart", 9)) ) return; // Special case...if this is a enclosed RFC822 message, give it a nice // name. if (aAttach->real_type && !nsCRT::strcasecmp(aAttach->real_type, MESSAGE_RFC822)) { NS_ASSERTION(aHdrs, "How comes the object's headers is null!"); if (aHdrs && aHdrs->munged_subject) aAttach->real_name = PR_smprintf("%s.eml", aHdrs->munged_subject); else NS_MsgSACopy(&(aAttach->real_name), "ForwardedMessage.eml"); return; } // // Now validate any other name we have for the attachment! // if (!aAttach->real_name || *aAttach->real_name == 0) { nsString newAttachName(NS_LITERAL_STRING("attachment")); nsresult rv = NS_OK; nsCAutoString contentType (aAttach->real_type); PRInt32 pos = contentType.FindChar(';'); if (pos > 0) contentType.Truncate(pos); nsCOMPtr mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { nsCAutoString fileExtension; rv = mimeFinder->GetPrimaryExtension(contentType, EmptyCString(), fileExtension); if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty()) { newAttachName.Append(PRUnichar('.')); AppendUTF8toUTF16(fileExtension, newAttachName); } } aAttach->real_name = ToNewCString(newAttachName); } } static PRInt32 attIndex = 0; nsresult GenerateAttachmentData(MimeObject *object, const char *aMessageURL, MimeDisplayOptions *options, PRBool isAnAppleDoublePart, nsMsgAttachmentData *aAttachData) { nsXPIDLCString imappart; nsXPIDLCString part; PRBool isIMAPPart; PRBool isExternalAttachment = PR_FALSE; /* be sure the object has not be marked as Not to be an attachment */ if (object->dontShowAsAttachment) return NS_OK; part.Adopt(mime_part_address(object)); if (part.IsEmpty()) return NS_ERROR_OUT_OF_MEMORY; if (options->missing_parts) imappart.Adopt(mime_imap_part_address(object)); char *urlSpec = nsnull; if (!imappart.IsEmpty()) { isIMAPPart = PR_TRUE; urlSpec = mime_set_url_imap_part(aMessageURL, imappart.get(), part.get()); } else { isIMAPPart = PR_FALSE; char *no_part_url = nsnull; if (options->part_to_load && options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) no_part_url = mime_get_base_url(aMessageURL); if (no_part_url) { urlSpec = mime_set_url_part(no_part_url, part.get(), PR_TRUE); PR_Free(no_part_url); } else { // if the mime object contains an external attachment URL, then use it, otherwise // fall back to creating an attachment url based on the message URI and the // part number. urlSpec = mime_external_attachment_url(object); isExternalAttachment = urlSpec ? PR_TRUE : PR_FALSE; if (!urlSpec) urlSpec = mime_set_url_part(aMessageURL, part.get(), PR_TRUE); } } if (!urlSpec) return NS_ERROR_OUT_OF_MEMORY; if ((options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) && (nsCRT::strncasecmp(aMessageURL, urlSpec, strlen(urlSpec)) == 0)) return NS_OK; nsMsgAttachmentData *tmp = &(aAttachData[attIndex++]); nsresult rv = nsMimeNewURI(&(tmp->url), urlSpec, nsnull); PR_FREEIF(urlSpec); if ( (NS_FAILED(rv)) || (!tmp->url) ) return NS_ERROR_OUT_OF_MEMORY; tmp->real_type = object->content_type ? nsCRT::strdup(object->content_type) : nsnull; tmp->real_encoding = object->encoding ? nsCRT::strdup(object->encoding) : nsnull; tmp->isExternalAttachment = isExternalAttachment; PRInt32 i; char *charset = nsnull; char *disp = MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, PR_FALSE, PR_FALSE); if (disp) { tmp->real_name = MimeHeaders_get_parameter(disp, "filename", &charset, nsnull); if (isAnAppleDoublePart) for (i = 0; i < 2 && !tmp->real_name; i ++) { PR_FREEIF(disp); nsMemory::Free(charset); disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_DISPOSITION, PR_FALSE, PR_FALSE); tmp->real_name = MimeHeaders_get_parameter(disp, "filename", &charset, nsnull); } if (tmp->real_name) { // check encoded type // // The parameter of Content-Disposition must use RFC 2231. // But old Netscape 4.x and Outlook Express etc. use RFC2047. // So we should parse both types. char *fname = nsnull; fname = mime_decode_filename(tmp->real_name, charset, options); nsMemory::Free(charset); if (fname && fname != tmp->real_name) { PR_FREEIF(tmp->real_name); tmp->real_name = fname; } } PR_FREEIF(disp); } disp = MimeHeaders_get(object->headers, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE); if (disp) { tmp->x_mac_type = MimeHeaders_get_parameter(disp, PARAM_X_MAC_TYPE, nsnull, nsnull); tmp->x_mac_creator= MimeHeaders_get_parameter(disp, PARAM_X_MAC_CREATOR, nsnull, nsnull); if (!tmp->real_name || *tmp->real_name == 0) { PR_FREEIF(tmp->real_name); tmp->real_name = MimeHeaders_get_parameter(disp, "name", &charset, nsnull); if (isAnAppleDoublePart) // the data fork is the 2nd part, and we should ALWAYS look there first for the file name for (i = 1; i >= 0 && !tmp->real_name; i --) { PR_FREEIF(disp); nsMemory::Free(charset); disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE); tmp->real_name = MimeHeaders_get_parameter(disp, "name", &charset, nsnull); } if (tmp->real_name) { // check encoded type // // The parameter of Content-Disposition must use RFC 2231. // But old Netscape 4.x and Outlook Express etc. use RFC2047. // So we should parse both types. char *fname = nsnull; fname = mime_decode_filename(tmp->real_name, charset, options); nsMemory::Free(charset); if (fname && fname != tmp->real_name) { PR_Free(tmp->real_name); tmp->real_name = fname; } } } PR_FREEIF(disp); } tmp->description = MimeHeaders_get(object->headers, HEADER_CONTENT_DESCRIPTION, PR_FALSE, PR_FALSE); // Now, do the right thing with the name! if (!tmp->real_name && nsCRT::strcasecmp(tmp->real_type, MESSAGE_RFC822)) { /* If this attachment doesn't have a name, just give it one... */ tmp->real_name = MimeGetStringByID(MIME_MSG_DEFAULT_ATTACHMENT_NAME); if (tmp->real_name) { char *newName = PR_smprintf(tmp->real_name, part.get()); if (newName) { PR_Free(tmp->real_name); tmp->real_name = newName; } } else tmp->real_name = mime_part_address(object); } ValidateRealName(tmp, object->headers); return NS_OK; } nsresult BuildAttachmentList(MimeObject *anObject, nsMsgAttachmentData *aAttachData, const char *aMessageURL) { nsresult rv; PRInt32 i; MimeContainer *cobj = (MimeContainer *) anObject; if ( (!anObject) || (!cobj->children) || (!cobj->nchildren) || (mime_typep(anObject, (MimeObjectClass *)&mimeExternalBodyClass))) return NS_OK; for (i = 0; i < cobj->nchildren ; i++) { MimeObject *child = cobj->children[i]; // Skip the first child if it's in fact a message body if (i == 0) // it's the first child if (child->content_type) // and it's content-type is one of folowing... if (!nsCRT::strcasecmp (child->content_type, TEXT_PLAIN) || !nsCRT::strcasecmp (child->content_type, TEXT_HTML) || !nsCRT::strcasecmp (child->content_type, TEXT_MDL)) { if (child->headers) // and finally, be sure it doesn't have a content-disposition: attachment { char * disp = MimeHeaders_get (child->headers, HEADER_CONTENT_DISPOSITION, PR_TRUE, PR_FALSE); if (!disp || nsCRT::strcasecmp (disp, "attachment")) continue; } else continue; } // We should generate an attachment for leaf object only but... PRBool isALeafObject = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeLeafClass); // ...we will generate an attachment for inline message too. PRBool isAnInlineMessage = mime_typep(child, (MimeObjectClass *) &mimeMessageClass); // AppleDouble part need special care: we need to fetch the part as well its two // children for the needed info as they could be anywhere, eventually, they won't contain // a name or file name. In any case we need to build only one attachment data PRBool isAnAppleDoublePart = mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass) && ((MimeContainer *)child)->nchildren == 2; if (isALeafObject || isAnInlineMessage || isAnAppleDoublePart) { rv = GenerateAttachmentData(child, aMessageURL, anObject->options, isAnAppleDoublePart, aAttachData); NS_ENSURE_SUCCESS(rv, rv); } // Now build the attachment list for the children of our object... if (!isALeafObject && !isAnAppleDoublePart) { rv = BuildAttachmentList((MimeObject *)child, aAttachData, aMessageURL); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } extern "C" nsresult MimeGetAttachmentList(MimeObject *tobj, const char *aMessageURL, nsMsgAttachmentData **data) { MimeObject *obj; MimeContainer *cobj; PRInt32 n; PRBool isAnInlineMessage; if (!data) return 0; *data = nsnull; obj = mime_get_main_object(tobj); if (!obj) return 0; if (!mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeContainerClass)) { if (!PL_strcasecmp(obj->content_type, MESSAGE_RFC822)) return 0; else return ProcessBodyAsAttachment(obj, data); } isAnInlineMessage = mime_typep(obj, (MimeObjectClass *) &mimeMessageClass); cobj = (MimeContainer*) obj; n = CountTotalMimeAttachments(cobj); if (n <= 0) return n; //in case of an inline message (as body), we need an extra slot for the message itself //that we will fill later... if (isAnInlineMessage) n ++; *data = (nsMsgAttachmentData *)PR_Malloc( (n + 1) * sizeof(nsMsgAttachmentData)); if (!*data) return NS_ERROR_OUT_OF_MEMORY; attIndex = 0; memset(*data, 0, (n + 1) * sizeof(nsMsgAttachmentData)); // Now, build the list! nsresult rv; if (isAnInlineMessage) { rv = GenerateAttachmentData(obj, aMessageURL, obj->options, PR_FALSE, *data); NS_ENSURE_SUCCESS(rv, rv); } return BuildAttachmentList((MimeObject *) cobj, *data, aMessageURL); } extern "C" void MimeFreeAttachmentList(nsMsgAttachmentData *data) { if (data) { nsMsgAttachmentData *tmp; for (tmp = data ; tmp->url ; tmp++) { /* Can't do PR_FREEIF on `const' values... */ NS_IF_RELEASE(tmp->url); if (tmp->real_type) PR_Free((char *) tmp->real_type); if (tmp->real_encoding) PR_Free((char *) tmp->real_encoding); if (tmp->real_name) PR_Free((char *) tmp->real_name); if (tmp->x_mac_type) PR_Free((char *) tmp->x_mac_type); if (tmp->x_mac_creator) PR_Free((char *) tmp->x_mac_creator); if (tmp->description) PR_Free((char *) tmp->description); tmp->url = 0; tmp->real_type = 0; tmp->real_name = 0; tmp->description = 0; } PR_Free(data); } } extern "C" void NotifyEmittersOfAttachmentList(MimeDisplayOptions *opt, nsMsgAttachmentData *data) { PRInt32 i = 0; struct nsMsgAttachmentData *tmp = data; if (!tmp) return; while (tmp->url) { if (!tmp->real_name) { ++i; ++tmp; continue; } nsCAutoString spec; if ( tmp->url ) tmp->url->GetSpec(spec); mimeEmitterStartAttachment(opt, tmp->real_name, tmp->real_type, spec.get(), tmp->isExternalAttachment); mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_URL, spec.get()); if ( (opt->format_out == nsMimeOutput::nsMimeMessageQuoting) || (opt->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) || (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs) || (opt->format_out == nsMimeOutput::nsMimeMessagePrintOutput)) { mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_DESCRIPTION, tmp->description); mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_TYPE, tmp->real_type); mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_ENCODING, tmp->real_encoding); /* rhp - for now, just leave these here, but they are really not necessary printf("URL for Part : %s\n", spec); printf("Real Name : %s\n", tmp->real_name); printf("Desired Type : %s\n", tmp->desired_type); printf("Real Type : %s\n", tmp->real_type); printf("Real Encoding : %s\n", tmp->real_encoding); printf("Description : %s\n", tmp->description); printf("Mac Type : %s\n", tmp->x_mac_type); printf("Mac Creator : %s\n", tmp->x_mac_creator); */ } mimeEmitterEndAttachment(opt); ++i; ++tmp; } mimeEmitterEndAllAttachments(opt); } // Utility to create a nsIURI object... extern "C" nsresult nsMimeNewURI(nsIURI** aInstancePtrResult, const char *aSpec, nsIURI *aBase) { nsresult res; if (nsnull == aInstancePtrResult) return NS_ERROR_NULL_POINTER; nsCOMPtr pService(do_GetService(kIOServiceCID, &res)); if (NS_FAILED(res)) return NS_ERROR_FACTORY_NOT_REGISTERED; return pService->NewURI(nsDependentCString(aSpec), nsnull, aBase, aInstancePtrResult); } extern "C" nsresult SetMailCharacterSetToMsgWindow(MimeObject *obj, const char *aCharacterSet) { nsresult rv = NS_OK; if (obj && obj->options) { mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure); if (msd) { nsIChannel *channel = msd->channel; if (channel) { nsCOMPtr uri; channel->GetURI(getter_AddRefs(uri)); if (uri) { nsCOMPtr msgurl (do_QueryInterface(uri)); if (msgurl) { nsCOMPtr msgWindow; msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); if (msgWindow) rv = msgWindow->SetMailCharacterSet(!nsCRT::strcasecmp(aCharacterSet, "us-ascii") ? "ISO-8859-1" : aCharacterSet); } } } } } return rv; } static char * mime_file_type (const char *filename, void *stream_closure) { char *retType = nsnull; char *ext = nsnull; nsresult rv; ext = PL_strrchr(filename, '.'); if (ext) { ext++; nsCOMPtr mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); if (mimeFinder) { nsCAutoString type; mimeFinder->GetTypeFromExtension(nsDependentCString(ext), type); retType = ToNewCString(type); } } return retType; } int ConvertUsingEncoderAndDecoder(const char *stringToUse, PRInt32 inLength, nsIUnicodeEncoder *encoder, nsIUnicodeDecoder *decoder, char **pConvertedString, PRInt32 *outLength) { // buffer size 144 = // 72 (default line len for compose) // times 2 (converted byte len might be larger) const int klocalbufsize = 144; // do the conversion PRUnichar *unichars; PRInt32 unicharLength; PRInt32 srcLen = inLength; PRInt32 dstLength = 0; char *dstPtr; nsresult rv; // use this local buffer if possible PRUnichar localbuf[klocalbufsize+1]; if (inLength > klocalbufsize) { rv = decoder->GetMaxLength(stringToUse, srcLen, &unicharLength); // allocate temporary buffer to hold unicode string unichars = new PRUnichar[unicharLength]; } else { unichars = localbuf; unicharLength = klocalbufsize+1; } if (unichars == nsnull) { rv = NS_ERROR_OUT_OF_MEMORY; } else { // convert to unicode, replacing failed chars with 0xFFFD as in // the methode used in nsXMLHttpRequest::ConvertBodyToText and nsScanner::Append // // We will need several pass to convert the whole string if it has invalid characters // 'totalChars' is where the sum of the number of converted characters will be done // 'dataLen' is the number of character left to convert // 'outLen' is the number of characters still available in the output buffer as input of decoder->Convert // and the number of characters written in it as output. PRInt32 totalChars = 0, inBufferIndex = 0, outBufferIndex = 0; PRInt32 dataLen = srcLen, outLen = unicharLength; do { PRInt32 inBufferLength = dataLen; rv = decoder->Convert(&stringToUse[inBufferIndex], &inBufferLength, &unichars[outBufferIndex], &outLen); totalChars += outLen; // Done if conversion successful if (NS_SUCCEEDED(rv)) break; // We consume one byte, replace it with U+FFFD // and try the conversion again. outBufferIndex += outLen; unichars[outBufferIndex++] = PRUnichar(0xFFFD); // totalChars is updated here outLen = unicharLength - (++totalChars); inBufferIndex += inBufferLength + 1; dataLen -= inBufferLength + 1; decoder->Reset(); // If there is not at least one byte available after the one we // consumed, we're done } while ( dataLen > 0 ); rv = encoder->GetMaxLength(unichars, totalChars, &dstLength); // allocale an output buffer dstPtr = (char *) PR_Malloc(dstLength + 1); if (dstPtr == nsnull) { rv = NS_ERROR_OUT_OF_MEMORY; } else { PRInt32 buffLength = dstLength; // convert from unicode rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nsnull, '?'); if (NS_SUCCEEDED(rv)) { rv = encoder->Convert(unichars, &totalChars, dstPtr, &dstLength); if (NS_SUCCEEDED(rv)) { PRInt32 finLen = buffLength - dstLength; rv = encoder->Finish((char *)(dstPtr+dstLength), &finLen); if (NS_SUCCEEDED(rv)) { dstLength += finLen; } dstPtr[dstLength] = '\0'; *pConvertedString = dstPtr; // set the result string *outLength = dstLength; } } } if (inLength > klocalbufsize) delete [] unichars; } return NS_SUCCEEDED(rv) ? 0 : -1; } static int mime_convert_charset (const char *input_line, PRInt32 input_length, const char *input_charset, const char *output_charset, char **output_ret, PRInt32 *output_size_ret, void *stream_closure, nsIUnicodeDecoder *decoder, nsIUnicodeEncoder *encoder) { PRInt32 res = -1; char *convertedString = NULL; PRInt32 convertedStringLen = 0; if (encoder && decoder) { res = ConvertUsingEncoderAndDecoder(input_line, input_length, encoder, decoder, &convertedString, &convertedStringLen); } if (res != 0) { *output_ret = 0; *output_size_ret = 0; } else { *output_ret = (char *) convertedString; *output_size_ret = convertedStringLen; } return 0; } static int mime_output_fn(const char *buf, PRInt32 size, void *stream_closure) { PRUint32 written = 0; struct mime_stream_data *msd = (struct mime_stream_data *) stream_closure; if ( (!msd->pluginObj2) && (!msd->output_emitter) ) return -1; // Fire pending start request ((nsStreamConverter*)msd->pluginObj2)->FirePendingStartRequest(); // Now, write to the WriteBody method if this is a message body and not // a part retrevial if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) { if (msd->output_emitter) { msd->output_emitter->WriteBody(buf, (PRUint32) size, &written); } } else { if (msd->output_emitter) { msd->output_emitter->Write(buf, (PRUint32) size, &written); } } return written; } #ifdef XP_MAC static int compose_only_output_fn(const char *buf, PRInt32 size, void *stream_closure) { return 0; } #endif extern "C" int mime_display_stream_write (nsMIMESession *stream, const char* buf, PRInt32 size) { struct mime_stream_data *msd = (struct mime_stream_data *) ((nsMIMESession *)stream)->data_object; MimeObject *obj = (msd ? msd->obj : 0); if (!obj) return -1; // // Ok, now check to see if this is a display operation for a MIME Parts on Demand // enabled call. // if (msd->firstCheck) { if (msd->channel) { nsCOMPtr aUri; if (NS_SUCCEEDED(msd->channel->GetURI(getter_AddRefs(aUri)))) { nsCOMPtr imapURL = do_QueryInterface(aUri); if (imapURL) { nsImapContentModifiedType cModified; if (NS_SUCCEEDED(imapURL->GetContentModified(&cModified))) { if ( cModified != nsImapContentModifiedTypes::IMAP_CONTENT_NOT_MODIFIED ) msd->options->missing_parts = PR_TRUE; } } } } msd->firstCheck = PR_FALSE; } return obj->clazz->parse_buffer((char *) buf, size, obj); } extern "C" void mime_display_stream_complete (nsMIMESession *stream) { struct mime_stream_data *msd = (struct mime_stream_data *) ((nsMIMESession *)stream)->data_object; MimeObject *obj = (msd ? msd->obj : 0); if (obj) { int status; PRBool abortNow = PR_FALSE; if ((obj->options) && (obj->options->headers == MimeHeadersOnly)) abortNow = PR_TRUE; status = obj->clazz->parse_eof(obj, abortNow); obj->clazz->parse_end(obj, (status < 0 ? PR_TRUE : PR_FALSE)); // // Ok, now we are going to process the attachment data by getting all // of the attachment info and then driving the emitter with this data. // if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) { nsMsgAttachmentData *attachments; nsresult rv = MimeGetAttachmentList(obj, msd->url_name, &attachments); if (NS_SUCCEEDED(rv)) { NotifyEmittersOfAttachmentList(msd->options, attachments); MimeFreeAttachmentList(attachments); } } // Release the conversion object - this has to be done after // we finish processing data. if ( obj->options) { NS_IF_RELEASE(obj->options->conv); } // Destroy the object now. PR_ASSERT(msd->options == obj->options); mime_free(obj); obj = NULL; if (msd->options) { delete msd->options; msd->options = 0; } } if (msd->headers) MimeHeaders_free (msd->headers); if (msd->url_name) nsCRT::free(msd->url_name); if (msd->orig_url_name) nsCRT::free(msd->orig_url_name); PR_FREEIF(msd); } extern "C" void mime_display_stream_abort (nsMIMESession *stream, int status) { struct mime_stream_data *msd = (struct mime_stream_data *) ((nsMIMESession *)stream)->data_object; MimeObject *obj = (msd ? msd->obj : 0); if (obj) { if (!obj->closed_p) obj->clazz->parse_eof(obj, PR_TRUE); if (!obj->parsed_p) obj->clazz->parse_end(obj, PR_TRUE); // Destroy code.... PR_ASSERT(msd->options == obj->options); mime_free(obj); if (msd->options) { delete msd->options; msd->options = 0; } } if (msd->headers) MimeHeaders_free (msd->headers); if (msd->url_name) nsCRT::free(msd->url_name); if (msd->orig_url_name) nsCRT::free(msd->orig_url_name); PR_FREEIF(msd); } #ifdef XP_MAC static PRUint32 mime_convert_chars_to_ostype(const char *osTypeStr) { if (!osTypeStr) return '????'; PRUint32 result; const char *p = osTypeStr; for (result = 0; *p; p++) { char C = *p; PRInt8 unhex = ((C >= '0' && C <= '9') ? C - '0' : ((C >= 'A' && C <= 'F') ? C - 'A' + 10 : ((C >= 'a' && C <= 'f') ? C - 'a' + 10 : -1))); if (unhex < 0) break; result = (result << 4) | unhex; } return result; } #endif static int mime_output_init_fn (const char *type, const char *charset, const char *name, const char *x_mac_type, const char *x_mac_creator, void *stream_closure) { struct mime_stream_data *msd = (struct mime_stream_data *) stream_closure; // Now, all of this stream creation is done outside of libmime, so this // is just a check of the pluginObj member and returning accordingly. if (!msd->pluginObj2) return -1; else return 0; } static void *mime_image_begin(const char *image_url, const char *content_type, void *stream_closure); static void mime_image_end(void *image_closure, int status); static char *mime_image_make_image_html(void *image_data); static int mime_image_write_buffer(const char *buf, PRInt32 size, void *image_closure); /* Interface between libmime and inline display of images: the abomination that is known as "internal-external-reconnect". */ class mime_image_stream_data { public: mime_image_stream_data(); struct mime_stream_data *msd; char *url; nsMIMESession *istream; nsCOMPtr memCacheOutputStream; PRBool m_shouldCacheImage; }; mime_image_stream_data::mime_image_stream_data() { url = nsnull; istream = nsnull; msd = nsnull; m_shouldCacheImage = PR_FALSE; } static void * mime_image_begin(const char *image_url, const char *content_type, void *stream_closure) { struct mime_stream_data *msd = (struct mime_stream_data *) stream_closure; class mime_image_stream_data *mid; mid = new mime_image_stream_data; if (!mid) return nsnull; mid->msd = msd; mid->url = (char *) nsCRT::strdup(image_url); if (!mid->url) { PR_Free(mid); return nsnull; } if (msd->channel) { nsCOMPtr uri; nsresult rv = msd->channel->GetURI(getter_AddRefs(uri)); if (NS_SUCCEEDED(rv) && uri) { nsCOMPtr mailUrl = do_QueryInterface(uri); if (mailUrl) { nsCOMPtr memCacheSession; mailUrl->GetImageCacheSession(getter_AddRefs(memCacheSession)); if (memCacheSession) { nsCOMPtr entry; // we may need to convert the image_url into just a part url - in any case, // it has to be the same as what imglib will be asking imap for later // on so that we'll find this in the memory cache. rv = memCacheSession->OpenCacheEntry(nsDependentCString(image_url), nsICache::ACCESS_READ_WRITE, nsICache::BLOCKING, getter_AddRefs(entry)); nsCacheAccessMode access; if (entry) { entry->GetAccessGranted(&access); #ifdef DEBUG_bienvenu printf("Mime opening cache entry for %s access = %ld\n", image_url, access); #endif // if we only got write access, then we should fill in this cache entry if (access & nsICache::ACCESS_WRITE && !(access & nsICache::ACCESS_READ)) { mailUrl->CacheCacheEntry(entry); entry->MarkValid(); // remember the content type as meta data so we can pull it out in the imap code // to feed the cache entry directly to imglib w/o going through mime. entry->SetMetaDataElement("contentType", content_type); rv = entry->OpenOutputStream(0, getter_AddRefs(mid->memCacheOutputStream)); if (NS_FAILED(rv)) return nsnull; } } } } } } mid->istream = (nsMIMESession *) msd->pluginObj2; return mid; } static void mime_image_end(void *image_closure, int status) { mime_image_stream_data *mid = (mime_image_stream_data *) image_closure; PR_ASSERT(mid); if (!mid) return; if (mid->memCacheOutputStream) mid->memCacheOutputStream->Close(); PR_FREEIF(mid->url); delete mid; } static char * mime_image_make_image_html(void *image_closure) { mime_image_stream_data *mid = (mime_image_stream_data *) image_closure; const char *prefix = "

"; const char *url; char *buf; PR_ASSERT(mid); if (!mid) return 0; /* Internal-external-reconnect only works when going to the screen. */ if (!mid->istream) return nsCRT::strdup("

\"[Image]\"

"); if ( (!mid->url) || (!(*mid->url)) ) url = ""; else url = mid->url; buf = (char *) PR_MALLOC (strlen(prefix) + strlen(suffix) + strlen(url) + 20) ; if (!buf) return 0; *buf = 0; PL_strcat (buf, prefix); PL_strcat (buf, url); PL_strcat (buf, suffix); return buf; } static int mime_image_write_buffer(const char *buf, PRInt32 size, void *image_closure) { mime_image_stream_data *mid = (mime_image_stream_data *) image_closure; struct mime_stream_data *msd = mid->msd; if ( ( (!msd->output_emitter) ) && ( (!msd->pluginObj2) ) ) return -1; // // If we get here, we are just eating the data this time around // and the returned URL will deal with writing the data to the viewer. // Just return the size value to the caller. // if (mid->memCacheOutputStream) { PRUint32 bytesWritten; mid->memCacheOutputStream->Write(buf, size, &bytesWritten); } return size; } MimeObject* mime_get_main_object(MimeObject* obj) { MimeContainer *cobj; if (!(mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMessageClass))) { return obj; } cobj = (MimeContainer*) obj; if (cobj->nchildren != 1) return obj; obj = cobj->children[0]; while (obj) { if ( (!mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMultipartSignedClass)) && (PL_strcasecmp(obj->content_type, MULTIPART_SIGNED) != 0) ) { return obj; } else { if (mime_subclass_p(obj->clazz, (MimeObjectClass*)&mimeContainerClass)) { // We don't care about a signed/smime object; Go inside to the // thing that we signed or smime'ed // cobj = (MimeContainer*) obj; if (cobj->nchildren > 0) obj = cobj->children[0]; else obj = nsnull; } else { // we received a message with a child object that looks like a signed // object, but it is not a subclass of mimeContainer, so let's // return the given child object. return obj; } } } return nsnull; } PRBool MimeObjectChildIsMessageBody(MimeObject *obj, PRBool *isAlternativeOrRelated) { char *disp = 0; PRBool bRet = PR_FALSE; MimeObject *firstChild = 0; MimeContainer *container = (MimeContainer*) obj; if (isAlternativeOrRelated) *isAlternativeOrRelated = PR_FALSE; if (!container || !mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeContainerClass)) { return bRet; } else if (mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMultipartRelatedClass)) { if (isAlternativeOrRelated) *isAlternativeOrRelated = PR_TRUE; return bRet; } else if (mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMultipartAlternativeClass)) { if (isAlternativeOrRelated) *isAlternativeOrRelated = PR_TRUE; return bRet; } if (container->children) firstChild = container->children[0]; if (!firstChild || !firstChild->content_type || !firstChild->headers) return bRet; disp = MimeHeaders_get (firstChild->headers, HEADER_CONTENT_DISPOSITION, PR_TRUE, PR_FALSE); if (disp /* && !nsCRT::strcasecmp (disp, "attachment") */) bRet = PR_FALSE; else if (!nsCRT::strcasecmp (firstChild->content_type, TEXT_PLAIN) || !nsCRT::strcasecmp (firstChild->content_type, TEXT_HTML) || !nsCRT::strcasecmp (firstChild->content_type, TEXT_MDL) || !nsCRT::strcasecmp (firstChild->content_type, MULTIPART_ALTERNATIVE) || !nsCRT::strcasecmp (firstChild->content_type, MULTIPART_RELATED) || !nsCRT::strcasecmp (firstChild->content_type, MESSAGE_NEWS) || !nsCRT::strcasecmp (firstChild->content_type, MESSAGE_RFC822)) bRet = PR_TRUE; else bRet = PR_FALSE; PR_FREEIF(disp); return bRet; } // // New Stream Converter Interface // // Get the connnection to prefs service manager nsIPrefBranch * GetPrefBranch(MimeDisplayOptions *opt) { if (!opt) return nsnull; return opt->m_prefBranch; } // Get the text converter... mozITXTToHTMLConv * GetTextConverter(MimeDisplayOptions *opt) { if (!opt) return nsnull; return opt->conv; } MimeDisplayOptions::MimeDisplayOptions() { conv = nsnull; // For text conversion... format_out = 0; // The format out type url = nsnull; memset(&headers,0, sizeof(headers)); fancy_headers_p = PR_FALSE; output_vcard_buttons_p = PR_FALSE; fancy_links_p = PR_FALSE; variable_width_plaintext_p = PR_FALSE; wrap_long_lines_p = PR_FALSE; rot13_p = PR_FALSE; part_to_load = nsnull; write_html_p = PR_FALSE; decrypt_p = PR_FALSE; nice_html_only_p = PR_FALSE; whattodo = 0 ; default_charset = nsnull; override_charset = PR_FALSE; force_user_charset = PR_FALSE; stream_closure = nsnull; /* For setting up the display stream, so that the MIME parser can inform the caller of the type of the data it will be getting. */ output_init_fn = nsnull; output_fn = nsnull; output_closure = nsnull; charset_conversion_fn = nsnull; rfc1522_conversion_p = PR_FALSE; file_type_fn = nsnull; passwd_prompt_fn = nsnull; html_closure = nsnull; generate_header_html_fn = nsnull; generate_post_header_html_fn = nsnull; generate_footer_html_fn = nsnull; generate_reference_url_fn = nsnull; generate_mailto_url_fn = nsnull; generate_news_url_fn = nsnull; image_begin = nsnull; image_end = nsnull; image_write_buffer = nsnull; make_image_html = nsnull; state = nsnull; #ifdef MIME_DRAFTS decompose_file_p = PR_FALSE; done_parsing_outer_headers = PR_FALSE; is_multipart_msg = PR_FALSE; decompose_init_count = 0; signed_p = PR_FALSE; caller_need_root_headers = PR_FALSE; decompose_headers_info_fn = nsnull; decompose_file_init_fn = nsnull; decompose_file_output_fn = nsnull; decompose_file_close_fn = nsnull; #endif /* MIME_DRAFTS */ attachment_icon_layer_id = 0; missing_parts = PR_FALSE; show_attachment_inline_p = PR_FALSE; } MimeDisplayOptions::~MimeDisplayOptions() { PR_FREEIF(part_to_load); PR_FREEIF(default_charset); } //////////////////////////////////////////////////////////////// // Bridge routines for new stream converter XP-COM interface //////////////////////////////////////////////////////////////// extern "C" void * mime_bridge_create_display_stream( nsIMimeEmitter *newEmitter, nsStreamConverter *newPluginObj2, nsIURI *uri, nsMimeOutputType format_out, PRUint32 whattodo, nsIChannel *aChannel) { int status = 0; MimeObject *obj; struct mime_stream_data *msd; nsMIMESession *stream = 0; if (!uri) return nsnull; msd = PR_NEWZAP(struct mime_stream_data); if (!msd) return NULL; // Assign the new mime emitter - will handle output operations msd->output_emitter = newEmitter; msd->firstCheck = PR_TRUE; // Store the URL string for this decode operation nsCAutoString urlString; nsresult rv; // Keep a hold of the channel... msd->channel = aChannel; rv = uri->GetSpec(urlString); if (NS_SUCCEEDED(rv)) { if (!urlString.IsEmpty()) { msd->url_name = ToNewCString(urlString); if (!(msd->url_name)) { PR_FREEIF(msd); return NULL; } nsCOMPtr msgUrl = do_QueryInterface(uri); if (msgUrl) msgUrl->GetOriginalSpec(&msd->orig_url_name); } } msd->format_out = format_out; // output format msd->pluginObj2 = newPluginObj2; // the plugin object pointer msd->options = new MimeDisplayOptions; if (!msd->options) { PR_Free(msd); return 0; } // memset(msd->options, 0, sizeof(*msd->options)); msd->options->format_out = format_out; // output format msd->options->m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) { PR_FREEIF(msd); return nsnull; } // Need the text converter... rv = CallCreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &(msd->options->conv)); if (NS_FAILED(rv)) { msd->options->m_prefBranch = 0; PR_FREEIF(msd); return nsnull; } // // Set the defaults, based on the context, and the output-type. // MIME_HeaderType = MimeHeadersAll; msd->options->write_html_p = PR_TRUE; switch (format_out) { case nsMimeOutput::nsMimeMessageSplitDisplay: // the wrapper HTML output to produce the split header/body display case nsMimeOutput::nsMimeMessageHeaderDisplay: // the split header/body display case nsMimeOutput::nsMimeMessageBodyDisplay: // the split header/body display msd->options->fancy_headers_p = PR_TRUE; msd->options->output_vcard_buttons_p = PR_TRUE; msd->options->fancy_links_p = PR_TRUE; break; case nsMimeOutput::nsMimeMessageSaveAs: // Save As operations case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted/printed output case nsMimeOutput::nsMimeMessagePrintOutput: msd->options->fancy_headers_p = PR_TRUE; msd->options->fancy_links_p = PR_TRUE; break; case nsMimeOutput::nsMimeMessageBodyQuoting: // only HTML body quoted output MIME_HeaderType = MimeHeadersNone; break; case nsMimeOutput::nsMimeMessageAttach: // handling attachment storage msd->options->write_html_p = PR_FALSE; break; case nsMimeOutput::nsMimeMessageRaw: // the raw RFC822 data (view source) and attachments case nsMimeOutput::nsMimeMessageDraftOrTemplate: // Loading drafts & templates case nsMimeOutput::nsMimeMessageEditorTemplate: // Loading templates into editor case nsMimeOutput::nsMimeMessageFilterSniffer: // generating an output that can be scan by a message filter break; case nsMimeOutput::nsMimeMessageDecrypt: msd->options->decrypt_p = PR_TRUE; msd->options->write_html_p = PR_FALSE; break; } //////////////////////////////////////////////////////////// // Now, get the libmime prefs... //////////////////////////////////////////////////////////// MIME_WrapLongLines = PR_TRUE; MIME_VariableWidthPlaintext = PR_TRUE; msd->options->force_user_charset = PR_FALSE; if (msd->options->m_prefBranch) { msd->options->m_prefBranch->GetBoolPref("mail.wrap_long_lines", &MIME_WrapLongLines); msd->options->m_prefBranch->GetBoolPref("mail.fixed_width_messages", &MIME_VariableWidthPlaintext); // // Charset overrides takes place here // // We have a bool pref (mail.force_user_charset) to deal with attachments. // 1) If true - libmime does NO conversion and just passes it through to raptor // 2) If false, then we try to use the charset of the part and if not available, // the charset of the root message // msd->options->m_prefBranch->GetBoolPref("mail.force_user_charset", &(msd->options->force_user_charset)); msd->options->m_prefBranch->GetBoolPref("mail.inline_attachments", &(msd->options->show_attachment_inline_p)); } /* This pref is written down in with the opposite sense of what we like to use... */ MIME_VariableWidthPlaintext = !MIME_VariableWidthPlaintext; msd->options->wrap_long_lines_p = MIME_WrapLongLines; msd->options->headers = MIME_HeaderType; // We need to have the URL to be able to support the various // arguments status = mime_parse_url_options(msd->url_name, msd->options); if (status < 0) { PR_FREEIF(msd->options->part_to_load); PR_Free(msd->options); PR_Free(msd); return 0; } if (msd->options->headers == MimeHeadersMicro && (msd->url_name == NULL || (strncmp(msd->url_name, "news:", 5) != 0 && strncmp(msd->url_name, "snews:", 6) != 0)) ) msd->options->headers = MimeHeadersMicroPlus; msd->options->url = msd->url_name; msd->options->output_init_fn = mime_output_init_fn; msd->options->output_fn = mime_output_fn; msd->options->whattodo = whattodo; msd->options->charset_conversion_fn = mime_convert_charset; msd->options->rfc1522_conversion_p = PR_TRUE; msd->options->file_type_fn = mime_file_type; msd->options->stream_closure = msd; msd->options->passwd_prompt_fn = 0; msd->options->image_begin = mime_image_begin; msd->options->image_end = mime_image_end; msd->options->make_image_html = mime_image_make_image_html; msd->options->image_write_buffer = mime_image_write_buffer; msd->options->variable_width_plaintext_p = MIME_VariableWidthPlaintext; // If this is a part, then we should emit the HTML to render the data // (i.e. embedded images) if (msd->options->part_to_load && msd->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay) msd->options->write_html_p = PR_FALSE; obj = mime_new ((MimeObjectClass *)&mimeMessageClass, (MimeHeaders *) NULL, MESSAGE_RFC822); if (!obj) { delete msd->options; PR_Free(msd); return 0; } obj->options = msd->options; msd->obj = obj; /* Both of these better not be true at the same time. */ PR_ASSERT(! (obj->options->decrypt_p && obj->options->write_html_p)); stream = PR_NEW(nsMIMESession); if (!stream) { delete msd->options; PR_Free(msd); PR_Free(obj); return 0; } memset (stream, 0, sizeof (*stream)); stream->name = "MIME Conversion Stream"; stream->complete = mime_display_stream_complete; stream->abort = mime_display_stream_abort; stream->put_block = mime_display_stream_write; stream->data_object = msd; status = obj->clazz->initialize(obj); if (status >= 0) status = obj->clazz->parse_begin(obj); if (status < 0) { PR_Free(stream); delete msd->options; PR_Free(msd); PR_Free(obj); return 0; } return stream; } // // Emitter Wrapper Routines! // nsIMimeEmitter * GetMimeEmitter(MimeDisplayOptions *opt) { mime_stream_data *msd = (mime_stream_data *)opt->stream_closure; if (!msd) return NULL; nsIMimeEmitter *ptr = (nsIMimeEmitter *)(msd->output_emitter); return ptr; } mime_stream_data * GetMSD(MimeDisplayOptions *opt) { if (!opt) return nsnull; mime_stream_data *msd = (mime_stream_data *)opt->stream_closure; return msd; } PRBool NoEmitterProcessing(nsMimeOutputType format_out) { if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) || (format_out == nsMimeOutput::nsMimeMessageEditorTemplate)) return PR_TRUE; else return PR_FALSE; } extern "C" nsresult mimeEmitterAddAttachmentField(MimeDisplayOptions *opt, const char *field, const char *value) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->AddAttachmentField(field, value); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterAddHeaderField(MimeDisplayOptions *opt, const char *field, const char *value) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->AddHeaderField(field, value); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterAddAllHeaders(MimeDisplayOptions *opt, const char *allheaders, const PRInt32 allheadersize) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->AddAllHeaders(allheaders, allheadersize); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterStartAttachment(MimeDisplayOptions *opt, const char *name, const char *contentType, const char *url, PRBool aIsExternalAttachment) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->StartAttachment(name, contentType, url, aIsExternalAttachment); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterEndAttachment(MimeDisplayOptions *opt) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; if (emitter) return emitter->EndAttachment(); else return NS_OK; } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterEndAllAttachments(MimeDisplayOptions *opt) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; if (emitter) return emitter->EndAllAttachments(); else return NS_OK; } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterStartBody(MimeDisplayOptions *opt, PRBool bodyOnly, const char *msgID, const char *outCharset) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->StartBody(bodyOnly, msgID, outCharset); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterEndBody(MimeDisplayOptions *opt) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->EndBody(); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterEndHeader(MimeDisplayOptions *opt) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->EndHeader(); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterUpdateCharacterSet(MimeDisplayOptions *opt, const char *aCharset) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->UpdateCharacterSet(aCharset); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeEmitterStartHeader(MimeDisplayOptions *opt, PRBool rootMailHeader, PRBool headerOnly, const char *msgID, const char *outCharset) { // Check for draft processing... if (NoEmitterProcessing(opt->format_out)) return NS_OK; mime_stream_data *msd = GetMSD(opt); if (!msd) return NS_ERROR_FAILURE; if (msd->output_emitter) { nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; return emitter->StartHeader(rootMailHeader, headerOnly, msgID, outCharset); } return NS_ERROR_FAILURE; } extern "C" nsresult mimeSetNewURL(nsMIMESession *stream, char *url) { if ( (!stream) || (!url) || (!*url) ) return NS_ERROR_FAILURE; mime_stream_data *msd = (mime_stream_data *)stream->data_object; if (!msd) return NS_ERROR_FAILURE; char *tmpPtr = nsCRT::strdup(url); if (!tmpPtr) return NS_ERROR_FAILURE; PR_FREEIF(msd->url_name); msd->url_name = nsCRT::strdup(tmpPtr); return NS_OK; } #define MIME_URL "chrome://messenger/locale/mime.properties" extern "C" char * MimeGetStringByID(PRInt32 stringID) { char *tempString = nsnull; const char *resultString = "???"; nsresult res = NS_OK; if (!stringBundle) { char* propertyURL = NULL; propertyURL = MIME_URL; nsCOMPtr sBundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &res); if (NS_SUCCEEDED(res) && (nsnull != sBundleService)) { res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(stringBundle)); } } if (stringBundle) { nsXPIDLString v; res = stringBundle->GetStringFromID(stringID, getter_Copies(v)); if (NS_SUCCEEDED(res)) tempString = ToNewUTF8String(v); } if (!tempString) tempString = nsCRT::strdup(resultString); return tempString; } void PR_CALLBACK ResetChannelCharset(MimeObject *obj) { if (obj->options && obj->options->stream_closure && obj->options->default_charset && obj->headers ) { mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure); char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, PR_FALSE, PR_FALSE); if ( (ct) && (msd) && (msd->channel) ) { char *ptr = strstr(ct, "charset="); if (ptr) { // First, setup the channel! msd->channel->SetContentType(nsDependentCString(ct)); // Second, if this is a Save As operation, then we need to convert // to override the output charset! mime_stream_data *msd = GetMSD(obj->options); if ( (msd) && (msd->format_out == nsMimeOutput::nsMimeMessageSaveAs) ) { // Extract the charset alone char *cSet = nsnull; if (*(ptr+8) == '"') cSet = nsCRT::strdup(ptr+9); else cSet = nsCRT::strdup(ptr+8); if (cSet) { char *ptr2 = cSet; while ( (*cSet) && (*cSet != ' ') && (*cSet != ';') && (*cSet != nsCRT::CR) && (*cSet != nsCRT::LF) && (*cSet != '"') ) ptr2++; if (*cSet) { PR_FREEIF(obj->options->default_charset); obj->options->default_charset = nsCRT::strdup(cSet); obj->options->override_charset = PR_TRUE; } PR_FREEIF(cSet); } } } PR_FREEIF(ct); } } } //////////////////////////////////////////////////////////// // Function to get up mail/news fontlang //////////////////////////////////////////////////////////// nsresult GetMailNewsFont(MimeObject *obj, PRBool styleFixed, PRInt32 *fontPixelSize, PRInt32 *fontSizePercentage, nsCString& fontLang) { nsresult rv = NS_OK; nsIPrefBranch *prefBranch = GetPrefBranch(obj->options); if (prefBranch) { MimeInlineText *text = (MimeInlineText *) obj; nsCAutoString charset; // get a charset if (!text->initializeCharset) ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj); if (!text->charset || !(*text->charset)) charset.Assign("us-ascii"); else charset.Assign(text->charset); nsCOMPtr charSetConverterManager2; nsCOMPtr langGroupAtom; nsCAutoString prefStr; ToLowerCase(charset); charSetConverterManager2 = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); if ( NS_FAILED(rv)) return rv; // get a language, e.g. x-western, ja rv = charSetConverterManager2->GetCharsetLangGroup(charset.get(), getter_AddRefs(langGroupAtom)); if (NS_FAILED(rv)) return rv; rv = langGroupAtom->ToUTF8String(fontLang); if (NS_FAILED(rv)) return rv; // get a font size from pref prefStr.Assign(!styleFixed ? "font.size.variable." : "font.size.fixed."); prefStr.Append(fontLang); rv = prefBranch->GetIntPref(prefStr.get(), fontPixelSize); if (NS_FAILED(rv)) return rv; nsCOMPtr prefDefBranch; nsCOMPtr prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if(prefSvc) rv = prefSvc->GetDefaultBranch("", getter_AddRefs(prefDefBranch)); if(!prefDefBranch) return rv; // get original font size PRInt32 originalSize; rv = prefDefBranch->GetIntPref(prefStr.get(), &originalSize); if (NS_FAILED(rv)) return rv; // calculate percentage *fontSizePercentage = originalSize ? (PRInt32)((float)*fontPixelSize / (float)originalSize * 100) : 0; } return NS_OK; } /* This function syncronously converts an HTML document (as string) to plaintext (as string) using the Gecko converter. flags: see nsIDocumentEncoder.h */ // TODO: |printf|s? /* */ nsresult HTML2Plaintext(const nsString& inString, nsString& outString, PRUint32 flags, PRUint32 wrapCol) { nsresult rv = NS_OK; #if DEBUG_BenB printf("Converting HTML to plaintext\n"); char* charstar = ToNewUTF8String(inString); printf("HTML source is:\n--------------------\n%s--------------------\n", charstar); delete[] charstar; #endif // Create a parser nsCOMPtr parser = do_CreateInstance(kParserCID); NS_ENSURE_TRUE(parser, NS_ERROR_FAILURE); // Create the appropriate output sink nsCOMPtr sink = do_CreateInstance(NS_PLAINTEXTSINK_CONTRACTID); NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE); nsCOMPtr textSink(do_QueryInterface(sink)); NS_ENSURE_TRUE(textSink, NS_ERROR_FAILURE); textSink->Initialize(&outString, flags, wrapCol); parser->SetContentSink(sink); nsCOMPtr dtd = do_CreateInstance(kNavDTDCID); NS_ENSURE_TRUE(dtd, NS_ERROR_FAILURE); parser->RegisterDTD(dtd); rv = parser->Parse(inString, 0, NS_LITERAL_CSTRING("text/html"), PR_FALSE, PR_TRUE); // Aah! How can NS_ERROR and NS_ABORT_IF_FALSE be no-ops in release builds??? if (NS_FAILED(rv)) { NS_ERROR("Parse() failed!"); return rv; } #if DEBUG_BenB charstar = ToNewUTF8String(outString); printf("Plaintext is:\n--------------------\n%s--------------------\n", charstar); delete[] charstar; #endif return rv; } // /* This function syncronously sanitizes an HTML document (string->string) using the Gecko ContentSink mozISanitizingHTMLSerializer. flags: currently unused allowedTags: see mozSanitizingHTMLSerializer::ParsePrefs() */ // copied from HTML2Plaintext above nsresult HTMLSanitize(const nsString& inString, nsString& outString, PRUint32 flags, const nsAString& allowedTags) { nsresult rv = NS_OK; #if DEBUG_BenB printf("Sanitizing HTML\n"); char* charstar = ToNewUTF8String(inString); printf("Original HTML is:\n--------------------\n%s--------------------\n", charstar); delete[] charstar; #endif // Create a parser nsCOMPtr parser = do_CreateInstance(kParserCID); NS_ENSURE_TRUE(parser, NS_ERROR_FAILURE); // Create the appropriate output sink nsCOMPtr sink = do_CreateInstance(MOZ_SANITIZINGHTMLSERIALIZER_CONTRACTID); NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE); nsCOMPtr sanSink(do_QueryInterface(sink)); NS_ENSURE_TRUE(sanSink, NS_ERROR_FAILURE); sanSink->Initialize(&outString, flags, allowedTags); parser->SetContentSink(sink); nsCOMPtr dtd = do_CreateInstance(kNavDTDCID); NS_ENSURE_TRUE(dtd, NS_ERROR_FAILURE); parser->RegisterDTD(dtd); rv = parser->Parse(inString, 0, NS_LITERAL_CSTRING("text/html"), PR_FALSE, PR_TRUE); if (NS_FAILED(rv)) { NS_ERROR("Parse() failed!"); return rv; } #if DEBUG_BenB charstar = ToNewUTF8String(outString); printf("Sanitized HTML is:\n--------------------\n%s--------------------\n", charstar); delete[] charstar; #endif return rv; }