diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/mime/src/mimei.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/mime/src/mimei.cpp')
-rw-r--r-- | comm/mailnews/mime/src/mimei.cpp | 1716 |
1 files changed, 1716 insertions, 0 deletions
diff --git a/comm/mailnews/mime/src/mimei.cpp b/comm/mailnews/mime/src/mimei.cpp new file mode 100644 index 0000000000..3001189f8e --- /dev/null +++ b/comm/mailnews/mime/src/mimei.cpp @@ -0,0 +1,1716 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * 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. + */ + +// clang-format off +#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 */ +// clang-format on + +#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 "mimemoz2.h" +#include "nsIMimeContentTypeHandler.h" +#include "nsICategoryManager.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" +#include "nsIMailChannel.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 = nullptr; + +/* + * 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 = nullptr; + 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 specific 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 specific 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 = nullptr; + 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, + 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(rdfURI); + if (!rdfURI.IsEmpty()) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + GetMsgDBHdrFromURI(rdfURI, 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", 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)) != nullptr) { +#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", nullptr, nullptr); + + mime_stream_data* msd = (mime_stream_data*)(opts->stream_closure); + nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(msd->channel); + if (mailChannel) { + mailChannel->SetImipMethod( + nsCString(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/vcard", 10) || + !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 truly 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", + nullptr, nullptr) + : 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, nullptr, nullptr) + : 0; + char* micalg = + ct ? MimeHeaders_get_parameter(ct, PARAM_MICALG, nullptr, nullptr) + : 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( + nsDependentCString(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 (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*)&mimeSuppressedCryptoClass; + } else { + char* ct = + hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false) + : nullptr; + char* st = + ct ? MimeHeaders_get_parameter(ct, "smime-type", nullptr, nullptr) + : 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); + + // Workaround 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*)&mimeMessageClass) && + (clazz != (MimeObjectClass*)&mimeInlineImageClass) && + (!opts->show_attachment_inline_text || + ((clazz != (MimeObjectClass*)&mimeInlineTextHTMLClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextHTMLParsedClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextHTMLSanitizedClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextHTMLAsPlaintextClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextRichtextClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextEnrichedClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextPlainClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextPlainFlowedClass)))) { + // not a special inline type, so show as attachment + // However, mimeSuppressedCryptoClass is treated identically as + // mimeExternalObjectClass, let's not lose that type information. + if (clazz != (MimeObjectClass*)&mimeSuppressedCryptoClass) { + 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; + if (!child->superclass) return false; + 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"); + + /* 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); + + 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; + 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; + 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; + + if (clearsigned_counts && + clazz == ((MimeObjectClass*)&mimeMultipartSignedCMSClass)) + return true; + + 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) { + while (*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; + end = q; + while (*end && *end != '&') end++; + value = q; + while (*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=\"moz-mime-attachment-header\">"; + 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=\"moz-mime-attachment-header-name\">"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString escapedName; + nsAppendEscapedHTML(name, escapedName); + + 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>"; + 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 output + if (obj->options && + 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 && !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, nullptr, nullptr); + x_mac_creator = MimeHeaders_get_parameter(ct, PARAM_X_MAC_CREATOR, + nullptr, nullptr); + /* 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, + nullptr, nullptr); + x_mac_creator = MimeHeaders_get_parameter(ctp, PARAM_X_MAC_CREATOR, + nullptr, nullptr); + 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, '&'); + // strlen(s) cannot be zero, because it matches the above text + 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; +} |