summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/mime/src/mimei.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/mime/src/mimei.cpp
parentInitial commit. (diff)
downloadthunderbird-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.cpp1716
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;
+}