diff options
Diffstat (limited to 'mailnews/mime/src/mimei.cpp')
-rw-r--r-- | mailnews/mime/src/mimei.cpp | 1920 |
1 files changed, 1920 insertions, 0 deletions
diff --git a/mailnews/mime/src/mimei.cpp b/mailnews/mime/src/mimei.cpp new file mode 100644 index 000000000..c0a134a62 --- /dev/null +++ b/mailnews/mime/src/mimei.cpp @@ -0,0 +1,1920 @@ +/* -*- Mode: C; tab-width: 4; 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/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#include "nsCOMPtr.h" +#include "mimeobj.h" /* MimeObject (abstract) */ +#include "mimecont.h" /* |--- MimeContainer (abstract) */ +#include "mimemult.h" /* | |--- MimeMultipart (abstract) */ +#include "mimemmix.h" /* | | |--- MimeMultipartMixed */ +#include "mimemdig.h" /* | | |--- MimeMultipartDigest */ +#include "mimempar.h" /* | | |--- MimeMultipartParallel */ +#include "mimemalt.h" /* | | |--- MimeMultipartAlternative */ +#include "mimemrel.h" /* | | |--- MimeMultipartRelated */ +#include "mimemapl.h" /* | | |--- MimeMultipartAppleDouble */ +#include "mimesun.h" /* | | |--- MimeSunAttachment */ +#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/ +#ifdef ENABLE_SMIME +#include "mimemcms.h" /* | | |---MimeMultipartSignedCMS */ +#endif +#include "mimecryp.h" /* | |--- MimeEncrypted (abstract) */ +#ifdef ENABLE_SMIME +#include "mimecms.h" /* | | |--- MimeEncryptedPKCS7 */ +#endif +#include "mimemsg.h" /* | |--- MimeMessage */ +#include "mimeunty.h" /* | |--- MimeUntypedText */ +#include "mimeleaf.h" /* |--- MimeLeaf (abstract) */ +#include "mimetext.h" /* | |--- MimeInlineText (abstract) */ +#include "mimetpla.h" /* | | |--- MimeInlineTextPlain */ +#include "mimethpl.h" /* | | | |--- M.I.TextHTMLAsPlaintext */ +#include "mimetpfl.h" /* | | |--- MimeInlineTextPlainFlowed */ +#include "mimethtm.h" /* | | |--- MimeInlineTextHTML */ +#include "mimethsa.h" /* | | | |--- M.I.TextHTMLSanitized */ +#include "mimeTextHTMLParsed.h" /*| | |--- M.I.TextHTMLParsed */ +#include "mimetric.h" /* | | |--- MimeInlineTextRichtext */ +#include "mimetenr.h" /* | | | |--- MimeInlineTextEnriched */ +/* SUPPORTED VIA PLUGIN | | |--- MimeInlineTextVCard */ +#include "mimeiimg.h" /* | |--- MimeInlineImage */ +#include "mimeeobj.h" /* | |--- MimeExternalObject */ +#include "mimeebod.h" /* |--- MimeExternalBody */ + /* If you add classes here,also add them to mimei.h */ +#include "prlog.h" +#include "prmem.h" +#include "prenv.h" +#include "plstr.h" +#include "prlink.h" +#include "prprf.h" +#include "mimecth.h" +#include "mimebuf.h" +#include "nsIServiceManager.h" +#include "mimemoz2.h" +#include "nsIMimeContentTypeHandler.h" +#include "nsIComponentManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsXPCOMCID.h" +#include "nsISimpleMimeConverter.h" +#include "nsSimpleMimeConverterStub.h" +#include "nsTArray.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include "nsMsgUtils.h" +#include "nsIPrefBranch.h" +#include "mozilla/Preferences.h" +#include "imgLoader.h" + +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgHdr.h" + +using namespace mozilla; + +// forward declaration +void getMsgHdrForCurrentURL(MimeDisplayOptions *opts, nsIMsgDBHdr ** aMsgHdr); + +#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part" +#define EXTERNAL_ATTACHMENT_URL_HEADER "X-Mozilla-External-Attachment-URL" + +/* ========================================================================== + Allocation and destruction + ========================================================================== + */ +static int mime_classinit(MimeObjectClass *clazz); + +/* + * These are the necessary defines/variables for doing + * content type handlers in external plugins. + */ +typedef struct { + char content_type[128]; + bool force_inline_display; +} cthandler_struct; + +nsTArray<cthandler_struct*> *ctHandlerList = NULL; + +/* + * This will return TRUE if the content_type is found in the + * list, FALSE if it is not found. + */ +bool +find_content_type_attribs(const char *content_type, + bool *force_inline_display) +{ + *force_inline_display = false; + if (!ctHandlerList) + return false; + + for (size_t i = 0; i < ctHandlerList->Length(); i++) + { + cthandler_struct *ptr = ctHandlerList->ElementAt(i); + if (PL_strcasecmp(content_type, ptr->content_type) == 0) + { + *force_inline_display = ptr->force_inline_display; + return true; + } + } + + return false; +} + +void +add_content_type_attribs(const char *content_type, + contentTypeHandlerInitStruct *ctHandlerInfo) +{ + cthandler_struct *ptr = NULL; + bool force_inline_display; + + if (find_content_type_attribs(content_type, &force_inline_display)) + return; + + if ( (!content_type) || (!ctHandlerInfo) ) + return; + + if (!ctHandlerList) + ctHandlerList = new nsTArray<cthandler_struct*>(); + + if (!ctHandlerList) + return; + + ptr = (cthandler_struct *) PR_MALLOC(sizeof(cthandler_struct)); + if (!ptr) + return; + + PL_strncpy(ptr->content_type, content_type, sizeof(ptr->content_type)); + ptr->force_inline_display = ctHandlerInfo->force_inline_display; + ctHandlerList->AppendElement(ptr); +} + +/* + * This routine will find all content type handler for a specifc content + * type (if it exists) + */ +bool +force_inline_display(const char *content_type) +{ + bool force_inline_disp; + + find_content_type_attribs(content_type, &force_inline_disp); + return (force_inline_disp); +} + +/* + * This routine will find all content type handler for a specifc content + * type (if it exists) and is defined to the nsRegistry + */ +MimeObjectClass * +mime_locate_external_content_handler(const char *content_type, + contentTypeHandlerInitStruct *ctHandlerInfo) +{ + if (!content_type || !*(content_type)) // null or empty content type + return nullptr; + + MimeObjectClass *newObj = NULL; + nsresult rv; + + nsAutoCString lookupID("@mozilla.org/mimecth;1?type="); + nsAutoCString contentType; + ToLowerCase(nsDependentCString(content_type), contentType); + lookupID += contentType; + + nsCOMPtr<nsIMimeContentTypeHandler> ctHandler = do_CreateInstance(lookupID.get(), &rv); + if (NS_FAILED(rv) || !ctHandler) { + nsCOMPtr<nsICategoryManager> catman = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return nullptr; + + nsCString value; + rv = catman->GetCategoryEntry(NS_SIMPLEMIMECONVERTERS_CATEGORY, + contentType.get(), getter_Copies(value)); + if (NS_FAILED(rv) || value.IsEmpty()) + return nullptr; + rv = MIME_NewSimpleMimeConverterStub(contentType.get(), + getter_AddRefs(ctHandler)); + if (NS_FAILED(rv) || !ctHandler) + return nullptr; + } + + rv = ctHandler->CreateContentTypeHandlerClass(contentType.get(), ctHandlerInfo, &newObj); + if (NS_FAILED(rv)) + return nullptr; + + add_content_type_attribs(contentType.get(), ctHandlerInfo); + return newObj; +} + +/* This is necessary to expose the MimeObject method outside of this DLL */ +int +MIME_MimeObject_write(MimeObject *obj, const char *output, int32_t length, bool user_visible_p) +{ + return MimeObject_write(obj, output, length, user_visible_p); +} + +MimeObject * +mime_new (MimeObjectClass *clazz, MimeHeaders *hdrs, + const char *override_content_type) +{ + int size = clazz->instance_size; + MimeObject *object; + int status; + + /* Some assertions to verify that this isn't random junk memory... */ + NS_ASSERTION(clazz->class_name && strlen(clazz->class_name) > 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + NS_ASSERTION(size > 0 && size < 1000, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (!clazz->class_initialized) + { + status = mime_classinit(clazz); + if (status < 0) return 0; + } + + NS_ASSERTION(clazz->initialize && clazz->finalize, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (hdrs) + { + hdrs = MimeHeaders_copy (hdrs); + if (!hdrs) return 0; + } + + object = (MimeObject *) PR_MALLOC(size); + if (!object) return 0; + + memset(object, 0, size); + object->clazz = clazz; + object->headers = hdrs; + object->dontShowAsAttachment = false; + + if (override_content_type && *override_content_type) + object->content_type = strdup(override_content_type); + + status = clazz->initialize(object); + if (status < 0) + { + clazz->finalize(object); + PR_Free(object); + return 0; + } + + return object; +} + +void +mime_free (MimeObject *object) +{ +# ifdef DEBUG__ + int i, size = object->clazz->instance_size; + uint32_t *array = (uint32_t*) object; +# endif /* DEBUG */ + + object->clazz->finalize(object); + +# ifdef DEBUG__ + for (i = 0; i < (size / sizeof(*array)); i++) + array[i] = (uint32_t) 0xDEADBEEF; +# endif /* DEBUG */ + + PR_Free(object); +} + + +bool mime_is_allowed_class(const MimeObjectClass *clazz, + int32_t types_of_classes_to_disallow) +{ + if (types_of_classes_to_disallow == 0) + return true; + bool avoid_html = (types_of_classes_to_disallow >= 1); + bool avoid_images = (types_of_classes_to_disallow >= 2); + bool avoid_strange_content = (types_of_classes_to_disallow >= 3); + bool allow_only_vanilla_classes = (types_of_classes_to_disallow == 100); + + if (allow_only_vanilla_classes) + /* A "safe" class is one that is unlikely to have security bugs or to + allow security exploits or one that is essential for the usefulness + of the application, even for paranoid users. + What's included here is more personal judgement than following + strict rules, though, unfortunately. + The function returns true only for known good classes, i.e. is a + "whitelist" in this case. + This idea comes from Georgi Guninski. + */ + return + ( + clazz == (MimeObjectClass *)&mimeInlineTextPlainClass || + clazz == (MimeObjectClass *)&mimeInlineTextPlainFlowedClass || + clazz == (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass || + clazz == (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass || + /* The latter 2 classes bear some risk, because they use the Gecko + HTML parser, but the user has the option to make an explicit + choice in this case, via html_as. */ + clazz == (MimeObjectClass *)&mimeMultipartMixedClass || + clazz == (MimeObjectClass *)&mimeMultipartAlternativeClass || + clazz == (MimeObjectClass *)&mimeMultipartDigestClass || + clazz == (MimeObjectClass *)&mimeMultipartAppleDoubleClass || + clazz == (MimeObjectClass *)&mimeMessageClass || + clazz == (MimeObjectClass *)&mimeExternalObjectClass || + /* mimeUntypedTextClass? -- does uuencode */ +#ifdef ENABLE_SMIME + clazz == (MimeObjectClass *)&mimeMultipartSignedCMSClass || + clazz == (MimeObjectClass *)&mimeEncryptedCMSClass || +#endif + clazz == 0 + ); + + /* Contrairy to above, the below code is a "blacklist", i.e. it + *excludes* some "bad" classes. */ + return + !( + (avoid_html + && ( + clazz == (MimeObjectClass *)&mimeInlineTextHTMLParsedClass + /* Should not happen - we protect against that in + mime_find_class(). Still for safety... */ + )) || + (avoid_images + && ( + clazz == (MimeObjectClass *)&mimeInlineImageClass + )) || + (avoid_strange_content + && ( + clazz == (MimeObjectClass *)&mimeInlineTextEnrichedClass || + clazz == (MimeObjectClass *)&mimeInlineTextRichtextClass || + clazz == (MimeObjectClass *)&mimeSunAttachmentClass || + clazz == (MimeObjectClass *)&mimeExternalBodyClass + )) + ); +} + +void getMsgHdrForCurrentURL(MimeDisplayOptions *opts, nsIMsgDBHdr ** aMsgHdr) +{ + *aMsgHdr = nullptr; + + if (!opts) + return; + + mime_stream_data *msd = (mime_stream_data *) (opts->stream_closure); + if (!msd) + return; + + nsCOMPtr<nsIChannel> channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgMessageUrl> msgURI; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + msgURI = do_QueryInterface(uri); + if (msgURI) + { + msgURI->GetMessageHeader(aMsgHdr); + if (*aMsgHdr) + return; + nsCString rdfURI; + msgURI->GetUri(getter_Copies(rdfURI)); + if (!rdfURI.IsEmpty()) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + GetMsgDBHdrFromURI(rdfURI.get(), getter_AddRefs(msgHdr)); + NS_IF_ADDREF(*aMsgHdr = msgHdr); + } + } + } + } + + return; +} + +MimeObjectClass * +mime_find_class (const char *content_type, MimeHeaders *hdrs, + MimeDisplayOptions *opts, bool exact_match_p) +{ + MimeObjectClass *clazz = 0; + MimeObjectClass *tempClass = 0; + contentTypeHandlerInitStruct ctHandlerInfo; + + // Read some prefs + nsIPrefBranch *prefBranch = GetPrefBranch(opts); + int32_t html_as = 0; // def. see below + int32_t types_of_classes_to_disallow = 0; /* Let only a few libmime classes + process incoming data. This protects from bugs (e.g. buffer overflows) + and from security loopholes (e.g. allowing unchecked HTML in some + obscure classes, although the user has html_as > 0). + This option is mainly for the UI of html_as. + 0 = allow all available classes + 1 = Use hardcoded blacklist to avoid rendering (incoming) HTML + 2 = ... and images + 3 = ... and some other uncommon content types + 4 = show all body parts + 100 = Use hardcoded whitelist to avoid even more bugs(buffer overflows). + This mode will limit the features available (e.g. uncommon + attachment types and inline images) and is for paranoid users. + */ + if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer && + opts->format_out != nsMimeOutput::nsMimeMessageDecrypt + && opts->format_out != nsMimeOutput::nsMimeMessageAttach) + if (prefBranch) + { + prefBranch->GetIntPref("mailnews.display.html_as", &html_as); + prefBranch->GetIntPref("mailnews.display.disallow_mime_handlers", + &types_of_classes_to_disallow); + if (types_of_classes_to_disallow > 0 && html_as == 0) + // We have non-sensical prefs. Do some fixup. + html_as = 1; + } + + // First, check to see if the message has been marked as JUNK. If it has, + // then force the message to be rendered as simple, unless this has been + // called by a filtering routine. + bool sanitizeJunkMail = false; + + // it is faster to read the pref first then figure out the msg hdr for the current url only if we have to + // XXX instead of reading this pref every time, part of mime should be an observer listening to this pref change + // and updating internal state accordingly. But none of the other prefs in this file seem to be doing that...=( + if (prefBranch) + prefBranch->GetBoolPref("mail.spam.display.sanitize", &sanitizeJunkMail); + + if (sanitizeJunkMail && + !(opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer)) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr)); + if (msgHdr) + { + nsCString junkScoreStr; + (void) msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + if (html_as == 0 && junkScoreStr.get() && atoi(junkScoreStr.get()) > 50) + html_as = 3; // 3 == Simple HTML + } // if msgHdr + } // if we are supposed to sanitize junk mail + + /* + * What we do first is check for an external content handler plugin. + * This will actually extend the mime handling by calling a routine + * which will allow us to load an external content type handler + * for specific content types. If one is not found, we will drop back + * to the default handler. + */ + if ((tempClass = mime_locate_external_content_handler(content_type, &ctHandlerInfo)) != NULL) + { +#ifdef MOZ_THUNDERBIRD + // This is a case where we only want to add this property if we are a thunderbird build AND + // we have found an external mime content handler for text/calendar + // This will enable iMIP support in Lightning + if ( hdrs && (!PL_strncasecmp(content_type, "text/calendar", 13))) + { + char *full_content_type = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (full_content_type) + { + char *imip_method = MimeHeaders_get_parameter(full_content_type, "method", NULL, NULL); + nsCOMPtr<nsIMsgDBHdr> msgHdr; + getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr)); + if (msgHdr) + msgHdr->SetStringProperty("imip_method", (imip_method) ? imip_method : "nomethod"); + // PR_Free checks for null + PR_Free(imip_method); + PR_Free(full_content_type); + } + } +#endif + + if (types_of_classes_to_disallow > 0 + && (!PL_strncasecmp(content_type, "text/x-vcard", 12)) + ) + /* Use a little hack to prevent some dangerous plugins, which ship + with Mozilla, to run. + For the truely user-installed plugins, we rely on the judgement + of the user. */ + { + if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; // As attachment + } + else + clazz = (MimeObjectClass *)tempClass; + } + else + { + if (!content_type || !*content_type || + !PL_strcasecmp(content_type, "text")) /* with no / in the type */ + clazz = (MimeObjectClass *)&mimeUntypedTextClass; + + /* Subtypes of text... + */ + else if (!PL_strncasecmp(content_type, "text/", 5)) + { + if (!PL_strcasecmp(content_type+5, "html")) + { + if (opts && + (opts->format_out == nsMimeOutput::nsMimeMessageSaveAs || + opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer || + opts->format_out == nsMimeOutput::nsMimeMessageDecrypt || + opts->format_out == nsMimeOutput::nsMimeMessageAttach)) + // SaveAs in new modes doesn't work yet. + { + // Don't use the parsed HTML class if we're ... + // - saving the HTML of a message + // - getting message content for filtering + // - snarfing attachments (nsMimeMessageDecrypt used in SnarfMsgAttachment) + // - processing attachments (like deleting attachments). + clazz = (MimeObjectClass *)&mimeInlineTextHTMLClass; + types_of_classes_to_disallow = 0; + } + else if (html_as == 0 || html_as == 4) // Render sender's HTML + clazz = (MimeObjectClass *)&mimeInlineTextHTMLParsedClass; + else if (html_as == 1) // convert HTML to plaintext + // Do a HTML->TXT->HTML conversion, see mimethpl.h. + clazz = (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass; + else if (html_as == 2) // display HTML source + /* This is for the freaks. Treat HTML as plaintext, + which will cause the HTML source to be displayed. + Not very user-friendly, but some seem to want this. */ + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + else if (html_as == 3) // Sanitize + // Strip all but allowed HTML + clazz = (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass; + else // Goofy pref + /* User has an unknown pref value. Maybe he used a newer Mozilla + with a new alternative to avoid HTML. Defaulting to option 1, + which is less dangerous than defaulting to the raw HTML. */ + clazz = (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass; + } + else if (!PL_strcasecmp(content_type+5, "enriched")) + clazz = (MimeObjectClass *)&mimeInlineTextEnrichedClass; + else if (!PL_strcasecmp(content_type+5, "richtext")) + clazz = (MimeObjectClass *)&mimeInlineTextRichtextClass; + else if (!PL_strcasecmp(content_type+5, "rtf")) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else if (!PL_strcasecmp(content_type+5, "plain")) + { + // Preliminary use the normal plain text + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + + if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer + && opts->format_out != nsMimeOutput::nsMimeMessageAttach + && opts->format_out != nsMimeOutput::nsMimeMessageRaw) + { + bool disable_format_flowed = false; + if (prefBranch) + prefBranch->GetBoolPref("mailnews.display.disable_format_flowed_support", + &disable_format_flowed); + + if(!disable_format_flowed) + { + // Check for format=flowed, damn, it is already stripped away from + // the contenttype! + // Look in headers instead even though it's expensive and clumsy + // First find Content-Type: + char *content_type_row = + (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : 0); + // Then the format parameter if there is one. + // I would rather use a PARAM_FORMAT but I can't find the right + // place to put the define. The others seems to be in net.h + // but is that really really the right place? There is also + // a nsMimeTypes.h but that one isn't included. Bug? + char *content_type_format = + (content_type_row + ? MimeHeaders_get_parameter(content_type_row, "format", NULL,NULL) + : 0); + + if (content_type_format && !PL_strcasecmp(content_type_format, + "flowed")) + clazz = (MimeObjectClass *)&mimeInlineTextPlainFlowedClass; + PR_FREEIF(content_type_format); + PR_FREEIF(content_type_row); + } + } + } + else if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + } + + /* Subtypes of multipart... + */ + else if (!PL_strncasecmp(content_type, "multipart/", 10)) + { + // When html_as is 4, we want all MIME parts of the message to + // show up in the displayed message body, if they are MIME types + // that we know how to display, and also in the attachment pane + // if it's appropriate to put them there. Both + // multipart/alternative and multipart/related play games with + // hiding various MIME parts, and we don't want that to happen, + // so we prevent that by parsing those MIME types as + // multipart/mixed, which won't mess with anything. + // + // When our output format is nsMimeOutput::nsMimeMessageAttach, + // i.e., we are reformatting the message to remove attachments, + // we are in a similar boat. The code for deleting + // attachments properly in that mode is in mimemult.cpp + // functions which are inherited by mimeMultipartMixedClass but + // not by mimeMultipartAlternativeClass or + // mimeMultipartRelatedClass. Therefore, to ensure that + // everything is handled properly, in this context too we parse + // those MIME types as multipart/mixed. + bool basic_formatting = (html_as == 4) || + (opts && opts->format_out == nsMimeOutput::nsMimeMessageAttach); + if (!PL_strcasecmp(content_type+10, "alternative")) + clazz = basic_formatting ? (MimeObjectClass *)&mimeMultipartMixedClass : + (MimeObjectClass *)&mimeMultipartAlternativeClass; + else if (!PL_strcasecmp(content_type+10, "related")) + clazz = basic_formatting ? (MimeObjectClass *)&mimeMultipartMixedClass : + (MimeObjectClass *)&mimeMultipartRelatedClass; + else if (!PL_strcasecmp(content_type+10, "digest")) + clazz = (MimeObjectClass *)&mimeMultipartDigestClass; + else if (!PL_strcasecmp(content_type+10, "appledouble") || + !PL_strcasecmp(content_type+10, "header-set")) + clazz = (MimeObjectClass *)&mimeMultipartAppleDoubleClass; + else if (!PL_strcasecmp(content_type+10, "parallel")) + clazz = (MimeObjectClass *)&mimeMultipartParallelClass; + else if (!PL_strcasecmp(content_type+10, "mixed")) + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; +#ifdef ENABLE_SMIME + else if (!PL_strcasecmp(content_type+10, "signed")) + { + /* Check that the "protocol" and "micalg" parameters are ones we + know about. */ + char *ct = (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : 0); + char *proto = (ct + ? MimeHeaders_get_parameter(ct, PARAM_PROTOCOL, NULL, NULL) + : 0); + char *micalg = (ct + ? MimeHeaders_get_parameter(ct, PARAM_MICALG, NULL, NULL) + : 0); + + if (proto + && ( + (/* is a signature */ + !PL_strcasecmp(proto, APPLICATION_XPKCS7_SIGNATURE) + || + !PL_strcasecmp(proto, APPLICATION_PKCS7_SIGNATURE)) + && micalg + && (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_4) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_5) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD2)))) + clazz = (MimeObjectClass *)&mimeMultipartSignedCMSClass; + else + clazz = 0; + PR_FREEIF(proto); + PR_FREEIF(micalg); + PR_FREEIF(ct); + } +#endif + + if (!clazz && !exact_match_p) + /* Treat all unknown multipart subtypes as "multipart/mixed" */ + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; + + /* If we are sniffing a message, let's treat alternative parts as mixed */ + if (opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer) + if (clazz == (MimeObjectClass *)&mimeMultipartAlternativeClass) + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; + } + + /* Subtypes of message... + */ + else if (!PL_strncasecmp(content_type, "message/", 8)) + { + if (!PL_strcasecmp(content_type+8, "rfc822") || + !PL_strcasecmp(content_type+8, "news")) + clazz = (MimeObjectClass *)&mimeMessageClass; + else if (!PL_strcasecmp(content_type+8, "external-body")) + clazz = (MimeObjectClass *)&mimeExternalBodyClass; + else if (!PL_strcasecmp(content_type+8, "partial")) + /* I guess these are most useful as externals, for now... */ + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else if (!exact_match_p) + /* Treat all unknown message subtypes as "text/plain" */ + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + } + + /* The magic image types which we are able to display internally... + */ + else if (!PL_strncasecmp(content_type, "image/", 6)) { + if (imgLoader::SupportImageWithMimeType(content_type, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) + clazz = (MimeObjectClass *)&mimeInlineImageClass; + else + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + +#ifdef ENABLE_SMIME + else if (!PL_strcasecmp(content_type, APPLICATION_XPKCS7_MIME) + || !PL_strcasecmp(content_type, APPLICATION_PKCS7_MIME)) { + + if (Preferences::GetBool("mailnews.p7m_subparts_external", false) && + opts->is_child) { + // We do not allow encrypted parts except as top level. + // Allowing them would leak the plain text in case the part is + // cleverly hidden and the decrypted content gets included in + // replies and forwards. + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + return clazz; + } + + char *ct = (hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : nullptr); + char *st = (ct ? MimeHeaders_get_parameter(ct, "smime-type", NULL, NULL) + : nullptr); + + /* by default, assume that it is an encrypted message */ + clazz = (MimeObjectClass *)&mimeEncryptedCMSClass; + + /* if the smime-type parameter says that it's a certs-only or + compressed file, then show it as an attachment, however + (MimeEncryptedCMS doesn't handle these correctly) */ + if (st && + (!PL_strcasecmp(st, "certs-only") || + !PL_strcasecmp(st, "compressed-data"))) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else { + /* look at the file extension... less reliable, but still covered + by the S/MIME specification (RFC 3851, section 3.2.1) */ + char *name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr); + if (name) { + char *suf = PL_strrchr(name, '.'); + bool p7mExternal = false; + + if (prefBranch) + prefBranch->GetBoolPref("mailnews.p7m_external", &p7mExternal); + if (suf && + ((!PL_strcasecmp(suf, ".p7m") && p7mExternal) || + !PL_strcasecmp(suf, ".p7c") || + !PL_strcasecmp(suf, ".p7z"))) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + PR_Free(name); + } + PR_Free(st); + PR_Free(ct); + } +#endif + /* A few types which occur in the real world and which we would otherwise + treat as non-text types (which would be bad) without this special-case... + */ + else if (!PL_strcasecmp(content_type, APPLICATION_PGP) || + !PL_strcasecmp(content_type, APPLICATION_PGP2)) + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + + else if (!PL_strcasecmp(content_type, SUN_ATTACHMENT)) + clazz = (MimeObjectClass *)&mimeSunAttachmentClass; + + /* Everything else gets represented as a clickable link. + */ + else if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + + if (!mime_is_allowed_class(clazz, types_of_classes_to_disallow)) + { + /* Do that check here (not after the if block), because we want to allow + user-installed plugins. */ + if(!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else + clazz = 0; + } + } + +#ifdef ENABLE_SMIME + // see bug #189988 + if (opts && opts->format_out == nsMimeOutput::nsMimeMessageDecrypt && + (clazz != (MimeObjectClass *)&mimeEncryptedCMSClass)) { + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } +#endif + + if (!exact_match_p) + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz) return 0; + + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (clazz && !clazz->class_initialized) + { + int status = mime_classinit(clazz); + if (status < 0) return 0; + } + + return clazz; +} + + +MimeObject * +mime_create (const char *content_type, MimeHeaders *hdrs, + MimeDisplayOptions *opts, bool forceInline /* = false */) +{ + /* If there is no Content-Disposition header, or if the Content-Disposition + is ``inline'', then we display the part inline (and let mime_find_class() + decide how.) + + If there is any other Content-Disposition (either ``attachment'' or some + disposition that we don't recognise) then we always display the part as + an external link, by using MimeExternalObject to display it. + + But Content-Disposition is ignored for all containers except `message'. + (including multipart/mixed, and multipart/digest.) It's not clear if + this is to spec, but from a usability standpoint, I think it's necessary. + */ + + MimeObjectClass *clazz = 0; + char *content_disposition = 0; + MimeObject *obj = 0; + char *override_content_type = 0; + + /* We've had issues where the incoming content_type is invalid, of a format: + content_type="=?windows-1252?q?application/pdf" (bug 659355) + We decided to fix that by simply trimming the stuff before the ? + */ + if (content_type) + { + const char *lastQuestion = strrchr(content_type, '?'); + if (lastQuestion) + content_type = lastQuestion + 1; // the substring after the last '?' + } + + /* There are some clients send out all attachments with a content-type + of application/octet-stream. So, if we have an octet-stream attachment, + try to guess what type it really is based on the file extension. I HATE + that we have to do this... + */ + if (hdrs && opts && opts->file_type_fn && + + /* ### mwelch - don't override AppleSingle */ + (content_type ? PL_strcasecmp(content_type, APPLICATION_APPLEFILE) : true) && + /* ## davidm Apple double shouldn't use this #$%& either. */ + (content_type ? PL_strcasecmp(content_type, MULTIPART_APPLEDOUBLE) : true) && + (!content_type || + !PL_strcasecmp(content_type, APPLICATION_OCTET_STREAM) || + !PL_strcasecmp(content_type, UNKNOWN_CONTENT_TYPE))) + { + char *name = MimeHeaders_get_name(hdrs, opts); + if (name) + { + override_content_type = opts->file_type_fn (name, opts->stream_closure); + // appledouble isn't a valid override content type, and makes + // attachments invisible. + if (!PL_strcasecmp(override_content_type, MULTIPART_APPLEDOUBLE)) + override_content_type = nullptr; + PR_FREEIF(name); + + // Workaroung for saving '.eml" file encoded with base64. + // Do not override with message/rfc822 whenever Transfer-Encoding is + // base64 since base64 encoding of message/rfc822 is invalid. + // Our MimeMessageClass has no capability to decode it. + if (!PL_strcasecmp(override_content_type, MESSAGE_RFC822)) { + nsCString encoding; + encoding.Adopt(MimeHeaders_get(hdrs, + HEADER_CONTENT_TRANSFER_ENCODING, + true, false)); + if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + override_content_type = nullptr; + } + + // If we get here and it is not the unknown content type from the + // file name, let's do some better checking not to inline something bad + if (override_content_type && + *override_content_type && + (PL_strcasecmp(override_content_type, UNKNOWN_CONTENT_TYPE))) + content_type = override_content_type; + } + } + + clazz = mime_find_class(content_type, hdrs, opts, false); + + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz) goto FAIL; + + if (opts && opts->part_to_load) + /* Always ignore Content-Disposition when we're loading some specific + sub-part (which may be within some container that we wouldn't otherwise + descend into, if the container itself had a Content-Disposition of + `attachment'. */ + content_disposition = 0; + + else if (mime_subclass_p(clazz,(MimeObjectClass *)&mimeContainerClass) && + !mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass)) + /* Ignore Content-Disposition on all containers except `message'. + That is, Content-Disposition is ignored for multipart/mixed objects, + but is obeyed for message/rfc822 objects. */ + content_disposition = 0; + + else + { + /* Check to see if the plugin should override the content disposition + to make it appear inline. One example is a vcard which has a content + disposition of an "attachment;" */ + if (force_inline_display(content_type)) + NS_MsgSACopy(&content_disposition, "inline"); + else + content_disposition = (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION, true, false) + : 0); + } + + if (!content_disposition || !PL_strcasecmp(content_disposition, "inline")) + ; /* Use the class we've got. */ + else + { + // override messages that have content disposition set to "attachment" + // even though we probably should show them inline. + if ( (clazz != (MimeObjectClass *)&mimeInlineTextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextPlainClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextPlainFlowedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLParsedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextRichtextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextEnrichedClass) && + (clazz != (MimeObjectClass *)&mimeMessageClass) && + (clazz != (MimeObjectClass *)&mimeInlineImageClass) ) + // not a special inline type, so show as attachment + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + + /* If the option `Show Attachments Inline' is off, now would be the time to change our mind... */ + /* Also, if we're doing a reply (i.e. quoting the body), then treat that according to preference. */ + if (opts && ((!opts->show_attachment_inline_p && !forceInline) || + (!opts->quote_attachment_inline_p && + (opts->format_out == nsMimeOutput::nsMimeMessageQuoting || + opts->format_out == nsMimeOutput::nsMimeMessageBodyQuoting)))) + { + if (mime_subclass_p(clazz, (MimeObjectClass *)&mimeInlineTextClass)) + { + /* It's a text type. Write it only if it's the *first* part + that we're writing, and then only if it has no "filename" + specified (the assumption here being, if it has a filename, + it wasn't simply typed into the text field -- it was actually + an attached document.) */ + if (opts->state && opts->state->first_part_written_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else + { + /* If there's a name, then write this as an attachment. */ + char *name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr); + if (name) + { + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + PR_Free(name); + } + } + } + else + if (mime_subclass_p(clazz,(MimeObjectClass *)&mimeContainerClass) && + !mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass)) + /* Multipart subtypes are ok, except for messages; descend into + multiparts, and defer judgement. + + Encrypted blobs are just like other containers (make the crypto + layer invisible, and treat them as simple containers. So there's + no easy way to save encrypted data directly to disk; it will tend + to always be wrapped inside a message/rfc822. That's ok.) */ + ; + else if (opts && opts->part_to_load && + mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass)) + /* Descend into messages only if we're looking for a specific sub-part. */ + ; + else + { + /* Anything else, and display it as a link (and cause subsequent + text parts to also be displayed as links.) */ + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + } + + PR_FREEIF(content_disposition); + obj = mime_new (clazz, hdrs, content_type); + + FAIL: + + /* If we decided to ignore the content-type in the headers of this object + (see above) then make sure that our new content-type is stored in the + object itself. (Or free it, if we're in an out-of-memory situation.) + */ + if (override_content_type) + { + if (obj) + { + PR_FREEIF(obj->content_type); + obj->content_type = override_content_type; + } + else + { + PR_Free(override_content_type); + } + } + + return obj; +} + + + +static int mime_classinit_1(MimeObjectClass *clazz, MimeObjectClass *target); + +static int +mime_classinit(MimeObjectClass *clazz) +{ + int status; + if (clazz->class_initialized) + return 0; + + NS_ASSERTION(clazz->class_initialize, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz->class_initialize) + return -1; + + /* First initialize the superclass. + */ + if (clazz->superclass && !clazz->superclass->class_initialized) + { + status = mime_classinit(clazz->superclass); + if (status < 0) return status; + } + + /* Now run each of the superclass-init procedures in turn, + parentmost-first. */ + status = mime_classinit_1(clazz, clazz); + if (status < 0) return status; + + /* Now we're done. */ + clazz->class_initialized = true; + return 0; +} + +static int +mime_classinit_1(MimeObjectClass *clazz, MimeObjectClass *target) +{ + int status; + if (clazz->superclass) + { + status = mime_classinit_1(clazz->superclass, target); + if (status < 0) return status; + } + return clazz->class_initialize(target); +} + + +bool +mime_subclass_p(MimeObjectClass *child, MimeObjectClass *parent) +{ + if (child == parent) + return true; + else if (!child->superclass) + return false; + else + return mime_subclass_p(child->superclass, parent); +} + +bool +mime_typep(MimeObject *obj, MimeObjectClass *clazz) +{ + return mime_subclass_p(obj->clazz, clazz); +} + + + +/* URL munging + */ + + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +char * +mime_part_address(MimeObject *obj) +{ + if (!obj->parent) + return strdup("0"); + else + { + /* Find this object in its parent. */ + int32_t i, j = -1; + char buf [20]; + char *higher = 0; + MimeContainer *cont = (MimeContainer *) obj->parent; + NS_ASSERTION(mime_typep(obj->parent, + (MimeObjectClass *)&mimeContainerClass), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + for (i = 0; i < cont->nchildren; i++) + if (cont->children[i] == obj) + { + j = i+1; + break; + } + if (j == -1) + { + NS_ERROR("No children under MeimContainer"); + return 0; + } + + PR_snprintf(buf, sizeof(buf), "%ld", j); + if (obj->parent->parent) + { + higher = mime_part_address(obj->parent); + if (!higher) return 0; /* MIME_OUT_OF_MEMORY */ + } + + if (!higher) + return strdup(buf); + else + { + uint32_t slen = strlen(higher) + strlen(buf) + 3; + char *s = (char *)PR_MALLOC(slen); + if (!s) + { + PR_Free(higher); + return 0; /* MIME_OUT_OF_MEMORY */ + } + PL_strncpyz(s, higher, slen); + PL_strcatn(s, slen, "."); + PL_strcatn(s, slen, buf); + PR_Free(higher); + return s; + } + } +} + + +/* Returns a string describing the location of the *IMAP* part (like "2.5.3"). + This is not a full URL, just a part-number. + This part is explicitly passed in the X-Mozilla-IMAP-Part header. + Return value must be freed by the caller. + */ +char * +mime_imap_part_address(MimeObject *obj) +{ + if (!obj || !obj->headers) + return 0; + else + return MimeHeaders_get(obj->headers, IMAP_EXTERNAL_CONTENT_HEADER, false, false); +} + +/* Returns a full URL if the current mime object has a EXTERNAL_ATTACHMENT_URL_HEADER + header. + Return value must be freed by the caller. +*/ +char * +mime_external_attachment_url(MimeObject *obj) +{ + if (!obj || !obj->headers) + return 0; + else + return MimeHeaders_get(obj->headers, EXTERNAL_ATTACHMENT_URL_HEADER, false, false); +} + +#ifdef ENABLE_SMIME +/* Asks whether the given object is one of the cryptographically signed + or encrypted objects that we know about. (MimeMessageClass uses this + to decide if the headers need to be presented differently.) + */ +bool +mime_crypto_object_p(MimeHeaders *hdrs, bool clearsigned_counts, MimeDisplayOptions *opts) +{ + char *ct; + MimeObjectClass *clazz; + + if (!hdrs) return false; + + ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, true, false); + if (!ct) return false; + + /* Rough cut -- look at the string before doing a more complex comparison. */ + if (PL_strcasecmp(ct, MULTIPART_SIGNED) && + PL_strncasecmp(ct, "application/", 12)) + { + PR_Free(ct); + return false; + } + + /* It's a candidate for being a crypto object. Let's find out for sure... */ + clazz = mime_find_class(ct, hdrs, opts, true); + PR_Free(ct); + + if (clazz == ((MimeObjectClass *)&mimeEncryptedCMSClass)) + return true; + else if (clearsigned_counts && + clazz == ((MimeObjectClass *)&mimeMultipartSignedCMSClass)) + return true; + else + return false; +} + +/* Whether the given object has written out the HTML version of its headers + in such a way that it will have a "crypto stamp" next to the headers. If + this is true, then the child must write out its HTML slightly differently + to take this into account... + */ +bool +mime_crypto_stamped_p(MimeObject *obj) +{ + if (!obj) return false; + if (mime_typep (obj, (MimeObjectClass *) &mimeMessageClass)) + return ((MimeMessage *) obj)->crypto_stamped_p; + else + return false; +} + +#endif // ENABLE_SMIME + +/* Puts a part-number into a URL. If append_p is true, then the part number + is appended to any existing part-number already in that URL; otherwise, + it replaces it. + */ +char * +mime_set_url_part(const char *url, const char *part, bool append_p) +{ + const char *part_begin = 0; + const char *part_end = 0; + bool got_q = false; + const char *s; + char *result; + + if (!url || !part) return 0; + + nsAutoCString urlString(url); + int32_t typeIndex = urlString.Find("?type=application/x-message-display"); + if (typeIndex != -1) + { + urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1); + if (urlString.CharAt(typeIndex) == '&') + urlString.Replace(typeIndex, 1, '?'); + url = urlString.get(); + } + + for (s = url; *s; s++) + { + if (*s == '?') + { + got_q = true; + if (!PL_strncasecmp(s, "?part=", 6)) + part_begin = (s += 6); + } + else if (got_q && *s == '&' && !PL_strncasecmp(s, "&part=", 6)) + part_begin = (s += 6); + + if (part_begin) + { + for (; (*s && *s != '?' && *s != '&'); s++) + ; + part_end = s; + break; + } + } + + uint32_t resultlen = strlen(url) + strlen(part) + 10; + result = (char *) PR_MALLOC(resultlen); + if (!result) return 0; + + if (part_begin) + { + if (append_p) + { + memcpy(result, url, part_end - url); + result [part_end - url] = '.'; + result [part_end - url + 1] = 0; + } + else + { + memcpy(result, url, part_begin - url); + result [part_begin - url] = 0; + } + } + else + { + PL_strncpyz(result, url, resultlen); + if (got_q) + PL_strcatn(result, resultlen, "&part="); + else + PL_strcatn(result, resultlen, "?part="); + } + + PL_strcatn(result, resultlen, part); + + if (part_end && *part_end) + PL_strcatn(result, resultlen, part_end); + + /* Semi-broken kludge to omit a trailing "?part=0". */ + { + int L = strlen(result); + if (L > 6 && + (result[L-7] == '?' || result[L-7] == '&') && + !strcmp("part=0", result + L - 6)) + result[L-7] = 0; + } + + return result; +} + + + +/* Puts an *IMAP* part-number into a URL. + Strips off any previous *IMAP* part numbers, since they are absolute, not relative. + */ +char * +mime_set_url_imap_part(const char *url, const char *imappart, const char *libmimepart) +{ + char *result = 0; + char *whereCurrent = PL_strstr(url, "/;section="); + if (whereCurrent) + { + *whereCurrent = 0; + } + + uint32_t resultLen = strlen(url) + strlen(imappart) + strlen(libmimepart) + 17; + result = (char *) PR_MALLOC(resultLen); + if (!result) return 0; + + PL_strncpyz(result, url, resultLen); + PL_strcatn(result, resultLen, "/;section="); + PL_strcatn(result, resultLen, imappart); + PL_strcatn(result, resultLen, "?part="); + PL_strcatn(result, resultLen, libmimepart); + + if (whereCurrent) + *whereCurrent = '/'; + + return result; +} + + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches, and returns the MimeObject (else NULL.) + (part is not a URL -- it's of the form "1.3.5".) + */ +MimeObject * +mime_address_to_part(const char *part, MimeObject *obj) +{ + /* Note: this is an N^2 operation, but the number of parts in a message + shouldn't ever be large enough that this really matters... */ + + bool match; + + if (!part || !*part) + { + match = !obj->parent; + } + else + { + char *part2 = mime_part_address(obj); + if (!part2) return 0; /* MIME_OUT_OF_MEMORY */ + match = !strcmp(part, part2); + PR_Free(part2); + } + + if (match) + { + /* These are the droids we're looking for. */ + return obj; + } + else if (!mime_typep(obj, (MimeObjectClass *) &mimeContainerClass)) + { + /* Not a container, pull up, pull up! */ + return 0; + } + else + { + int32_t i; + MimeContainer *cont = (MimeContainer *) obj; + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *o2 = mime_address_to_part(part, cont->children[i]); + if (o2) return o2; + } + return 0; + } +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +char * +mime_find_content_type_of_part(const char *part, MimeObject *obj) +{ + char *result = 0; + + obj = mime_address_to_part(part, obj); + if (!obj) return 0; + + result = (obj->headers ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, true, false) : 0); + + return result; +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +char * +mime_find_suggested_name_of_part(const char *part, MimeObject *obj) +{ + char *result = 0; + + obj = mime_address_to_part(part, obj); + if (!obj) return 0; + + result = (obj->headers ? MimeHeaders_get_name(obj->headers, obj->options) : 0); + + /* If this part doesn't have a name, but this part is one fork of an + AppleDouble, and the AppleDouble itself has a name, then use that. */ + if (!result && + obj->parent && + obj->parent->headers && + mime_typep(obj->parent, + (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + result = MimeHeaders_get_name(obj->parent->headers, obj->options); + + /* Else, if this part is itself an AppleDouble, and one of its children + has a name, then use that (check data fork first, then resource.) */ + if (!result && + mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + { + MimeContainer *cont = (MimeContainer *) obj; + if (cont->nchildren > 1 && + cont->children[1] && + cont->children[1]->headers) + result = MimeHeaders_get_name(cont->children[1]->headers, obj->options); + + if (!result && + cont->nchildren > 0 && + cont->children[0] && + cont->children[0]->headers) + result = MimeHeaders_get_name(cont->children[0]->headers, obj->options); + } + + /* Ok, now we have the suggested name, if any. + Now we remove any extensions that correspond to the + Content-Transfer-Encoding. For example, if we see the headers + + Content-Type: text/plain + Content-Disposition: inline; filename=foo.text.uue + Content-Transfer-Encoding: x-uuencode + + then we would look up (in mime.types) the file extensions which are + associated with the x-uuencode encoding, find that "uue" is one of + them, and remove that from the end of the file name, thus returning + "foo.text" as the name. This is because, by the time this file ends + up on disk, its content-transfer-encoding will have been removed; + therefore, we should suggest a file name that indicates that. + */ + if (result && obj->encoding && *obj->encoding) + { + int32_t L = strlen(result); + const char **exts = 0; + + /* + I'd like to ask the mime.types file, "what extensions correspond + to obj->encoding (which happens to be "x-uuencode") but doing that + in a non-sphagetti way would require brain surgery. So, since + currently uuencode is the only content-transfer-encoding which we + understand which traditionally has an extension, we just special- + case it here! Icepicks in my forehead! + + Note that it's special-cased in a similar way in libmsg/compose.c. + */ + if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE)) + { + static const char *uue_exts[] = { "uu", "uue", 0 }; + exts = uue_exts; + } + + while (exts && *exts) + { + const char *ext = *exts; + int32_t L2 = strlen(ext); + if (L > L2 + 1 && /* long enough */ + result[L - L2 - 1] == '.' && /* '.' in right place*/ + !PL_strcasecmp(ext, result + (L - L2))) /* ext matches */ + { + result[L - L2 - 1] = 0; /* truncate at '.' and stop. */ + break; + } + exts++; + } + } + + return result; +} + +/* Parse the various "?" options off the URL and into the options struct. + */ +int +mime_parse_url_options(const char *url, MimeDisplayOptions *options) +{ + const char *q; + + if (!url || !*url) return 0; + if (!options) return 0; + + MimeHeadersState default_headers = options->headers; + + q = PL_strrchr (url, '?'); + if (! q) return 0; + q++; + while (*q) + { + const char *end, *value, *name_end; + for (end = q; *end && *end != '&'; end++) + ; + for (value = q; *value != '=' && value < end; value++) + ; + name_end = value; + if (value < end) value++; + if (name_end <= q) + ; + else if (!PL_strncasecmp ("headers", q, name_end - q)) + { + if (end > value && !PL_strncasecmp ("only", value, end-value)) + options->headers = MimeHeadersOnly; + else if (end > value && !PL_strncasecmp ("none", value, end-value)) + options->headers = MimeHeadersNone; + else if (end > value && !PL_strncasecmp ("all", value, end - value)) + options->headers = MimeHeadersAll; + else if (end > value && !PL_strncasecmp ("some", value, end - value)) + options->headers = MimeHeadersSome; + else if (end > value && !PL_strncasecmp ("micro", value, end - value)) + options->headers = MimeHeadersMicro; + else if (end > value && !PL_strncasecmp ("cite", value, end - value)) + options->headers = MimeHeadersCitation; + else if (end > value && !PL_strncasecmp ("citation", value, end-value)) + options->headers = MimeHeadersCitation; + else + options->headers = default_headers; + } + else if (!PL_strncasecmp ("part", q, name_end - q) && + options->format_out != nsMimeOutput::nsMimeMessageBodyQuoting) + { + PR_FREEIF (options->part_to_load); + if (end > value) + { + options->part_to_load = (char *) PR_MALLOC(end - value + 1); + if (!options->part_to_load) + return MIME_OUT_OF_MEMORY; + memcpy(options->part_to_load, value, end-value); + options->part_to_load[end-value] = 0; + } + } + else if (!PL_strncasecmp ("rot13", q, name_end - q)) + { + options->rot13_p = end <= value || !PL_strncasecmp ("true", value, end - value); + } + else if (!PL_strncasecmp ("emitter", q, name_end - q)) + { + if ((end > value) && !PL_strncasecmp ("js", value, end - value)) + { + // the js emitter needs to hear about nested message bodies + // in order to build a proper representation. + options->notify_nested_bodies = true; + // show_attachment_inline_p has the side-effect of letting the + // emitter see all parts of a multipart/alternative, which it + // really appreciates. + options->show_attachment_inline_p = true; + // however, show_attachment_inline_p also results in a few + // subclasses writing junk into the body for display purposes. + // put a stop to these shenanigans by enabling write_pure_bodies. + // current offenders are: + // - MimeInlineImage + options->write_pure_bodies = true; + // we don't actually care about the data in the attachments, just the + // metadata (i.e. size) + options->metadata_only = true; + } + } + + q = end; + if (*q) + q++; + } + + +/* Compatibility with the "?part=" syntax used in the old (Mozilla 2.0) + MIME parser. + + Basically, the problem is that the old part-numbering code was totally + busted: here's a comparison of the old and new numberings with a pair + of hypothetical messages (one with a single part, and one with nested + containers.) + NEW: OLD: OR: + message/rfc822 + image/jpeg 1 0 0 + + message/rfc822 + multipart/mixed 1 0 0 + text/plain 1.1 1 1 + image/jpeg 1.2 2 2 + message/rfc822 1.3 - 3 + text/plain 1.3.1 3 - + message/rfc822 1.4 - 4 + multipart/mixed 1.4.1 4 - + text/plain 1.4.1.1 4.1 - + image/jpeg 1.4.1.2 4.2 - + text/plain 1.5 5 5 + + The "NEW" column is how the current code counts. The "OLD" column is + what "?part=" references would do in 3.0b4 and earlier; you'll see that + you couldn't directly refer to the child message/rfc822 objects at all! + But that's when it got really weird, because if you turned on + "Attachments As Links" (or used a URL like "?inline=false&part=...") + then you got a totally different numbering system (seen in the "OR" + column.) Gag! + + So, the problem is, ClariNet had been using these part numbers in their + HTML news feeds, as a sleazy way of transmitting both complex HTML layouts + and images using NNTP as transport, without invoking HTTP. + + The following clause is to provide some small amount of backward + compatibility. By looking at that table, one can see that in the new + model, "part=0" has no meaning, and neither does "part=2" or "part=3" + and so on. + + "part=1" is ambiguous between the old and new way, as is any part + specification that has a "." in it. + + So, the compatibility hack we do here is: if the part is "0", then map + that to "1". And if the part is >= "2", then prepend "1." to it (so that + we map "2" to "1.2", and "3" to "1.3".) + + This leaves the URLs compatible in the cases of: + + = single part messages + = references to elements of a top-level multipart except the first + + and leaves them incompatible for: + + = the first part of a top-level multipart + = all elements deeper than the outermost part + + Life s#$%s when you don't properly think out things that end up turning + into de-facto standards... + */ + + if (options->part_to_load && + !PL_strchr(options->part_to_load, '.')) /* doesn't contain a dot */ + { + if (!strcmp(options->part_to_load, "0")) /* 0 */ + { + PR_Free(options->part_to_load); + options->part_to_load = strdup("1"); + if (!options->part_to_load) + return MIME_OUT_OF_MEMORY; + } + else if (strcmp(options->part_to_load, "1")) /* not 1 */ + { + const char *prefix = "1."; + uint32_t slen = strlen(options->part_to_load) + strlen(prefix) + 1; + char *s = (char *) PR_MALLOC(slen); + if (!s) return MIME_OUT_OF_MEMORY; + PL_strncpyz(s, prefix, slen); + PL_strcatn(s, slen, options->part_to_load); + PR_Free(options->part_to_load); + options->part_to_load = s; + } + } + + return 0; +} + + +/* Some output-generation utility functions... + */ + +int +MimeOptions_write(MimeHeaders *hdrs, MimeDisplayOptions *opt, const char *data, + int32_t length, bool user_visible_p) +{ + int status = 0; + void* closure = 0; + if (!opt || !opt->output_fn || !opt->state) + return 0; + + closure = opt->output_closure; + if (!closure) closure = opt->stream_closure; + +// PR_ASSERT(opt->state->first_data_written_p); + + if (opt->state->separator_queued_p && user_visible_p) + { + opt->state->separator_queued_p = false; + if (opt->state->separator_suppressed_p) + opt->state->separator_suppressed_p = false; + else { + const char *sep = "<BR><FIELDSET CLASS=\"mimeAttachmentHeader\">"; + int lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString name; + name.Adopt(MimeHeaders_get_name(hdrs, opt)); + MimeHeaders_convert_header_value(opt, name, false); + + if (!name.IsEmpty()) { + sep = "<LEGEND CLASS=\"mimeAttachmentHeaderName\">"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString escapedName; + escapedName.Adopt(MsgEscapeHTML(name.get())); + + lstatus = opt->output_fn(escapedName.get(), + escapedName.Length(), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + sep = "</LEGEND>"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + } + + sep = "</FIELDSET><BR/>"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + } + } + if (user_visible_p) + opt->state->separator_suppressed_p = false; + + if (length > 0) + { + status = opt->output_fn(data, length, closure); + if (status < 0) return status; + } + + return 0; +} + +int +MimeObject_write(MimeObject *obj, const char *output, int32_t length, + bool user_visible_p) +{ + if (!obj->output_p) return 0; + + // if we're stripping attachments, check if any parent is not being ouput + if (obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) + { + // if true, mime generates a separator in html - we don't want that. + user_visible_p = false; + + for (MimeObject *parent = obj->parent; parent; parent = parent->parent) + { + if (!parent->output_p) + return 0; + } + } + if (!obj->options->state->first_data_written_p) + { + int status = MimeObject_output_init(obj, 0); + if (status < 0) return status; + NS_ASSERTION(obj->options->state->first_data_written_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + + return MimeOptions_write(obj->headers, obj->options, output, length, user_visible_p); +} + +int +MimeObject_write_separator(MimeObject *obj) +{ + if (obj->options && obj->options->state && + // we never want separators if we are asking for pure bodies + !obj->options->write_pure_bodies) + obj->options->state->separator_queued_p = true; + return 0; +} + +int +MimeObject_output_init(MimeObject *obj, const char *content_type) +{ + if (obj && + obj->options && + obj->options->state && + !obj->options->state->first_data_written_p) + { + int status; + const char *charset = 0; + char *name = 0, *x_mac_type = 0, *x_mac_creator = 0; + + if (!obj->options->output_init_fn) + { + obj->options->state->first_data_written_p = true; + return 0; + } + + if (obj->headers) + { + char *ct; + name = MimeHeaders_get_name(obj->headers, obj->options); + + ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, + false, false); + if (ct) + { + x_mac_type = MimeHeaders_get_parameter(ct, PARAM_X_MAC_TYPE, NULL, NULL); + x_mac_creator= MimeHeaders_get_parameter(ct, PARAM_X_MAC_CREATOR, NULL, NULL); + /* if don't have a x_mac_type and x_mac_creator, we need to try to get it from its parent */ + if (!x_mac_type && !x_mac_creator && obj->parent && obj->parent->headers) + { + char * ctp = MimeHeaders_get(obj->parent->headers, HEADER_CONTENT_TYPE, false, false); + if (ctp) + { + x_mac_type = MimeHeaders_get_parameter(ctp, PARAM_X_MAC_TYPE, NULL, NULL); + x_mac_creator= MimeHeaders_get_parameter(ctp, PARAM_X_MAC_CREATOR, NULL, NULL); + PR_Free(ctp); + } + } + + if (!(obj->options->override_charset)) { + char *charset = MimeHeaders_get_parameter(ct, "charset", nullptr, nullptr); + if (charset) + { + PR_FREEIF(obj->options->default_charset); + obj->options->default_charset = charset; + } + } + PR_Free(ct); + } + } + + if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextClass)) + charset = ((MimeInlineText *)obj)->charset; + + if (!content_type) + content_type = obj->content_type; + if (!content_type) + content_type = TEXT_PLAIN; + + // + // Set the charset on the channel we are dealing with so people know + // what the charset is set to. Do this for quoting/Printing ONLY! + // + extern void ResetChannelCharset(MimeObject *obj); + if ( (obj->options) && + ( (obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) || + (obj->options->format_out == nsMimeOutput::nsMimeMessagePrintOutput) ) ) + ResetChannelCharset(obj); + + status = obj->options->output_init_fn (content_type, charset, name, + x_mac_type, x_mac_creator, + obj->options->stream_closure); + PR_FREEIF(name); + PR_FREEIF(x_mac_type); + PR_FREEIF(x_mac_creator); + obj->options->state->first_data_written_p = true; + return status; + } + return 0; +} + +char * +mime_get_base_url(const char *url) +{ + if (!url) + return nullptr; + + const char *s = strrchr(url, '?'); + if (s && !strncmp(s, "?type=application/x-message-display", sizeof("?type=application/x-message-display") - 1)) + { + const char *nextTerm = strchr(s, '&'); + s = (nextTerm) ? nextTerm : s + strlen(s) - 1; + } + // we need to keep the ?number part of the url, or we won't know + // which local message the part belongs to. + if (s && *s && *(s+1) && !strncmp(s + 1, "number=", sizeof("number=") - 1)) + { + const char *nextTerm = strchr(++s, '&'); + s = (nextTerm) ? nextTerm : s + strlen(s) - 1; + } + char *result = (char *) PR_MALLOC(strlen(url) + 1); + NS_ASSERTION(result, "out of memory"); + if (!result) + return nullptr; + + memcpy(result, url, s - url); + result[s - url] = 0; + return result; +} |