diff options
Diffstat (limited to 'mailnews/mime/src')
98 files changed, 28741 insertions, 0 deletions
diff --git a/mailnews/mime/src/MimeHeaderParser.cpp b/mailnews/mime/src/MimeHeaderParser.cpp new file mode 100644 index 000000000..15341de46 --- /dev/null +++ b/mailnews/mime/src/MimeHeaderParser.cpp @@ -0,0 +1,248 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/mailnews/Services.h" +#include "mozilla/DebugOnly.h" +#include "nsMemory.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIMsgHeaderParser.h" + +namespace mozilla { +namespace mailnews { + +void detail::DoConversion(const nsTArray<nsString> &aUTF16Array, + nsTArray<nsCString> &aUTF8Array) +{ + uint32_t count = aUTF16Array.Length(); + aUTF8Array.SetLength(count); + for (uint32_t i = 0; i < count; ++i) + CopyUTF16toUTF8(aUTF16Array[i], aUTF8Array[i]); +} + +void MakeMimeAddress(const nsACString &aName, const nsACString &aEmail, + nsACString &full) +{ + nsAutoString utf16Address; + MakeMimeAddress(NS_ConvertUTF8toUTF16(aName), NS_ConvertUTF8toUTF16(aEmail), + utf16Address); + + CopyUTF16toUTF8(utf16Address, full); +} + +void MakeMimeAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full) +{ + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + + nsCOMPtr<msgIAddressObject> address; + headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(address)); + msgIAddressObject *obj = address; + headerParser->MakeMimeHeader(&obj, 1, full); +} + +void MakeDisplayAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full) +{ + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + + nsCOMPtr<msgIAddressObject> object; + headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(object)); + object->ToString(full); +} + +void RemoveDuplicateAddresses(const nsACString &aHeader, + const nsACString &aOtherEmails, + nsACString &result) +{ + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + + headerParser->RemoveDuplicateAddresses(aHeader, aOtherEmails, result); +} + +///////////////////////////////////////////// +// These are the core shim methods we need // +///////////////////////////////////////////// + +nsCOMArray<msgIAddressObject> DecodedHeader(const nsAString &aHeader) +{ + nsCOMArray<msgIAddressObject> retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + NS_ENSURE_TRUE(headerParser, retval); + msgIAddressObject **addresses = nullptr; + uint32_t length; + nsresult rv = headerParser->ParseDecodedHeader(aHeader, false, + &length, &addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Javascript jsmime returned an error!"); + if (NS_SUCCEEDED(rv) && length > 0 && addresses) { + retval.Adopt(addresses, length); + } + return retval; +} + +nsCOMArray<msgIAddressObject> EncodedHeader(const nsACString &aHeader, + const char *aCharset) +{ + nsCOMArray<msgIAddressObject> retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + NS_ENSURE_TRUE(headerParser, retval); + msgIAddressObject **addresses = nullptr; + uint32_t length; + nsresult rv = headerParser->ParseEncodedHeader(aHeader, aCharset, + false, &length, &addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!"); + if (NS_SUCCEEDED(rv) && length > 0 && addresses) { + retval.Adopt(addresses, length); + } + return retval; +} + +nsCOMArray<msgIAddressObject> EncodedHeaderW(const nsAString &aHeader) +{ + nsCOMArray<msgIAddressObject> retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + NS_ENSURE_TRUE(headerParser, retval); + msgIAddressObject **addresses = nullptr; + uint32_t length; + nsresult rv = headerParser->ParseEncodedHeaderW(aHeader, &length, &addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!"); + if (NS_SUCCEEDED(rv) && length > 0 && addresses) { + retval.Adopt(addresses, length); + } + return retval; +} + +void ExtractAllAddresses(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &names, nsTArray<nsString> &emails) +{ + uint32_t count = aHeader.Length(); + + // Prefill arrays before we start + names.SetLength(count); + emails.SetLength(count); + + for (uint32_t i = 0; i < count; i++) + { + aHeader[i]->GetName(names[i]); + aHeader[i]->GetEmail(emails[i]); + } + + if (count == 1 && names[0].IsEmpty() && emails[0].IsEmpty()) + { + names.Clear(); + emails.Clear(); + } +} + +void ExtractDisplayAddresses(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &displayAddrs) +{ + uint32_t count = aHeader.Length(); + + displayAddrs.SetLength(count); + for (uint32_t i = 0; i < count; i++) + aHeader[i]->ToString(displayAddrs[i]); + + if (count == 1 && displayAddrs[0].IsEmpty()) + displayAddrs.Clear(); +} + +///////////////////////////////////////////////// +// All of these are based on the above methods // +///////////////////////////////////////////////// + +void ExtractEmails(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &emails) +{ + nsTArray<nsString> names; + ExtractAllAddresses(aHeader, names, emails); +} + +void ExtractEmail(const nsCOMArray<msgIAddressObject> &aHeader, + nsACString &email) +{ + AutoTArray<nsString, 1> names; + AutoTArray<nsString, 1> emails; + ExtractAllAddresses(aHeader, names, emails); + + if (emails.Length() > 0) + CopyUTF16toUTF8(emails[0], email); + else + email.Truncate(); +} + +void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader, + nsACString &name, nsACString &email) +{ + AutoTArray<nsString, 1> names, emails; + ExtractAllAddresses(aHeader, names, emails); + if (names.Length() > 0) + { + CopyUTF16toUTF8(names[0], name); + CopyUTF16toUTF8(emails[0], email); + } + else + { + name.Truncate(); + email.Truncate(); + } +} + +void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader, + nsAString &name, nsACString &email) +{ + AutoTArray<nsString, 1> names, emails; + ExtractAllAddresses(aHeader, names, emails); + if (names.Length() > 0) + { + name = names[0]; + CopyUTF16toUTF8(emails[0], email); + } + else + { + name.Truncate(); + email.Truncate(); + } +} + +void ExtractName(const nsCOMArray<msgIAddressObject> &aHeader, nsACString &name) +{ + nsCString email; + ExtractFirstAddress(aHeader, name, email); + if (name.IsEmpty()) + name = email; +} + +void ExtractName(const nsCOMArray<msgIAddressObject> &aHeader, nsAString &name) +{ + AutoTArray<nsString, 1> names; + AutoTArray<nsString, 1> emails; + ExtractAllAddresses(aHeader, names, emails); + if (names.Length() > 0) + { + if (names[0].IsEmpty()) + name = emails[0]; + else + name = names[0]; + } + else + { + name.Truncate(); + } +} + +} // namespace mailnews +} // namespace mozilla diff --git a/mailnews/mime/src/comi18n.cpp b/mailnews/mime/src/comi18n.cpp new file mode 100644 index 000000000..7425e32ff --- /dev/null +++ b/mailnews/mime/src/comi18n.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "comi18n.h" +#include "nsIStringCharsetDetector.h" +#include "nsMsgUtils.h" +#include "nsICharsetConverterManager.h" +#include "nsIMIMEHeaderParam.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgMimeCID.h" +#include "nsIMimeConverter.h" + + +//////////////////////////////////////////////////////////////////////////////// +// BEGIN PUBLIC INTERFACE +extern "C" { + + +void MIME_DecodeMimeHeader(const char *header, const char *default_charset, + bool override_charset, bool eatContinuations, + nsACString &result) +{ + nsresult rv; + nsCOMPtr <nsIMimeConverter> mimeConverter = + do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + result.Truncate(); + return; + } + mimeConverter->DecodeMimeHeaderToUTF8(nsDependentCString(header), + default_charset, override_charset, + eatContinuations, result); +} + +// UTF-8 utility functions. +//detect charset soly based on aBuf. return in aCharset +nsresult +MIME_detect_charset(const char *aBuf, int32_t aLength, const char** aCharset) +{ + nsresult res = NS_ERROR_UNEXPECTED; + nsString detector_name; + *aCharset = nullptr; + + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "intl.charset.detector", EmptyString(), detector_name); + + if (!detector_name.IsEmpty()) { + nsAutoCString detector_contractid; + detector_contractid.AssignLiteral(NS_STRCDETECTOR_CONTRACTID_BASE); + detector_contractid.Append(NS_ConvertUTF16toUTF8(detector_name)); + nsCOMPtr<nsIStringCharsetDetector> detector = do_CreateInstance(detector_contractid.get(), &res); + if (NS_SUCCEEDED(res)) { + nsDetectionConfident oConfident; + res = detector->DoIt(aBuf, aLength, aCharset, oConfident); + if (NS_SUCCEEDED(res) && (eBestAnswer == oConfident || eSureAnswer == oConfident)) { + return NS_OK; + } + } + } + return res; +} + +//Get unicode decoder(from inputcharset to unicode) for aInputCharset +nsresult +MIME_get_unicode_decoder(const char* aInputCharset, nsIUnicodeDecoder **aDecoder) +{ + nsresult res; + + // get charset converters. + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res); + if (NS_SUCCEEDED(res)) { + + // create a decoder (conv to unicode), ok if failed if we do auto detection + if (!*aInputCharset || !PL_strcasecmp("us-ascii", aInputCharset)) + res = ccm->GetUnicodeDecoderRaw("ISO-8859-1", aDecoder); + else + // GetUnicodeDecoderInternal in order to support UTF-7 messages + // + // XXX this means that even HTML messages in UTF-7 will be decoded + res = ccm->GetUnicodeDecoderInternal(aInputCharset, aDecoder); + } + + return res; +} + +//Get unicode encoder(from unicode to inputcharset) for aOutputCharset +nsresult +MIME_get_unicode_encoder(const char* aOutputCharset, nsIUnicodeEncoder **aEncoder) +{ + nsresult res; + + // get charset converters. + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res); + if (NS_SUCCEEDED(res) && *aOutputCharset) { + // create a encoder (conv from unicode) + res = ccm->GetUnicodeEncoder(aOutputCharset, aEncoder); + } + + return res; +} + +} /* end of extern "C" */ +// END PUBLIC INTERFACE + diff --git a/mailnews/mime/src/comi18n.h b/mailnews/mime/src/comi18n.h new file mode 100644 index 000000000..6be89cfc5 --- /dev/null +++ b/mailnews/mime/src/comi18n.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _COMI18N_LOADED_H_ +#define _COMI18N_LOADED_H_ + +#include "msgCore.h" + +class nsIUnicodeDecoder; +class nsIUnicodeEncoder; + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Decode MIME header to UTF-8. + * Uses MIME_ConvertCharset if the decoded string needs a conversion. + * + * + * @param header [IN] A header to decode. + * @param default_charset [IN] Default charset to apply to ulabeled non-UTF-8 8bit data + * @param override_charset [IN] If true, default_charset used instead of any charset labeling other than UTF-8 + * @param eatContinuations [IN] If true, unfold headers + * @param result [OUT] Decoded buffer + */ +void MIME_DecodeMimeHeader(const char *header, const char *default_charset, + bool override_charset, bool eatContinuations, + nsACString &result); + +nsresult MIME_detect_charset(const char *aBuf, int32_t aLength, const char** aCharset); +nsresult MIME_get_unicode_decoder(const char* aInputCharset, nsIUnicodeDecoder **aDecoder); +nsresult MIME_get_unicode_encoder(const char* aOutputCharset, nsIUnicodeEncoder **aEncoder); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif // _COMI18N_LOADED_H_ + diff --git a/mailnews/mime/src/extraMimeParsers.jsm b/mailnews/mime/src/extraMimeParsers.jsm new file mode 100644 index 000000000..101eddc20 --- /dev/null +++ b/mailnews/mime/src/extraMimeParsers.jsm @@ -0,0 +1,29 @@ +/* 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/. */ + +function parseNewsgroups(headers) { + let ng = []; + for (let header of headers) { + ng = ng.concat(header.split(/\s*,\s*/)); + } + return ng; +} + +function emitNewsgroups(groups) { + // Don't encode the newsgroups names in RFC 2047... + if (groups.length == 1) + this.addText(groups[0], false); + else { + this.addText(groups[0], false); + for (let i = 1; i < groups.length; i++) { + this.addText(",", false); // only comma, no space! + this.addText(groups[i], false); + } + } +} + +jsmime.headerparser.addStructuredDecoder("Newsgroups", parseNewsgroups); +jsmime.headerparser.addStructuredDecoder("Followup-To", parseNewsgroups); +jsmime.headeremitter.addStructuredEncoder("Newsgroups", emitNewsgroups); +jsmime.headeremitter.addStructuredEncoder("Followup-To", emitNewsgroups); diff --git a/mailnews/mime/src/jsmime.jsm b/mailnews/mime/src/jsmime.jsm new file mode 100644 index 000000000..70728fe9c --- /dev/null +++ b/mailnews/mime/src/jsmime.jsm @@ -0,0 +1,90 @@ +/* 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/. */ +// vim:set ts=2 sw=2 sts=2 et ft=javascript: + +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * This file exports the JSMime code, polyfilling code as appropriate for use in + * Gecko. + */ + +// Load the core MIME parser. Since it doesn't define EXPORTED_SYMBOLS, we must +// use the subscript loader instead. +Services.scriptloader.loadSubScript("resource:///modules/jsmime/jsmime.js"); + +var EXPORTED_SYMBOLS = ["jsmime"]; + + +// A polyfill to support non-encoding-spec charsets. Since the only converter +// available to us from JavaScript has a very, very weak and inflexible API, we +// choose to rely on the regular text decoder unless absolutely necessary. +// support non-encoding-spec charsets. +function FakeTextDecoder(label="UTF-8", options = {}) { + this._reset(label); + // So nsIScriptableUnicodeConverter only gives us fatal=false, unless we are + // using UTF-8, where we only get fatal=true. The internals of said class tell + // us to use a C++-only class if we need better behavior. +} +FakeTextDecoder.prototype = { + _reset: function (label) { + this._encoder = Components.classes[ + "@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); + this._encoder.isInternal = true; + let manager = Components.classes[ + "@mozilla.org/charset-converter-manager;1"] + .createInstance(Components.interfaces.nsICharsetConverterManager); + this._encoder.charset = manager.getCharsetAlias(label); + }, + get encoding() { return this._encoder.charset; }, + decode: function (input, options = {}) { + let more = 'stream' in options ? options.stream : false; + let result = ""; + if (input !== undefined) { + let data = new Uint8Array(input); + result = this._encoder.convertFromByteArray(data, data.length); + } + // This isn't quite right--it won't handle errors if there are a few + // remaining bytes in the buffer, but it's the best we can do. + if (!more) + this._reset(this.encoding); + return result; + }, +}; + +var RealTextDecoder = TextDecoder; +function FallbackTextDecoder(charset, options) { + try { + return new RealTextDecoder(charset, options); + } catch (e) { + return new FakeTextDecoder(charset, options); + } +} + +TextDecoder = FallbackTextDecoder; + + +// The following code loads custom MIME encoders. +var CATEGORY_NAME = "custom-mime-encoder"; +Services.obs.addObserver(function (subject, topic, data) { + subject = subject.QueryInterface(Components.interfaces.nsISupportsCString) + .data; + if (data == CATEGORY_NAME) { + let url = catman.getCategoryEntry(CATEGORY_NAME, subject); + Services.scriptloader.loadSubScript(url, {}, "UTF-8"); + } +}, "xpcom-category-entry-added", false); + +var catman = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + +var entries = catman.enumerateCategory(CATEGORY_NAME); +while (entries.hasMoreElements()) { + let string = entries.getNext() + .QueryInterface(Components.interfaces.nsISupportsCString) + .data; + let url = catman.getCategoryEntry(CATEGORY_NAME, string); + Services.scriptloader.loadSubScript(url, {}, "UTF-8"); +} diff --git a/mailnews/mime/src/mime.def b/mailnews/mime/src/mime.def new file mode 100644 index 000000000..6b1c36bf9 --- /dev/null +++ b/mailnews/mime/src/mime.def @@ -0,0 +1,7 @@ +; 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/. + +LIBRARY mime.dll + +EXPORTS diff --git a/mailnews/mime/src/mimeJSComponents.js b/mailnews/mime/src/mimeJSComponents.js new file mode 100644 index 000000000..8e9d0684a --- /dev/null +++ b/mailnews/mime/src/mimeJSComponents.js @@ -0,0 +1,515 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/Deprecated.jsm"); +Components.utils.import("resource:///modules/jsmime.jsm"); +Components.utils.import("resource:///modules/mimeParser.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function HeaderHandler() { + this.value = ""; + this.deliverData = function (str) { this.value += str; }; + this.deliverEOF = function () {}; +} + +function StringEnumerator(iterator) { + this._iterator = iterator; + this._next = undefined; +} +StringEnumerator.prototype = { + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.nsIUTF8StringEnumerator]), + _setNext: function () { + if (this._next !== undefined) + return; + this._next = this._iterator.next(); + }, + hasMore: function () { + this._setNext(); + return !this._next.done; + }, + getNext: function () { + this._setNext(); + let result = this._next; + this._next = undefined; + if (result.done) + throw Components.results.NS_ERROR_UNEXPECTED; + return result.value; + } +}; + +/** + * If we get XPConnect-wrapped objects for msgIAddressObjects, we will have + * properties defined for 'group' that throws off jsmime. This function converts + * the addresses into the form that jsmime expects. + */ +function fixXpconnectAddresses(addrs) { + return addrs.map((addr) => { + // This is ideally !addr.group, but that causes a JS strict warning, if + // group is not in addr, since that's enabled in all chrome code now. + if (!('group' in addr) || addr.group === undefined || addr.group === null) { + return MimeAddressParser.prototype.makeMailboxObject(addr.name, + addr.email); + } else { + return MimeAddressParser.prototype.makeGroupObject(addr.name, + fixXpconnectAddresses(addr.group)); + } + }); +} + +/** + * This is a base handler for supporting msgIStructuredHeaders, since we have + * two implementations that need the readable aspects of the interface. + */ +function MimeStructuredHeaders() { +} +MimeStructuredHeaders.prototype = { + getHeader: function (aHeaderName) { + let name = aHeaderName.toLowerCase(); + return this._headers.get(name); + }, + + hasHeader: function (aHeaderName) { + return this._headers.has(aHeaderName.toLowerCase()); + }, + + getUnstructuredHeader: function (aHeaderName) { + let result = this.getHeader(aHeaderName); + if (result === undefined || typeof result == "string") + return result; + throw Components.results.NS_ERROR_ILLEGAL_VALUE; + }, + + getAddressingHeader: function (aHeaderName, aPreserveGroups, count) { + let addrs = this.getHeader(aHeaderName); + if (addrs === undefined) { + addrs = []; + } else if (!Array.isArray(addrs)) { + throw Components.results.NS_ERROR_ILLEGAL_VALUE; + } + return fixArray(addrs, aPreserveGroups, count); + }, + + getRawHeader: function (aHeaderName) { + let result = this.getHeader(aHeaderName); + if (result === undefined) + return result; + + let value = jsmime.headeremitter.emitStructuredHeader(aHeaderName, + result, {}); + // Strip off the header name and trailing whitespace before returning... + value = value.substring(aHeaderName.length + 2).trim(); + // ... as well as embedded newlines. + value = value.replace(/\r\n/g, ''); + return value; + }, + + get headerNames() { + return new StringEnumerator(this._headers.keys()); + }, + + buildMimeText: function () { + if (this._headers.size == 0) { + return ""; + } + let handler = new HeaderHandler(); + let emitter = jsmime.headeremitter.makeStreamingEmitter(handler, { + useASCII: true + }); + for (let [value, header] of this._headers) { + emitter.addStructuredHeader(value, header); + } + emitter.finish(); + return handler.value; + }, +}; + + +function MimeHeaders() { +} +MimeHeaders.prototype = { + __proto__: MimeStructuredHeaders.prototype, + classDescription: "Mime headers implementation", + classID: Components.ID("d1258011-f391-44fd-992e-c6f4b461a42f"), + contractID: "@mozilla.org/messenger/mimeheaders;1", + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMimeHeaders, + Components.interfaces.msgIStructuredHeaders]), + + initialize: function MimeHeaders_initialize(allHeaders) { + this._headers = MimeParser.extractHeaders(allHeaders); + }, + + extractHeader: function MimeHeaders_extractHeader(header, getAll) { + if (!this._headers) + throw Components.results.NS_ERROR_NOT_INITIALIZED; + // Canonicalized to lower-case form + header = header.toLowerCase(); + if (!this._headers.has(header)) + return null; + var values = this._headers.getRawHeader(header); + if (getAll) + return values.join(",\r\n\t"); + else + return values[0]; + }, + + get allHeaders() { + return this._headers.rawHeaderText; + } +}; + +function MimeWritableStructuredHeaders() { + this._headers = new Map(); +} +MimeWritableStructuredHeaders.prototype = { + __proto__: MimeStructuredHeaders.prototype, + classID: Components.ID("c560806a-425f-4f0f-bf69-397c58c599a7"), + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.msgIStructuredHeaders, + Components.interfaces.msgIWritableStructuredHeaders]), + + setHeader: function (aHeaderName, aValue) { + this._headers.set(aHeaderName.toLowerCase(), aValue); + }, + + deleteHeader: function (aHeaderName) { + this._headers.delete(aHeaderName.toLowerCase()); + }, + + addAllHeaders: function (aHeaders) { + let headerList = aHeaders.headerNames; + while (headerList.hasMore()) { + let header = headerList.getNext(); + this.setHeader(header, aHeaders.getHeader(header)); + } + }, + + setUnstructuredHeader: function (aHeaderName, aValue) { + this.setHeader(aHeaderName, aValue); + }, + + setAddressingHeader: function (aHeaderName, aAddresses, aCount) { + this.setHeader(aHeaderName, fixXpconnectAddresses(aAddresses)); + }, + + setRawHeader: function (aHeaderName, aValue, aCharset) { + aValue = jsmime.headerparser.convert8BitHeader(aValue, aCharset); + try { + this.setHeader(aHeaderName, + jsmime.headerparser.parseStructuredHeader(aHeaderName, aValue)); + } catch (e) { + // This means we don't have a structured encoder. Just assume it's a raw + // string value then. + this.setHeader(aHeaderName, aValue); + } + } +}; + +// These are prototypes for nsIMsgHeaderParser implementation +var Mailbox = { + toString: function () { + return this.name ? this.name + " <" + this.email + ">" : this.email; + } +}; + +var EmailGroup = { + toString: function () { + return this.name + ": " + this.group.map(x => x.toString()).join(", "); + } +}; + +// A helper method for parse*Header that takes into account the desire to +// preserve group and also tweaks the output to support the prototypes for the +// XPIDL output. +function fixArray(addresses, preserveGroups, count) { + function resetPrototype(obj, prototype) { + let prototyped = Object.create(prototype); + for (let key of Object.getOwnPropertyNames(obj)) { + if (typeof obj[key] == "string") { + prototyped[key] = obj[key].replace(/\x00/g, ''); + } else { + prototyped[key] = obj[key]; + } + } + return prototyped; + } + let outputArray = []; + for (let element of addresses) { + if ('group' in element) { + // Fix up the prototypes of the group and the list members + element = resetPrototype(element, EmailGroup); + element.group = element.group.map(e => resetPrototype(e, Mailbox)); + + // Add to the output array + if (preserveGroups) + outputArray.push(element); + else + outputArray = outputArray.concat(element.group); + } else { + element = resetPrototype(element, Mailbox); + outputArray.push(element); + } + } + + if (count) + count.value = outputArray.length; + return outputArray; +} + +function MimeAddressParser() { +} +MimeAddressParser.prototype = { + classID: Components.ID("96bd8769-2d0e-4440-963d-22b97fb3ba77"), + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgHeaderParser]), + + parseEncodedHeader: function (aHeader, aCharset, aPreserveGroups, count) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_ALL_I18N, aCharset); + return fixArray(value, aPreserveGroups, count); + }, + parseEncodedHeaderW: function (aHeader, count) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_ADDRESS | + MimeParser.HEADER_OPTION_DECODE_2231 | + MimeParser.HEADER_OPTION_DECODE_2047, + undefined); + return fixArray(value, false, count); + }, + parseDecodedHeader: function (aHeader, aPreserveGroups, count) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, MimeParser.HEADER_ADDRESS); + return fixArray(value, aPreserveGroups, count); + }, + + makeMimeHeader: function (addresses, length) { + addresses = fixXpconnectAddresses(addresses); + // Don't output any necessary continuations, so make line length as large as + // possible first. + let options = { + softMargin: 900, + hardMargin: 900, + useASCII: false // We don't want RFC 2047 encoding here. + }; + let handler = new HeaderHandler(); + let emitter = new jsmime.headeremitter.makeStreamingEmitter(handler, + options); + emitter.addAddresses(addresses); + emitter.finish(true); + return handler.value.replace(/\r\n( |$)/g, ''); + }, + + extractFirstName: function (aHeader) { + let address = this.parseDecodedHeader(aHeader, false)[0]; + return address.name || address.email; + }, + + removeDuplicateAddresses: function (aAddrs, aOtherAddrs) { + // This is actually a rather complicated algorithm, especially if we want to + // preserve group structure. Basically, we use a set to identify which + // headers we have seen and therefore want to remove. To work in several + // various forms of edge cases, we need to normalize the entries in that + // structure. + function normalize(email) { + // XXX: This algorithm doesn't work with IDN yet. It looks like we have to + // convert from IDN then do lower case, but I haven't confirmed yet. + return email.toLowerCase(); + } + + // The filtration function, which removes email addresses that are + // duplicates of those we have already seen. + function filterAccept(e) { + if ('email' in e) { + // If we've seen the address, don't keep this one; otherwise, add it to + // the list. + let key = normalize(e.email); + if (allAddresses.has(key)) + return false; + allAddresses.add(key); + } else { + // Groups -> filter out all the member addresses. + e.group = e.group.filter(filterAccept); + } + return true; + } + + // First, collect all of the emails to forcibly delete. + let allAddresses = new Set(); + for (let element of this.parseDecodedHeader(aOtherAddrs, false)) { + allAddresses.add(normalize(element.email)); + } + + // The actual data to filter + let filtered = this.parseDecodedHeader(aAddrs, true).filter(filterAccept); + return this.makeMimeHeader(filtered); + }, + + makeMailboxObject: function (aName, aEmail) { + let object = Object.create(Mailbox); + object.name = aName; + object.email = aEmail ? aEmail.trim() : aEmail; + return object; + }, + + makeGroupObject: function (aName, aMembers) { + let object = Object.create(EmailGroup); + object.name = aName; + object.group = aMembers; + return object; + }, + + makeFromDisplayAddress: function (aDisplay, count) { + // The basic idea is to split on every comma, so long as there is a + // preceding @. + let output = []; + while (aDisplay.length) { + let at = aDisplay.indexOf('@'); + let comma = aDisplay.indexOf(',', at + 1); + let addr; + if (comma > 0) { + addr = aDisplay.substr(0, comma); + aDisplay = aDisplay.substr(comma + 1); + } else { + addr = aDisplay; + aDisplay = ""; + } + output.push(this._makeSingleAddress(addr.trimLeft())); + } + if (count) + count.value = output.length; + return output; + }, + + /// Construct a single email address from a name <local@domain> token. + _makeSingleAddress: function (aDisplayName) { + if (aDisplayName.includes('<')) { + let lbracket = aDisplayName.lastIndexOf('<'); + let rbracket = aDisplayName.lastIndexOf('>'); + return this.makeMailboxObject( + lbracket == 0 ? '' : aDisplayName.slice(0, lbracket).trim(), + aDisplayName.slice(lbracket + 1, rbracket)); + } else { + return this.makeMailboxObject('', aDisplayName); + } + }, + + // What follows is the deprecated API that will be removed shortly. + + parseHeadersWithArray: function (aHeader, aAddrs, aNames, aFullNames) { + let addrs = [], names = [], fullNames = []; + let allAddresses = this.parseEncodedHeader(aHeader, undefined, false); + + // Don't index the dummy empty address. + if (aHeader.trim() == "") + allAddresses = []; + for (let address of allAddresses) { + addrs.push(address.email); + names.push(address.name || null); + fullNames.push(address.toString()); + } + + aAddrs.value = addrs; + aNames.value = names; + aFullNames.value = fullNames; + return allAddresses.length; + }, + + extractHeaderAddressMailboxes: function (aLine) { + return this.parseDecodedHeader(aLine).map(addr => addr.email).join(", "); + }, + + extractHeaderAddressNames: function (aLine) { + return this.parseDecodedHeader(aLine).map(addr => addr.name || addr.email) + .join(", "); + }, + + extractHeaderAddressName: function (aLine) { + let addrs = this.parseDecodedHeader(aLine).map(addr => + addr.name || addr.email); + return addrs.length == 0 ? "" : addrs[0]; + }, + + makeMimeAddress: function (aName, aEmail) { + let object = this.makeMailboxObject(aName, aEmail); + return this.makeMimeHeader([object]); + }, +}; + +function MimeConverter() { +} +MimeConverter.prototype = { + classID: Components.ID("93f8c049-80ed-4dda-9000-94ad8daba44c"), + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMimeConverter]), + + encodeMimePartIIStr_UTF8: function (aHeader, aStructured, aCharset, + aFieldNameLen, aLineLength) { + // The JSMime encoder only works in UTF-8, so if someone requests to not do + // it, they need to change their code. + if (aCharset.toLowerCase() != "utf-8") { + Deprecated.warning("Encoding to non-UTF-8 values is obsolete", + "http://bugzilla.mozilla.org/show_bug.cgi?id=790855"); + } + + // Compute the encoding options. The way our API is structured in this + // method is really horrendous and does not align with the way that JSMime + // handles it. Instead, we'll need to create a fake header to take into + // account the aFieldNameLen parameter. + let fakeHeader = '-'.repeat(aFieldNameLen); + let options = { + softMargin: aLineLength, + useASCII: true, + }; + let handler = new HeaderHandler(); + let emitter = new jsmime.headeremitter.makeStreamingEmitter(handler, + options); + + // Add the text to the be encoded. + emitter.addHeaderName(fakeHeader); + if (aStructured) { + // Structured really means "this is an addressing header" + let addresses = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_DECODE_2047); + // This happens in one of our tests if there is a "bare" email but no + // @ sign. Without it, the result disappears since our emission code + // assumes that an empty email is not worth emitting. + if (addresses.length === 1 && addresses[0].email === "" && + addresses[0].name !== "") { + addresses[0].email = addresses[0].name; + addresses[0].name = ""; + } + emitter.addAddresses(addresses); + } else { + emitter.addUnstructured(aHeader); + } + + // Compute the output. We need to strip off the fake prefix added earlier + // and the extra CRLF at the end. + emitter.finish(true); + let value = handler.value; + value = value.replace(new RegExp(fakeHeader + ":\\s*"), ""); + return value.substring(0, value.length - 2); + }, + + decodeMimeHeader: function (aHeader, aDefaultCharset, aOverride, aUnfold) { + let value = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_UNSTRUCTURED | MimeParser.HEADER_OPTION_ALL_I18N, + aDefaultCharset); + if (aUnfold) { + value = value.replace(/[\r\n]\t/g, ' ') + .replace(/[\r\n]/g, ''); + } + return value; + }, + + // This is identical to the above, except for factors that are handled by the + // xpconnect conversion process + decodeMimeHeaderToUTF8: function () { + return this.decodeMimeHeader.apply(this, arguments); + }, +}; + +var components = [MimeHeaders, MimeWritableStructuredHeaders, MimeAddressParser, + MimeConverter]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/mime/src/mimeParser.jsm b/mailnews/mime/src/mimeParser.jsm new file mode 100644 index 000000000..904675f10 --- /dev/null +++ b/mailnews/mime/src/mimeParser.jsm @@ -0,0 +1,258 @@ +/* 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/. */ +// vim:set ts=2 sw=2 sts=2 et ft=javascript: + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource:///modules/jsmime.jsm"); + +var EXPORTED_SYMBOLS = ["MimeParser"]; + +// Emitter helpers, for internal functions later on. +var ExtractHeadersEmitter = { + startPart: function (partNum, headers) { + if (partNum == '') { + this.headers = headers; + } + } +}; + +var ExtractHeadersAndBodyEmitter = { + body: '', + startPart: ExtractHeadersEmitter.startPart, + deliverPartData: function (partNum, data) { + if (partNum == '') + this.body += data; + } +}; + +var Ci = Components.interfaces; +var Cc = Components.classes; + +/// Sets appropriate default options for chrome-privileged environments +function setDefaultParserOptions(opts) { + if (!("onerror" in opts)) { + opts.onerror = Components.utils.reportError; + } +} + +var MimeParser = { + /** + * Triggers an asynchronous parse of the given input. + * + * The input is an input stream; the stream will be read until EOF and then + * closed upon completion. Both blocking and nonblocking streams are + * supported by this implementation, but it is still guaranteed that the first + * callback will not happen before this method returns. + * + * @param input An input stream of text to parse. + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + parseAsync: function MimeParser_parseAsync(input, emitter, opts) { + // Normalize the input into an input stream. + if (!(input instanceof Ci.nsIInputStream)) { + throw new Error("input is not a recognizable type!"); + } + + // We need a pump for the listener + var pump = Cc["@mozilla.org/network/input-stream-pump;1"] + .createInstance(Ci.nsIInputStreamPump); + pump.init(input, -1, -1, 0, 0, true); + + // Make a stream listener with the given emitter and use it to read from + // the pump. + var parserListener = MimeParser.makeStreamListenerParser(emitter, opts); + pump.asyncRead(parserListener, null); + }, + + /** + * Triggers an synchronous parse of the given input. + * + * The input is a string that is immediately parsed, calling all functions on + * the emitter before this function returns. + * + * @param input A string or input stream of text to parse. + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + parseSync: function MimeParser_parseSync(input, emitter, opts) { + // We only support string parsing if we are trying to do this parse + // synchronously. + if (typeof input != "string") { + throw new Error("input is not a recognizable type!"); + } + setDefaultParserOptions(opts); + var parser = new jsmime.MimeParser(emitter, opts); + parser.deliverData(input); + parser.deliverEOF(); + return; + }, + + /** + * Returns a stream listener that feeds data into a parser. + * + * In addition to the functions on the emitter that the parser may use, the + * generated stream listener will also make calls to onStartRequest and + * onStopRequest on the emitter (if they exist). + * + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + makeStreamListenerParser: function MimeParser_makeSLParser(emitter, opts) { + var StreamListener = { + onStartRequest: function SLP_onStartRequest(aRequest, aContext) { + try { + if ("onStartRequest" in emitter) + emitter.onStartRequest(aRequest, aContext); + } finally { + this._parser.resetParser(); + } + }, + onStopRequest: function SLP_onStopRequest(aRequest, aContext, aStatus) { + this._parser.deliverEOF(); + if ("onStopRequest" in emitter) + emitter.onStopRequest(aRequest, aContext, aStatus); + }, + onDataAvailable: function SLP_onData(aRequest, aContext, aStream, + aOffset, aCount) { + var scriptIn = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + scriptIn.init(aStream); + // Use readBytes instead of read to handle embedded NULs properly. + this._parser.deliverData(scriptIn.readBytes(aCount)); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener, + Ci.nsIRequestObserver]) + }; + setDefaultParserOptions(opts); + StreamListener._parser = new jsmime.MimeParser(emitter, opts); + return StreamListener; + }, + + /** + * Returns a new raw MIME parser. + * + * Prefer one of the other methods where possible, since the input here must + * be driven manually. + * + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + makeParser: function MimeParser_makeParser(emitter, opts) { + setDefaultParserOptions(opts); + return new jsmime.MimeParser(emitter, opts); + }, + + /** + * Returns a dictionary of headers for the given input. + * + * The input is any type of input that would be accepted by parseSync. What + * is returned is a JS object that represents the headers of the entire + * envelope as would be received by startPart when partNum is the empty + * string. + * + * @param input A string of text to parse. + */ + extractHeaders: function MimeParser_extractHeaders(input) { + var emitter = Object.create(ExtractHeadersEmitter); + MimeParser.parseSync(input, emitter, {pruneat: '', bodyformat: 'none'}); + return emitter.headers; + }, + + /** + * Returns the headers and body for the given input message. + * + * The return value is an array whose first element is the dictionary of + * headers (as would be returned by extractHeaders) and whose second element + * is a binary string of the entire body of the message. + * + * @param input A string of text to parse. + */ + extractHeadersAndBody: function MimeParser_extractHeaders(input) { + var emitter = Object.create(ExtractHeadersAndBodyEmitter); + MimeParser.parseSync(input, emitter, {pruneat: '', bodyformat: 'raw'}); + return [emitter.headers, emitter.body]; + }, + + // Parameters for parseHeaderField + + /** + * Parse the header as if it were unstructured. + * + * This results in the same string if no other options are specified. If other + * options are specified, this causes the string to be modified appropriately. + */ + HEADER_UNSTRUCTURED: 0x00, + /** + * Parse the header as if it were in the form text; attr=val; attr=val. + * + * Such headers include Content-Type, Content-Disposition, and most other + * headers used by MIME as opposed to messages. + */ + HEADER_PARAMETER: 0x02, + /** + * Parse the header as if it were a sequence of mailboxes. + */ + HEADER_ADDRESS: 0x03, + + /** + * This decodes parameter values according to RFC 2231. + * + * This flag means nothing if HEADER_PARAMETER is not specified. + */ + HEADER_OPTION_DECODE_2231: 0x10, + /** + * This decodes the inline encoded-words that are in RFC 2047. + */ + HEADER_OPTION_DECODE_2047: 0x20, + /** + * This converts the header from a raw string to proper Unicode. + */ + HEADER_OPTION_ALLOW_RAW: 0x40, + + /// Convenience for all three of the above. + HEADER_OPTION_ALL_I18N: 0x70, + + /** + * Parse a header field according to the specification given by flags. + * + * Permissible flags begin with one of the HEADER_* flags, which may be or'd + * with any of the HEADER_OPTION_* flags to modify the result appropriately. + * + * If the option HEADER_OPTION_ALLOW_RAW is passed, the charset parameter, if + * present, is the charset to fallback to if the header is not decodable as + * UTF-8 text. If HEADER_OPTION_ALLOW_RAW is passed but the charset parameter + * is not provided, then no fallback decoding will be done. If + * HEADER_OPTION_ALLOW_RAW is not passed, then no attempt will be made to + * convert charsets. + * + * @param text The value of a MIME or message header to parse. + * @param flags A set of flags that controls interpretation of the header. + * @param charset A default charset to assume if no information may be found. + */ + parseHeaderField: function MimeParser_parseHeaderField(text, flags, charset) { + // If we have a raw string, convert it to Unicode first + if (flags & MimeParser.HEADER_OPTION_ALLOW_RAW) + text = jsmime.headerparser.convert8BitHeader(text, charset); + + // The low 4 bits indicate the type of the header we are parsing. All of the + // higher-order bits are flags. + switch (flags & 0x0f) { + case MimeParser.HEADER_UNSTRUCTURED: + if (flags & MimeParser.HEADER_OPTION_DECODE_2047) + text = jsmime.headerparser.decodeRFC2047Words(text); + return text; + case MimeParser.HEADER_PARAMETER: + return jsmime.headerparser.parseParameterHeader(text, + (flags & MimeParser.HEADER_OPTION_DECODE_2047) != 0, + (flags & MimeParser.HEADER_OPTION_DECODE_2231) != 0); + case MimeParser.HEADER_ADDRESS: + return jsmime.headerparser.parseAddressingHeader(text, + (flags & MimeParser.HEADER_OPTION_DECODE_2047) != 0); + default: + throw "Illegal type of header field"; + } + }, +}; diff --git a/mailnews/mime/src/mimeTextHTMLParsed.cpp b/mailnews/mime/src/mimeTextHTMLParsed.cpp new file mode 100644 index 000000000..2d45bd2c1 --- /dev/null +++ b/mailnews/mime/src/mimeTextHTMLParsed.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Most of this code is copied from mimethsa. If you find a bug here, check that class, too. */ + +/* This runs the entire HTML document through the Mozilla HTML parser, and + then outputs it as string again. This ensures that the HTML document is + syntactically correct and complete and all tags and attributes are closed. + + That prevents "MIME in the middle" attacks like efail.de. + The base problem is that we concatenate different MIME parts in the output + and render them all together as a single HTML document in the display. + + The better solution would be to put each MIME part into its own <iframe type="content">. + during rendering. Unfortunately, we'd need <iframe seamless> for that. + That would remove the need for this workaround, and stop even more attack classes. +*/ + +#include "mimeTextHTMLParsed.h" +#include "prmem.h" +#include "prlog.h" +#include "msgCore.h" +#include "nsIDOMParser.h" +#include "nsIDOMDocument.h" +#include "nsIDocument.h" +#include "nsIDocumentEncoder.h" +#include "mozilla/ErrorResult.h" +#include "nsIPrefBranch.h" +#include "mimethtm.h" + +#define MIME_SUPERCLASS mimeInlineTextHTMLClass +MimeDefClass(MimeInlineTextHTMLParsed, MimeInlineTextHTMLParsedClass, + mimeInlineTextHTMLParsedClass, &MIME_SUPERCLASS); + +static int MimeInlineTextHTMLParsed_parse_line(const char *, int32_t, + MimeObject *); +static int MimeInlineTextHTMLParsed_parse_begin(MimeObject *obj); +static int MimeInlineTextHTMLParsed_parse_eof(MimeObject *, bool); +static void MimeInlineTextHTMLParsed_finalize(MimeObject *obj); + +static int +MimeInlineTextHTMLParsedClassInitialize(MimeInlineTextHTMLParsedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *)clazz; + NS_ASSERTION(!oclass->class_initialized, "problem with superclass"); + oclass->parse_line = MimeInlineTextHTMLParsed_parse_line; + oclass->parse_begin = MimeInlineTextHTMLParsed_parse_begin; + oclass->parse_eof = MimeInlineTextHTMLParsed_parse_eof; + oclass->finalize = MimeInlineTextHTMLParsed_finalize; + + return 0; +} + +static int +MimeInlineTextHTMLParsed_parse_begin(MimeObject *obj) +{ + MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj; + me->complete_buffer = new nsString(); + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) + return status; + + return 0; +} + +static int +MimeInlineTextHTMLParsed_parse_eof(MimeObject *obj, bool abort_p) +{ + + if (obj->closed_p) + return 0; + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) + return status; + MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj; + + // We have to cache all lines and parse the whole document at once. + // There's a useful sounding function parseFromStream(), but it only allows XML + // mimetypes, not HTML. Methinks that's because the HTML soup parser + // needs the entire doc to make sense of the gibberish that people write. + if (!me || !me->complete_buffer) + return 0; + + nsString& rawHTML = *(me->complete_buffer); + if (rawHTML.IsEmpty()) + return 0; + nsString parsed; + nsresult rv; + + // Parse the HTML source. + nsCOMPtr<nsIDOMDocument> document; + nsCOMPtr<nsIDOMParser> parser = do_GetService(NS_DOMPARSER_CONTRACTID); + rv = parser->ParseFromString(rawHTML.get(), "text/html", + getter_AddRefs(document)); + NS_ENSURE_SUCCESS(rv, -1); + + // Serialize it back to HTML source again. + nsCOMPtr<nsIDocumentEncoder> encoder = do_CreateInstance( + "@mozilla.org/layout/documentEncoder;1?type=text/html"); + uint32_t aFlags = nsIDocumentEncoder::OutputRaw | + nsIDocumentEncoder::OutputDisallowLineBreaking; + rv = encoder->Init(document, NS_LITERAL_STRING("text/html"), aFlags); + NS_ENSURE_SUCCESS(rv, -1); + rv = encoder->EncodeToString(parsed); + NS_ENSURE_SUCCESS(rv, -1); + + // Write it out. + NS_ConvertUTF16toUTF8 resultCStr(parsed); + MimeInlineTextHTML_insert_lang_div(obj, resultCStr); + MimeInlineTextHTML_remove_plaintext_tag(obj, resultCStr); + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line( + resultCStr.BeginWriting(), resultCStr.Length(), obj); + rawHTML.Truncate(); + return status; +} + +void +MimeInlineTextHTMLParsed_finalize(MimeObject *obj) +{ + MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj; + + if (me && me->complete_buffer) + { + obj->clazz->parse_eof(obj, false); + delete me->complete_buffer; + me->complete_buffer = NULL; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + +static int +MimeInlineTextHTMLParsed_parse_line(const char *line, int32_t length, + MimeObject *obj) +{ + MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj; + + if (!me || !(me->complete_buffer)) + return -1; + + nsCString linestr(line, length); + NS_ConvertUTF8toUTF16 line_ucs2(linestr.get()); + if (length && line_ucs2.IsEmpty()) + CopyASCIItoUTF16(linestr, line_ucs2); + (me->complete_buffer)->Append(line_ucs2); + + return 0; +} diff --git a/mailnews/mime/src/mimeTextHTMLParsed.h b/mailnews/mime/src/mimeTextHTMLParsed.h new file mode 100644 index 000000000..753a5153b --- /dev/null +++ b/mailnews/mime/src/mimeTextHTMLParsed.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETEXTHTMLPARSED_H_ +#define _MIMETEXTHTMLPARSED_H_ + +#include "mimethtm.h" + +typedef struct MimeInlineTextHTMLParsedClass MimeInlineTextHTMLParsedClass; +typedef struct MimeInlineTextHTMLParsed MimeInlineTextHTMLParsed; + +struct MimeInlineTextHTMLParsedClass { + MimeInlineTextHTMLClass html; +}; + +extern MimeInlineTextHTMLParsedClass mimeInlineTextHTMLParsedClass; + +struct MimeInlineTextHTMLParsed { + MimeInlineTextHTML html; + nsString *complete_buffer; // Gecko parser expects wide strings +}; + +#define MimeInlineTextHTMLParsedClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETEXTHTMLPARSED_H_ */ diff --git a/mailnews/mime/src/mimebuf.cpp b/mailnews/mime/src/mimebuf.cpp new file mode 100644 index 000000000..f81047205 --- /dev/null +++ b/mailnews/mime/src/mimebuf.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +/* + * mimebuf.c - libmsg like buffer handling routines for libmime + */ +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" + +extern "C" int +mime_GrowBuffer (uint32_t desired_size, uint32_t element_size, uint32_t quantum, + char **buffer, int32_t *size) +{ + if ((uint32_t) *size <= desired_size) + { + char *new_buf; + uint32_t increment = desired_size - *size; + if (increment < quantum) /* always grow by a minimum of N bytes */ + increment = quantum; + + new_buf = (*buffer + ? (char *) PR_Realloc (*buffer, (*size + increment) + * (element_size / sizeof(char))) + : (char *) PR_MALLOC ((*size + increment) + * (element_size / sizeof(char)))); + if (! new_buf) + return MIME_OUT_OF_MEMORY; + *buffer = new_buf; + *size += increment; + } + return 0; +} + +/* The opposite of mime_LineBuffer(): takes small buffers and packs them + up into bigger buffers before passing them along. + + Pass in a desired_buffer_size 0 to tell it to flush (for example, in + in the very last call to this function.) + */ +extern "C" int +mime_ReBuffer (const char *net_buffer, int32_t net_buffer_size, + uint32_t desired_buffer_size, + char **bufferP, int32_t *buffer_sizeP, uint32_t *buffer_fpP, + int32_t (*per_buffer_fn) (char *buffer, uint32_t buffer_size, + void *closure), + void *closure) +{ + int status = 0; + + if (desired_buffer_size >= (uint32_t) (*buffer_sizeP)) + { + status = mime_GrowBuffer (desired_buffer_size, sizeof(char), 1024, + bufferP, buffer_sizeP); + if (status < 0) return status; + } + + do + { + int32_t size = *buffer_sizeP - *buffer_fpP; + if (size > net_buffer_size) + size = net_buffer_size; + if (size > 0) + { + memcpy ((*bufferP) + (*buffer_fpP), net_buffer, size); + (*buffer_fpP) += size; + net_buffer += size; + net_buffer_size -= size; + } + + if (*buffer_fpP > 0 && + *buffer_fpP >= desired_buffer_size) + { + status = (*per_buffer_fn) ((*bufferP), (*buffer_fpP), closure); + *buffer_fpP = 0; + if (status < 0) return status; + } + } + while (net_buffer_size > 0); + + return 0; +} + +static int +convert_and_send_buffer(char* buf, int length, bool convert_newlines_p, + int32_t (* per_line_fn) (char *line, + uint32_t line_length, + void *closure), + void *closure) +{ + /* Convert the line terminator to the native form. + */ + char* newline; + +#if (MSG_LINEBREAK_LEN == 2) + /*** + * This is a patch to support a mail DB corruption cause by earlier version that lead to a crash. + * What happened is that the line terminator is CR+NULL+LF. Therefore, we first process a line + * terminated by CR then a second line that contains only NULL+LF. We need to ignore this second + * line. See bug http://bugzilla.mozilla.org/show_bug.cgi?id=61412 for more information. + ***/ + if (length == 2 && buf[0] == 0x00 && buf[1] == '\n') + return 0; +#endif + + NS_ASSERTION(buf && length > 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!buf || length <= 0) return -1; + newline = buf + length; + NS_ASSERTION(newline[-1] == '\r' || newline[-1] == '\n', "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (newline[-1] != '\r' && newline[-1] != '\n') return -1; + + if (!convert_newlines_p) + { + } +#if (MSG_LINEBREAK_LEN == 1) + else if ((newline - buf) >= 2 && + newline[-2] == '\r' && + newline[-1] == '\n') + { + /* CRLF -> CR or LF */ + buf [length - 2] = MSG_LINEBREAK[0]; + length--; + } + else if (newline > buf + 1 && + newline[-1] != MSG_LINEBREAK[0]) + { + /* CR -> LF or LF -> CR */ + buf [length - 1] = MSG_LINEBREAK[0]; + } +#else + else if (((newline - buf) >= 2 && newline[-2] != '\r') || + ((newline - buf) >= 1 && newline[-1] != '\n')) + { + /* LF -> CRLF or CR -> CRLF */ + length++; + buf[length - 2] = MSG_LINEBREAK[0]; + buf[length - 1] = MSG_LINEBREAK[1]; + } +#endif + + return (*per_line_fn)(buf, length, closure); +} + +extern "C" int +mime_LineBuffer (const char *net_buffer, int32_t net_buffer_size, + char **bufferP, int32_t *buffer_sizeP, uint32_t *buffer_fpP, + bool convert_newlines_p, + int32_t (* per_line_fn) (char *line, uint32_t line_length, + void *closure), + void *closure) +{ + int status = 0; + if (*buffer_fpP > 0 && *bufferP && (*bufferP)[*buffer_fpP - 1] == '\r' && + net_buffer_size > 0 && net_buffer[0] != '\n') { + /* The last buffer ended with a CR. The new buffer does not start + with a LF. This old buffer should be shipped out and discarded. */ + NS_ASSERTION((uint32_t) *buffer_sizeP > *buffer_fpP, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if ((uint32_t) *buffer_sizeP <= *buffer_fpP) return -1; + status = convert_and_send_buffer(*bufferP, *buffer_fpP, + convert_newlines_p, + per_line_fn, closure); + if (status < 0) return status; + *buffer_fpP = 0; + } + while (net_buffer_size > 0) + { + const char *net_buffer_end = net_buffer + net_buffer_size; + const char *newline = 0; + const char *s; + + + for (s = net_buffer; s < net_buffer_end; s++) + { + /* Move forward in the buffer until the first newline. + Stop when we see CRLF, CR, or LF, or the end of the buffer. + *But*, if we see a lone CR at the *very end* of the buffer, + treat this as if we had reached the end of the buffer without + seeing a line terminator. This is to catch the case of the + buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n". + */ + if (*s == '\r' || *s == '\n') + { + newline = s; + if (newline[0] == '\r') + { + if (s == net_buffer_end - 1) + { + /* CR at end - wait for the next character. */ + newline = 0; + break; + } + else if (newline[1] == '\n') + /* CRLF seen; swallow both. */ + newline++; + } + newline++; + break; + } + } + + /* Ensure room in the net_buffer and append some or all of the current + chunk of data to it. */ + { + const char *end = (newline ? newline : net_buffer_end); + uint32_t desired_size = (end - net_buffer) + (*buffer_fpP) + 1; + + if (desired_size >= (uint32_t) (*buffer_sizeP)) + { + status = mime_GrowBuffer (desired_size, sizeof(char), 1024, + bufferP, buffer_sizeP); + if (status < 0) return status; + } + memcpy ((*bufferP) + (*buffer_fpP), net_buffer, (end - net_buffer)); + (*buffer_fpP) += (end - net_buffer); + (*bufferP)[*buffer_fpP] = '\0'; + } + + /* Now *bufferP contains either a complete line, or as complete + a line as we have read so far. + + If we have a line, process it, and then remove it from `*bufferP'. + Then go around the loop again, until we drain the incoming data. + */ + if (!newline) + return 0; + + status = convert_and_send_buffer(*bufferP, *buffer_fpP, + convert_newlines_p, + per_line_fn, closure); + if (status < 0) + return status; + + net_buffer_size -= (newline - net_buffer); + net_buffer = newline; + (*buffer_fpP) = 0; + } + return 0; +} diff --git a/mailnews/mime/src/mimebuf.h b/mailnews/mime/src/mimebuf.h new file mode 100644 index 000000000..38b9a68c7 --- /dev/null +++ b/mailnews/mime/src/mimebuf.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#ifndef _MIMEBUF_H_ +#define _MIMEBUF_H_ + +extern "C" int mime_GrowBuffer (uint32_t desired_size, + uint32_t element_size, uint32_t quantum, + char **buffer, int32_t *size); + +extern "C" int mime_LineBuffer (const char *net_buffer, int32_t net_buffer_size, + char **bufferP, int32_t *buffer_sizeP, + int32_t *buffer_fpP, + bool convert_newlines_p, + int32_t (* per_line_fn) (char *line, int32_t + line_length, void *closure), + void *closure); + +extern "C" int mime_ReBuffer (const char *net_buffer, int32_t net_buffer_size, + uint32_t desired_buffer_size, + char **bufferP, uint32_t *buffer_sizeP, + uint32_t *buffer_fpP, + int32_t (*per_buffer_fn) (char *buffer, + uint32_t buffer_size, + void *closure), + void *closure); + + +#endif /* _MIMEBUF_H_ */ diff --git a/mailnews/mime/src/mimecms.cpp b/mailnews/mime/src/mimecms.cpp new file mode 100644 index 000000000..6f7455c0a --- /dev/null +++ b/mailnews/mime/src/mimecms.cpp @@ -0,0 +1,797 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsICMSMessage.h" +#include "nsICMSMessage2.h" +#include "nsICMSMessageErrors.h" +#include "nsICMSDecoder.h" +#include "mimecms.h" +#include "mimemcms.h" +#include "mimemsig.h" +#include "nspr.h" +#include "mimemsg.h" +#include "mimemoz2.h" +#include "nsIURI.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMsgSMIMEHeaderSink.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsIX509Cert.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" +#include "mozilla/mailnews/MimeHeaderParser.h" + +using namespace mozilla::mailnews; + +// The name "mime encrypted" is misleading, because this code is used +// both for CMS messages that are encrypted, and also for messages that +// aren't encrypted, but only contain a signature. + +#define MIME_SUPERCLASS mimeEncryptedClass +MimeDefClass(MimeEncryptedCMS, MimeEncryptedCMSClass, + mimeEncryptedCMSClass, &MIME_SUPERCLASS); + +static void *MimeCMS_init(MimeObject *, int (*output_fn) (const char *, int32_t, void *), void *); +static int MimeCMS_write (const char *, int32_t, void *); +static int MimeCMS_eof (void *, bool); +static char * MimeCMS_generate (void *); +static void MimeCMS_free (void *); + +extern int SEC_ERROR_CERT_ADDR_MISMATCH; + +static int MimeEncryptedCMSClassInitialize(MimeEncryptedCMSClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); +#endif + + MimeEncryptedClass *eclass = (MimeEncryptedClass *) clazz; + eclass->crypto_init = MimeCMS_init; + eclass->crypto_write = MimeCMS_write; + eclass->crypto_eof = MimeCMS_eof; + eclass->crypto_generate_html = MimeCMS_generate; + eclass->crypto_free = MimeCMS_free; + + return 0; +} + + +typedef struct MimeCMSdata +{ + int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure); + void *output_closure; + nsCOMPtr<nsICMSDecoder> decoder_context; + nsCOMPtr<nsICMSMessage> content_info; + bool ci_is_encrypted; + char *sender_addr; + bool decoding_failed; + bool skip_content; + uint32_t decoded_bytes; + MimeObject *self; + bool any_parent_is_encrypted_p; + bool any_parent_is_signed_p; + nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink; + nsCString url; + + MimeCMSdata() + :output_fn(nullptr), + output_closure(nullptr), + ci_is_encrypted(false), + sender_addr(nullptr), + decoding_failed(false), + skip_content(false), + decoded_bytes(0), + self(nullptr), + any_parent_is_encrypted_p(false), + any_parent_is_signed_p(false) {} + + ~MimeCMSdata() + { + if(sender_addr) + PR_Free(sender_addr); + + // Do an orderly release of nsICMSDecoder and nsICMSMessage // + if (decoder_context) + { + nsCOMPtr<nsICMSMessage> cinfo; + decoder_context->Finish(getter_AddRefs(cinfo)); + } + } +} MimeCMSdata; + +/* SEC_PKCS7DecoderContentCallback for SEC_PKCS7DecoderStart() */ +static void MimeCMS_content_callback (void *arg, const char *buf, unsigned long length) +{ + int status; + MimeCMSdata *data = (MimeCMSdata *) arg; + if (!data) return; + + if (!data->output_fn) + return; + + PR_SetError(0,0); + status = data->output_fn (buf, length, data->output_closure); + if (status < 0) + { + PR_SetError(status, 0); + data->output_fn = 0; + return; + } + + data->decoded_bytes += length; +} + +bool MimeEncryptedCMS_encrypted_p (MimeObject *obj) +{ + bool encrypted; + + if (!obj) return false; + if (mime_typep(obj, (MimeObjectClass *) &mimeEncryptedCMSClass)) + { + MimeEncrypted *enc = (MimeEncrypted *) obj; + MimeCMSdata *data = (MimeCMSdata *) enc->crypto_closure; + if (!data || !data->content_info) return false; + data->content_info->ContentIsEncrypted(&encrypted); + return encrypted; + } + return false; +} + +bool MimeEncOrMP_CMS_signed_p(MimeObject *obj) { + bool is_signed; + + if (!obj) return false; + if (mime_typep(obj, (MimeObjectClass *)&mimeMultipartSignedCMSClass)) { + return true; + } + if (mime_typep(obj, (MimeObjectClass *)&mimeEncryptedCMSClass)) { + MimeEncrypted *enc = (MimeEncrypted *)obj; + MimeCMSdata *data = (MimeCMSdata *)enc->crypto_closure; + if (!data || !data->content_info) return false; + data->content_info->ContentIsSigned(&is_signed); + return is_signed; + } + return false; +} + +bool MimeAnyParentCMSEncrypted(MimeObject *obj) +{ + MimeObject *o2 = obj; + while (o2 && o2->parent) { + if (MimeEncryptedCMS_encrypted_p(o2->parent)) { + return true; + } + o2 = o2->parent; + } + return false; +} + +bool MimeAnyParentCMSSigned(MimeObject *obj) +{ + MimeObject *o2 = obj; + while (o2 && o2->parent) { + if (MimeEncOrMP_CMS_signed_p(o2->parent)) { + return true; + } + o2 = o2->parent; + } + return false; +} + +bool MimeCMSHeadersAndCertsMatch(nsICMSMessage *content_info, + nsIX509Cert *signerCert, + const char *from_addr, + const char *from_name, + const char *sender_addr, + const char *sender_name, + bool *signing_cert_without_email_address) +{ + nsCString cert_addr; + bool match = true; + bool foundFrom = false; + bool foundSender = false; + + /* Find the name and address in the cert. + */ + if (content_info) + { + // Extract any address contained in the cert. + // This will be used for testing, whether the cert contains no addresses at all. + content_info->GetSignerEmailAddress (getter_Copies(cert_addr)); + } + + if (signing_cert_without_email_address) + *signing_cert_without_email_address = cert_addr.IsEmpty(); + + /* Now compare them -- + consider it a match if the address in the cert matches the + address in the From field (or as a fallback, the Sender field) + */ + + /* If there is no addr in the cert at all, it can not match and we fail. */ + if (cert_addr.IsEmpty()) + { + match = false; + } + else + { + if (signerCert) + { + if (from_addr && *from_addr) + { + NS_ConvertASCIItoUTF16 ucs2From(from_addr); + if (NS_FAILED(signerCert->ContainsEmailAddress(ucs2From, &foundFrom))) + { + foundFrom = false; + } + } + else if (sender_addr && *sender_addr) + { + NS_ConvertASCIItoUTF16 ucs2Sender(sender_addr); + if (NS_FAILED(signerCert->ContainsEmailAddress(ucs2Sender, &foundSender))) + { + foundSender = false; + } + } + } + + if (!foundSender && !foundFrom) + { + match = false; + } + } + + return match; +} + +class nsSMimeVerificationListener : public nsISMimeVerificationListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISMIMEVERIFICATIONLISTENER + + nsSMimeVerificationListener(const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, + int32_t aMimeNestingLevel, + const nsCString &aMsgNeckoURL); + +protected: + virtual ~nsSMimeVerificationListener() {} + + /** + * It is safe to declare this implementation as thread safe, + * despite not using a lock to protect the members. + * Because of the way the object will be used, we don't expect a race. + * After construction, the object is passed to another thread, + * but will no longer be accessed on the original thread. + * The other thread is unable to access/modify self's data members. + * When the other thread is finished, it will call into the "Notify" + * callback. Self's members will be accessed on the other thread, + * but this is fine, because there is no race with the original thread. + * Race-protection for XPCOM reference counting is sufficient. + */ + bool mSinkIsNull; + nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> mHeaderSink; + int32_t mMimeNestingLevel; + nsCString mMsgNeckoURL; + + nsCString mFromAddr; + nsCString mFromName; + nsCString mSenderAddr; + nsCString mSenderName; +}; + +class SignedStatusRunnable : public mozilla::Runnable +{ +public: + SignedStatusRunnable(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink, int32_t aNestingLevel, + int32_t aSignatureStatus, nsIX509Cert *aSignerCert, + const nsCString &aMsgNeckoURL); + NS_DECL_NSIRUNNABLE +protected: + nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> m_sink; + int32_t m_nestingLevel; + int32_t m_signatureStatus; + nsCOMPtr<nsIX509Cert> m_signerCert; + nsCString m_msgNeckoURL; +}; + +SignedStatusRunnable::SignedStatusRunnable(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink, + int32_t aNestingLevel, + int32_t aSignatureStatus, + nsIX509Cert *aSignerCert, + const nsCString &aMsgNeckoURL) : + m_sink(aSink), m_nestingLevel(aNestingLevel), + m_signatureStatus(aSignatureStatus), m_signerCert(aSignerCert), + m_msgNeckoURL(aMsgNeckoURL) +{ +} + +NS_IMETHODIMP SignedStatusRunnable::Run() +{ + return m_sink->SignedStatus(m_nestingLevel, m_signatureStatus, m_signerCert, + m_msgNeckoURL); +} + + +nsresult ProxySignedStatus(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink, + int32_t aNestingLevel, + int32_t aSignatureStatus, + nsIX509Cert *aSignerCert, + const nsCString &aMsgNeckoURL) +{ + RefPtr<SignedStatusRunnable> signedStatus = + new SignedStatusRunnable(aSink, aNestingLevel, aSignatureStatus, aSignerCert, aMsgNeckoURL); + return NS_DispatchToMainThread(signedStatus, NS_DISPATCH_SYNC); +} + +NS_IMPL_ISUPPORTS(nsSMimeVerificationListener, nsISMimeVerificationListener) + +nsSMimeVerificationListener::nsSMimeVerificationListener(const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel, + const nsCString &aMsgNeckoURL) + : mMsgNeckoURL(aMsgNeckoURL) +{ + mHeaderSink = new nsMainThreadPtrHolder<nsIMsgSMIMEHeaderSink>(aHeaderSink); + mSinkIsNull = !aHeaderSink; + mMimeNestingLevel = aMimeNestingLevel; + + mFromAddr = aFromAddr; + mFromName = aFromName; + mSenderAddr = aSenderAddr; + mSenderName = aSenderName; +} + +NS_IMETHODIMP nsSMimeVerificationListener::Notify(nsICMSMessage2 *aVerifiedMessage, + nsresult aVerificationResultCode) +{ + // Only continue if we have a valid pointer to the UI + NS_ENSURE_FALSE(mSinkIsNull, NS_OK); + + NS_ENSURE_TRUE(aVerifiedMessage, NS_ERROR_FAILURE); + + nsCOMPtr<nsICMSMessage> msg = do_QueryInterface(aVerifiedMessage); + NS_ENSURE_TRUE(msg, NS_ERROR_FAILURE); + + nsCOMPtr<nsIX509Cert> signerCert; + msg->GetSignerCert(getter_AddRefs(signerCert)); + + int32_t signature_status = nsICMSMessageErrors::GENERAL_ERROR; + + if (NS_FAILED(aVerificationResultCode)) + { + if (NS_ERROR_MODULE_SECURITY == NS_ERROR_GET_MODULE(aVerificationResultCode)) + signature_status = NS_ERROR_GET_CODE(aVerificationResultCode); + else if (NS_ERROR_NOT_IMPLEMENTED == aVerificationResultCode) + signature_status = nsICMSMessageErrors::VERIFY_ERROR_PROCESSING; + } + else + { + bool signing_cert_without_email_address; + + bool good_p = MimeCMSHeadersAndCertsMatch(msg, signerCert, + mFromAddr.get(), mFromName.get(), + mSenderAddr.get(), mSenderName.get(), + &signing_cert_without_email_address); + if (!good_p) + { + if (signing_cert_without_email_address) + signature_status = nsICMSMessageErrors::VERIFY_CERT_WITHOUT_ADDRESS; + else + signature_status = nsICMSMessageErrors::VERIFY_HEADER_MISMATCH; + } + else + signature_status = nsICMSMessageErrors::SUCCESS; + } + + ProxySignedStatus(mHeaderSink, mMimeNestingLevel, signature_status, + signerCert, mMsgNeckoURL); + + return NS_OK; +} + +int MIMEGetRelativeCryptoNestLevel(MimeObject *obj) +{ + /* + the part id of any mimeobj is mime_part_address(obj) + our currently displayed crypto part is obj + the part shown as the toplevel object in the current window is + obj->options->part_to_load + possibly stored in the toplevel object only ??? + but hopefully all nested mimeobject point to the same displayooptions + + we need to find out the nesting level of our currently displayed crypto object + wrt the shown part in the toplevel window + */ + + // if we are showing the toplevel message, aTopMessageNestLevel == 0 + int aTopMessageNestLevel = 0; + MimeObject *aTopShownObject = nullptr; + if (obj && obj->options->part_to_load) { + bool aAlreadyFoundTop = false; + for (MimeObject *walker = obj; walker; walker = walker->parent) { + if (aAlreadyFoundTop) { + if (!mime_typep(walker, (MimeObjectClass *) &mimeEncryptedClass) + && !mime_typep(walker, (MimeObjectClass *) &mimeMultipartSignedClass)) { + ++aTopMessageNestLevel; + } + } + if (!aAlreadyFoundTop && !strcmp(mime_part_address(walker), walker->options->part_to_load)) { + aAlreadyFoundTop = true; + aTopShownObject = walker; + } + if (!aAlreadyFoundTop && !walker->parent) { + // The mime part part_to_load is not a parent of the + // the crypto mime part passed in to this function as parameter obj. + // That means the crypto part belongs to another branch of the mime tree. + return -1; + } + } + } + + bool CryptoObjectIsChildOfTopShownObject = false; + if (!aTopShownObject) { + // no sub part specified, top message is displayed, and + // our crypto object is definitively a child of it + CryptoObjectIsChildOfTopShownObject = true; + } + + // if we are the child of the topmost message, aCryptoPartNestLevel == 1 + int aCryptoPartNestLevel = 0; + if (obj) { + for (MimeObject *walker = obj; walker; walker = walker->parent) { + // Crypto mime objects are transparent wrt nesting. + if (!mime_typep(walker, (MimeObjectClass *) &mimeEncryptedClass) + && !mime_typep(walker, (MimeObjectClass *) &mimeMultipartSignedClass)) { + ++aCryptoPartNestLevel; + } + if (aTopShownObject && walker->parent == aTopShownObject) { + CryptoObjectIsChildOfTopShownObject = true; + } + } + } + + if (!CryptoObjectIsChildOfTopShownObject) { + return -1; + } + + return aCryptoPartNestLevel - aTopMessageNestLevel; +} + +static void *MimeCMS_init(MimeObject *obj, + int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure), + void *output_closure) +{ + MimeCMSdata *data; + nsresult rv; + + if (!(obj && obj->options && output_fn)) return 0; + + data = new MimeCMSdata; + if (!data) return 0; + + data->self = obj; + data->output_fn = output_fn; + data->output_closure = output_closure; + PR_SetError(0, 0); + + data->any_parent_is_signed_p = MimeAnyParentCMSSigned(obj); + + if (data->any_parent_is_signed_p) { + // Parent is signed. + // We don't know yet if this child is signed or encrypted. + // (We'll know after decoding has completed and EOF is called.) + // We don't support "inner encrypt" with outer sign, because the + // inner encrypted part could have been produced by an attacker who + // stripped away a part containing the signature (S/MIME doesn't + // have integrity protection). + // A sign-then-sign encoding is confusing, too, because it could be + // an attempt to influence which signature is shown. + data->skip_content = true; + } + + if (!data->skip_content) { + data->decoder_context = do_CreateInstance(NS_CMSDECODER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + delete data; + return 0; + } + + rv = data->decoder_context->Start(MimeCMS_content_callback, data); + if (NS_FAILED(rv)) { + delete data; + return 0; + } + } + + data->any_parent_is_encrypted_p = MimeAnyParentCMSEncrypted(obj); + + mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure); + if (msd) + { + nsIChannel *channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsCOMPtr<nsIMsgMailNewsUrl> msgurl; + nsCOMPtr<nsISupports> securityInfo; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + rv = uri->GetSpec(data->url); + + // We only want to update the UI if the current mime transaction + // is intended for display. + // If the current transaction is intended for background processing, + // we can learn that by looking at the additional header=filter + // string contained in the URI. + // + // If we find something, we do not set smimeHeaderSink, + // which will prevent us from giving UI feedback. + // + // If we do not find header=filter, we assume the result of the + // processing will be shown in the UI. + + if (!strstr(data->url.get(), "?header=filter") && + !strstr(data->url.get(), "&header=filter") && + !strstr(data->url.get(), "?header=attach") && + !strstr(data->url.get(), "&header=attach")) + { + msgurl = do_QueryInterface(uri); + if (msgurl) + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + headerSink->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (securityInfo) + data->smimeHeaderSink = do_QueryInterface(securityInfo); + } + } + } // if channel + } // if msd + + return data; +} + +static int +MimeCMS_write (const char *buf, int32_t buf_size, void *closure) +{ + MimeCMSdata *data = (MimeCMSdata *) closure; + nsresult rv; + + if (!data || !data->output_fn || !data->decoder_context) return -1; + + if (!data->decoding_failed && !data->skip_content) { + PR_SetError(0, 0); + rv = data->decoder_context->Update(buf, buf_size); + data->decoding_failed = NS_FAILED(rv); + } + + return 0; +} + +void MimeCMSGetFromSender(MimeObject *obj, + nsCString &from_addr, + nsCString &from_name, + nsCString &sender_addr, + nsCString &sender_name) +{ + MimeHeaders *msg_headers = 0; + + /* Find the headers of the MimeMessage which is the parent (or grandparent) + of this object (remember, crypto objects nest.) */ + MimeObject *o2 = obj; + msg_headers = o2->headers; + while (o2 && + o2->parent && + !mime_typep(o2->parent, (MimeObjectClass *) &mimeMessageClass)) + { + o2 = o2->parent; + msg_headers = o2->headers; + } + + if (!msg_headers) + return; + + /* Find the names and addresses in the From and/or Sender fields. + */ + nsCString s; + + /* Extract the name and address of the "From:" field. */ + s.Adopt(MimeHeaders_get(msg_headers, HEADER_FROM, false, false)); + if (!s.IsEmpty()) + ExtractFirstAddress(EncodedHeader(s), from_name, from_addr); + + /* Extract the name and address of the "Sender:" field. */ + s.Adopt(MimeHeaders_get(msg_headers, HEADER_SENDER, false, false)); + if (!s.IsEmpty()) + ExtractFirstAddress(EncodedHeader(s), sender_name, sender_addr); +} + +void MimeCMSRequestAsyncSignatureVerification(nsICMSMessage *aCMSMsg, + const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel, + const nsCString &aMsgNeckoURL, + unsigned char* item_data, uint32_t item_len, + int16_t digest_type) +{ + nsCOMPtr<nsICMSMessage2> msg2 = do_QueryInterface(aCMSMsg); + if (!msg2) + return; + + RefPtr<nsSMimeVerificationListener> listener = + new nsSMimeVerificationListener(aFromAddr, aFromName, aSenderAddr, aSenderName, + aHeaderSink, aMimeNestingLevel, aMsgNeckoURL); + if (!listener) + return; + + if (item_data) + msg2->AsyncVerifyDetachedSignature(listener, item_data, item_len, digest_type); + else + msg2->AsyncVerifySignature(listener); +} + +static int +MimeCMS_eof (void *crypto_closure, bool abort_p) +{ + MimeCMSdata *data = (MimeCMSdata *) crypto_closure; + nsresult rv; + int32_t status = nsICMSMessageErrors::SUCCESS; + + if (!data || !data->output_fn) { + return -1; + } + + if (!data->skip_content && !data->decoder_context) { + // If we don't skip, we should have a context. + return -1; + } + + int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self); + + /* Hand an EOF to the crypto library. It may call data->output_fn. + (Today, the crypto library has no flushing to do, but maybe there + will be someday.) + + We save away the value returned and will use it later to emit a + blurb about whether the signature validation was cool. + */ + + PR_SetError(0, 0); + if (!data->skip_content) { + rv = data->decoder_context->Finish(getter_AddRefs(data->content_info)); + if (NS_FAILED(rv)) status = nsICMSMessageErrors::GENERAL_ERROR; + + data->decoder_context = nullptr; + } + + nsCOMPtr<nsIX509Cert> certOfInterest; + + if (!data->smimeHeaderSink) + return 0; + + if (aRelativeNestLevel < 0) + return 0; + + int32_t maxNestLevel = 0; + data->smimeHeaderSink->MaxWantedNesting(&maxNestLevel); + + if (aRelativeNestLevel > maxNestLevel) + return 0; + + if (data->decoding_failed) + status = nsICMSMessageErrors::GENERAL_ERROR; + + if (data->skip_content) { + // Skipping content means, we detected a forbidden combination + // of CMS objects, so let's make sure we replace the parent status + // with a bad status. + if (data->any_parent_is_signed_p) { + data->smimeHeaderSink->SignedStatus(aRelativeNestLevel, + nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url); + } + if (data->any_parent_is_encrypted_p) { + data->smimeHeaderSink->EncryptionStatus(aRelativeNestLevel, + nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url); + } + return 0; + } + + if (!data->content_info) + { + if (!data->decoded_bytes) + { + // We were unable to decode any data. + status = nsICMSMessageErrors::GENERAL_ERROR; + } + else + { + // Some content got decoded, but we failed to decode + // the final summary, probably we got truncated data. + status = nsICMSMessageErrors::ENCRYPT_INCOMPLETE; + } + + // Although a CMS message could be either encrypted or opaquely signed, + // what we see is most likely encrypted, because if it were + // signed only, we probably would have been able to decode it. + + data->ci_is_encrypted = true; + } + else + { + rv = data->content_info->ContentIsEncrypted(&data->ci_is_encrypted); + + if (NS_SUCCEEDED(rv) && data->ci_is_encrypted) { + data->content_info->GetEncryptionCert(getter_AddRefs(certOfInterest)); + } + else { + // Existing logic in mimei assumes, if !ci_is_encrypted, then it is signed. + // Make sure it indeed is signed. + + bool testIsSigned; + rv = data->content_info->ContentIsSigned(&testIsSigned); + + if (NS_FAILED(rv) || !testIsSigned) { + // Neither signed nor encrypted? + // We are unable to understand what we got, do not try to indicate S/Mime status. + return 0; + } + + nsCString from_addr; + nsCString from_name; + nsCString sender_addr; + nsCString sender_name; + + MimeCMSGetFromSender(data->self, + from_addr, from_name, + sender_addr, sender_name); + + MimeCMSRequestAsyncSignatureVerification(data->content_info, + from_addr.get(), from_name.get(), + sender_addr.get(), sender_name.get(), + data->smimeHeaderSink, aRelativeNestLevel, + data->url, nullptr, 0, 0); + } + } + + if (data->ci_is_encrypted) + { + data->smimeHeaderSink->EncryptionStatus( + aRelativeNestLevel, + status, + certOfInterest, + data->url + ); + } + + return 0; +} + +static void +MimeCMS_free (void *crypto_closure) +{ + MimeCMSdata *data = (MimeCMSdata *) crypto_closure; + if (!data) return; + + delete data; +} + +static char * +MimeCMS_generate (void *crypto_closure) +{ + return nullptr; +} + diff --git a/mailnews/mime/src/mimecms.h b/mailnews/mime/src/mimecms.h new file mode 100644 index 000000000..fa08b28b8 --- /dev/null +++ b/mailnews/mime/src/mimecms.h @@ -0,0 +1,36 @@ +/* -*- 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/. */ + +#ifndef _MIMECMS_H_ +#define _MIMECMS_H_ + +#include "mimecryp.h" + +class nsICMSMessage; + +/* The MimeEncryptedCMS class implements a type of MIME object where the + object is passed through a CMS decryption engine to decrypt or verify + signatures. That module returns a new MIME object, which is then presented + to the user. See mimecryp.h for details of the general mechanism on which + this is built. + */ + +typedef struct MimeEncryptedCMSClass MimeEncryptedCMSClass; +typedef struct MimeEncryptedCMS MimeEncryptedCMS; + +struct MimeEncryptedCMSClass { + MimeEncryptedClass encrypted; +}; + +extern MimeEncryptedCMSClass mimeEncryptedCMSClass; + +struct MimeEncryptedCMS { + MimeEncrypted encrypted; /* superclass variables */ +}; + +#define MimeEncryptedCMSClassInitializer(ITYPE,CSUPER) \ + { MimeEncryptedClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEPKCS_H_ */ diff --git a/mailnews/mime/src/mimecom.cpp b/mailnews/mime/src/mimecom.cpp new file mode 100644 index 000000000..384542742 --- /dev/null +++ b/mailnews/mime/src/mimecom.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mimei.h" +#include "mimeobj.h" /* MimeObject (abstract) */ +#include "mimecont.h" /* |--- MimeContainer (abstract) */ +#include "mimemult.h" /* | |--- MimeMultipart (abstract) */ +#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/ +#include "mimetext.h" /* | |--- MimeInlineText (abstract) */ +#include "mimecryp.h" +#include "mimecth.h" + +/* + * These calls are necessary to expose the object class hierarchy + * to externally developed content type handlers. + */ +extern "C" void * +XPCOM_GetmimeInlineTextClass(void) +{ + return (void *) &mimeInlineTextClass; +} + +extern "C" void * +XPCOM_GetmimeLeafClass(void) +{ + return (void *) &mimeLeafClass; +} + +extern "C" void * +XPCOM_GetmimeObjectClass(void) +{ + return (void *) &mimeObjectClass; +} + +extern "C" void * +XPCOM_GetmimeContainerClass(void) +{ + return (void *) &mimeContainerClass; +} + +extern "C" void * +XPCOM_GetmimeMultipartClass(void) +{ + return (void *) &mimeMultipartClass; +} + +extern "C" void * +XPCOM_GetmimeMultipartSignedClass(void) +{ + return (void *) &mimeMultipartSignedClass; +} + +extern "C" void * +XPCOM_GetmimeEncryptedClass(void) +{ + return (void *) &mimeEncryptedClass; +} + +extern "C" int +XPCOM_MimeObject_write(void *mimeObject, + char *data, + int32_t length, + bool user_visible_p) +{ + return MIME_MimeObject_write((MimeObject *)mimeObject, data, + length, user_visible_p); +} + +extern "C" void * +XPCOM_Mime_create(char *content_type, void* hdrs, void* opts) +{ + return mime_create(content_type, (MimeHeaders *)hdrs, (MimeDisplayOptions *)opts); +} diff --git a/mailnews/mime/src/mimecom.h b/mailnews/mime/src/mimecom.h new file mode 100644 index 000000000..83c654a34 --- /dev/null +++ b/mailnews/mime/src/mimecom.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * XP-COM Bridges for C function calls + */ +#ifndef _MIMECOM_H_ +#define _MIMECOM_H_ + +#include <stdint.h> + +/* + * These functions are exposed by libmime to be used by content type + * handler plugins for processing stream data. + */ +/* + * This is the write call for outputting processed stream data. + */ +extern "C" int XPCOM_MimeObject_write(void *mimeObject, const char *data, + int32_t length, + bool user_visible_p); +/* + * The following group of calls expose the pointers for the object + * system within libmime. + */ +extern "C" void *XPCOM_GetmimeInlineTextClass(void); +extern "C" void *XPCOM_GetmimeLeafClass(void); +extern "C" void *XPCOM_GetmimeObjectClass(void); +extern "C" void *XPCOM_GetmimeContainerClass(void); +extern "C" void *XPCOM_GetmimeMultipartClass(void); +extern "C" void *XPCOM_GetmimeMultipartSignedClass(void); +extern "C" void *XPCOM_GetmimeEncryptedClass(void); + +extern "C" void *XPCOM_Mime_create(char *content_type, void* hdrs, void* opts); + +#endif /* _MIMECOM_H_ */ diff --git a/mailnews/mime/src/mimecont.cpp b/mailnews/mime/src/mimecont.cpp new file mode 100644 index 000000000..c5755a87f --- /dev/null +++ b/mailnews/mime/src/mimecont.cpp @@ -0,0 +1,218 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "prio.h" +#include "mimecont.h" +#include "nsMimeStringResources.h" + +#define MIME_SUPERCLASS mimeObjectClass +MimeDefClass(MimeContainer, MimeContainerClass, + mimeContainerClass, &MIME_SUPERCLASS); + +static int MimeContainer_initialize (MimeObject *); +static void MimeContainer_finalize (MimeObject *); +static int MimeContainer_add_child (MimeObject *, MimeObject *); +static int MimeContainer_parse_eof (MimeObject *, bool); +static int MimeContainer_parse_end (MimeObject *, bool); +static bool MimeContainer_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeContainer_debug_print (MimeObject *, PRFileDesc *, int32_t depth); +#endif + +static int +MimeContainerClassInitialize(MimeContainerClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) &clazz->object; + + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeContainer_initialize; + oclass->finalize = MimeContainer_finalize; + oclass->parse_eof = MimeContainer_parse_eof; + oclass->parse_end = MimeContainer_parse_end; + oclass->displayable_inline_p = MimeContainer_displayable_inline_p; + clazz->add_child = MimeContainer_add_child; + +#if defined(DEBUG) && defined(XP_UNIX) + oclass->debug_print = MimeContainer_debug_print; +#endif + return 0; +} + + +static int +MimeContainer_initialize (MimeObject *object) +{ + /* This is an abstract class; it shouldn't be directly instantiated. */ + NS_ASSERTION(object->clazz != (MimeObjectClass *) &mimeContainerClass, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeContainer_finalize (MimeObject *object) +{ + MimeContainer *cont = (MimeContainer *) object; + + /* Do this first so that children have their parse_eof methods called + in forward order (0-N) but are destroyed in backward order (N-0) + */ + if (!object->closed_p) + object->clazz->parse_eof (object, false); + if (!object->parsed_p) + object->clazz->parse_end (object, false); + + if (cont->children) + { + int i; + for (i = cont->nchildren-1; i >= 0; i--) + { + MimeObject *kid = cont->children[i]; + if (kid) + mime_free(kid); + cont->children[i] = 0; + } + PR_FREEIF(cont->children); + cont->nchildren = 0; + } + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeContainer_parse_eof (MimeObject *object, bool abort_p) +{ + MimeContainer *cont = (MimeContainer *) object; + int status; + + /* We must run all of this object's parent methods first, to get all the + data flushed down its stream, so that the children's parse_eof methods + can access it. We do not access *this* object again after doing this, + only its children. + */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(object, abort_p); + if (status < 0) return status; + + if (cont->children) + { + int i; + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *kid = cont->children[i]; + if (kid && !kid->closed_p) + { + int lstatus = kid->clazz->parse_eof(kid, abort_p); + if (lstatus < 0) return lstatus; + } + } + } + return 0; +} + +static int +MimeContainer_parse_end (MimeObject *object, bool abort_p) +{ + MimeContainer *cont = (MimeContainer *) object; + int status; + + /* We must run all of this object's parent methods first, to get all the + data flushed down its stream, so that the children's parse_eof methods + can access it. We do not access *this* object again after doing this, + only its children. + */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end(object, abort_p); + if (status < 0) return status; + + if (cont->children) + { + int i; + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *kid = cont->children[i]; + if (kid && !kid->parsed_p) + { + int lstatus = kid->clazz->parse_end(kid, abort_p); + if (lstatus < 0) return lstatus; + } + } + } + return 0; +} + +static int +MimeContainer_add_child (MimeObject *parent, MimeObject *child) +{ + MimeContainer *cont = (MimeContainer *) parent; + MimeObject **old_kids, **new_kids; + + NS_ASSERTION(parent && child, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!parent || !child) return -1; + + old_kids = cont->children; + new_kids = (MimeObject **)PR_MALLOC(sizeof(MimeObject *) * (cont->nchildren + 1)); + if (!new_kids) return MIME_OUT_OF_MEMORY; + + if (cont->nchildren > 0) + memcpy(new_kids, old_kids, sizeof(MimeObject *) * cont->nchildren); + new_kids[cont->nchildren] = child; + PR_Free(old_kids); + cont->children = new_kids; + cont->nchildren++; + + child->parent = parent; + + /* Copy this object's options into the child. */ + child->options = parent->options; + + return 0; +} + +static bool +MimeContainer_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs) +{ + return true; +} + + +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeContainer_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + MimeContainer *cont = (MimeContainer *) obj; + int i; + char *addr = mime_part_address(obj); + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); + /* + PR_Write(stream, "<%s %s (%d kid%s) 0x%08X>\n", + obj->clazz->class_name, + addr ? addr : "???", + cont->nchildren, (cont->nchildren == 1 ? "" : "s"), + (uint32_t) cont); + */ + PR_FREEIF(addr); + +/* + if (cont->nchildren > 0) + fprintf(stream, "\n"); + */ + + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *kid = cont->children[i]; + int status = kid->clazz->debug_print (kid, stream, depth+1); + if (status < 0) return status; + } + +/* + if (cont->nchildren > 0) + fprintf(stream, "\n"); + */ + + return 0; +} +#endif diff --git a/mailnews/mime/src/mimecont.h b/mailnews/mime/src/mimecont.h new file mode 100644 index 000000000..8f8edc566 --- /dev/null +++ b/mailnews/mime/src/mimecont.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMECONT_H_ +#define _MIMECONT_H_ + +#include "mimeobj.h" + +/* MimeContainer is the class for the objects representing all MIME + types which can contain other MIME objects within them. In addition + to the methods inherited from MimeObject, it provides one method: + + int add_child (MimeObject *parent, MimeObject *child) + + Given a parent (a subclass of MimeContainer) this method adds the + child (any MIME object) to the parent's list of children. + + The MimeContainer `finalize' method will finalize the children as well. + */ + +typedef struct MimeContainerClass MimeContainerClass; +typedef struct MimeContainer MimeContainer; + +struct MimeContainerClass { + MimeObjectClass object; + int (*add_child) (MimeObject *parent, MimeObject *child); +}; + +extern MimeContainerClass mimeContainerClass; + +struct MimeContainer { + MimeObject object; /* superclass variables */ + + MimeObject **children; /* list of contained objects */ + int32_t nchildren; /* how many */ +}; + +#define MimeContainerClassInitializer(ITYPE,CSUPER) \ + { MimeObjectClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMECONT_H_ */ diff --git a/mailnews/mime/src/mimecryp.cpp b/mailnews/mime/src/mimecryp.cpp new file mode 100644 index 000000000..fd4f8f8ae --- /dev/null +++ b/mailnews/mime/src/mimecryp.cpp @@ -0,0 +1,562 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimecryp.h" +#include "mimemoz2.h" +#include "nsMimeTypes.h" +#include "nsMimeStringResources.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "mimemult.h" +#include "nsIMimeConverter.h" // for MimeConverterOutputCallback + + +#define MIME_SUPERCLASS mimeContainerClass +MimeDefClass(MimeEncrypted, MimeEncryptedClass, mimeEncryptedClass, + &MIME_SUPERCLASS); + +static int MimeEncrypted_initialize (MimeObject *); +static void MimeEncrypted_finalize (MimeObject *); +static int MimeEncrypted_parse_begin (MimeObject *); +static int MimeEncrypted_parse_buffer (const char *, int32_t, MimeObject *); +static int MimeEncrypted_parse_line (const char *, int32_t, MimeObject *); +static int MimeEncrypted_parse_decoded_buffer (const char *, int32_t, MimeObject *); +static int MimeEncrypted_parse_eof (MimeObject *, bool); +static int MimeEncrypted_parse_end (MimeObject *, bool); +static int MimeEncrypted_add_child (MimeObject *, MimeObject *); + +static int MimeHandleDecryptedOutput (const char *, int32_t, void *); +static int MimeHandleDecryptedOutputLine (char *, int32_t, MimeObject *); +static int MimeEncrypted_close_headers (MimeObject *); +static int MimeEncrypted_emit_buffered_child(MimeObject *); + +static int +MimeEncryptedClassInitialize(MimeEncryptedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeContainerClass *cclass = (MimeContainerClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); + oclass->initialize = MimeEncrypted_initialize; + oclass->finalize = MimeEncrypted_finalize; + oclass->parse_begin = MimeEncrypted_parse_begin; + oclass->parse_buffer = MimeEncrypted_parse_buffer; + oclass->parse_line = MimeEncrypted_parse_line; + oclass->parse_eof = MimeEncrypted_parse_eof; + oclass->parse_end = MimeEncrypted_parse_end; + + cclass->add_child = MimeEncrypted_add_child; + + clazz->parse_decoded_buffer = MimeEncrypted_parse_decoded_buffer; + + return 0; +} + + +static int +MimeEncrypted_initialize (MimeObject *obj) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + + +static int +MimeEncrypted_parse_begin (MimeObject *obj) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0; + + if (enc->crypto_closure) + return -1; + + enc->crypto_closure = (((MimeEncryptedClass *) obj->clazz)->crypto_init) (obj, MimeHandleDecryptedOutput, obj); + if (!enc->crypto_closure) + return -1; + + + /* (Mostly duplicated from MimeLeaf, see comments in mimecryp.h.) + Initialize a decoder if necessary. + */ + if (!obj->encoding) + ; + else if (!PL_strcasecmp(obj->encoding, ENCODING_BASE64)) + fn = &MimeB64DecoderInit; + else if (!PL_strcasecmp(obj->encoding, ENCODING_QUOTED_PRINTABLE)) + { + enc->decoder_data = + MimeQPDecoderInit (/* The (MimeConverterOutputCallback) cast is to turn the + `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) + ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer), + obj); + + if (!enc->decoder_data) + return MIME_OUT_OF_MEMORY; + } + else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4)) + fn = &MimeUUDecoderInit; + else if (!PL_strcasecmp(obj->encoding, ENCODING_YENCODE)) + fn = &MimeYDecoderInit; + if (fn) + { + enc->decoder_data = + fn (/* The (MimeConverterOutputCallback) cast is to turn the `void' + argument into `MimeObject'. */ + ((MimeConverterOutputCallback) + ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer), + obj); + + if (!enc->decoder_data) + return MIME_OUT_OF_MEMORY; + } + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); +} + + +static int +MimeEncrypted_parse_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + /* (Duplicated from MimeLeaf, see comments in mimecryp.h.) + */ + + MimeEncrypted *enc = (MimeEncrypted *) obj; + + if (obj->closed_p) return -1; + + /* Don't consult output_p here, since at this point we're behaving as a + simple container object -- the output_p decision should be made by + the child of this object. */ + + if (enc->decoder_data) + return MimeDecoderWrite (enc->decoder_data, buffer, size, nullptr); + else + return ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer (buffer, + size, + obj); +} + + +static int +MimeEncrypted_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("This method shouldn't ever be called."); + return -1; +} + +static int +MimeEncrypted_parse_decoded_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + return + ((MimeEncryptedClass *) obj->clazz)->crypto_write (buffer, size, + enc->crypto_closure); +} + + +static int +MimeEncrypted_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + MimeEncrypted *enc = (MimeEncrypted *) obj; + + if (obj->closed_p) return 0; + NS_ASSERTION(!obj->parsed_p, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); + + /* (Duplicated from MimeLeaf, see comments in mimecryp.h.) + Close off the decoder, to cause it to give up any buffered data that + it is still holding. + */ + if (enc->decoder_data) + { + int status = MimeDecoderDestroy(enc->decoder_data, false); + enc->decoder_data = 0; + if (status < 0) return status; + } + + + /* If there is still data in the ibuffer, that means that the last + *decrypted* line of this part didn't end in a newline; so push it out + anyway (this means that the parse_line method will be called with a + string with no trailing newline, which isn't the usual case.) */ + if (!abort_p && + obj->ibuffer_fp > 0) + { + int status = MimeHandleDecryptedOutputLine (obj->ibuffer, + obj->ibuffer_fp, obj); + obj->ibuffer_fp = 0; + if (status < 0) + { + obj->closed_p = true; + return status; + } + } + + + /* Now run the superclass's parse_eof, which (because we've already taken + care of ibuffer in a way appropriate for this class, immediately above) + will ony set closed_p to true. + */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p); + if (status < 0) return status; + + + /* Now close off the underlying crypto module. At this point, the crypto + module has all of the input. (DecoderDestroy called parse_decoded_buffer + which called crypto_write, with the last of the data.) + */ + if (enc->crypto_closure) + { + status = + ((MimeEncryptedClass *) obj->clazz)->crypto_eof (enc->crypto_closure, + abort_p); + if (status < 0 && !abort_p) + return status; + } + + /* Now we have the entire child part in the part buffer. + We are now able to verify its signature, emit a blurb, and then + emit the part. + */ + if (abort_p) + return 0; + else + return MimeEncrypted_emit_buffered_child (obj); +} + + +static int +MimeEncrypted_parse_end (MimeObject *obj, bool abort_p) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end (obj, abort_p); +} + + +static void +MimeEncrypted_cleanup (MimeObject *obj, bool finalizing_p) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + + if (enc->part_buffer) + { + MimePartBufferDestroy(enc->part_buffer); + enc->part_buffer = 0; + } + + if (finalizing_p && enc->crypto_closure) + { + /* Don't free these until this object is really going away -- keep them + around for the lifetime of the MIME object, so that we can get at the + security info of sub-parts of the currently-displayed message. */ + ((MimeEncryptedClass *) obj->clazz)->crypto_free (enc->crypto_closure); + enc->crypto_closure = 0; + } + + /* (Duplicated from MimeLeaf, see comments in mimecryp.h.) + Free the decoder data, if it's still around. */ + if (enc->decoder_data) + { + MimeDecoderDestroy(enc->decoder_data, true); + enc->decoder_data = 0; + } + + if (enc->hdrs) + { + MimeHeaders_free(enc->hdrs); + enc->hdrs = 0; + } +} + + +static void +MimeEncrypted_finalize (MimeObject *obj) +{ + MimeEncrypted_cleanup (obj, true); + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj); +} + + +static int +MimeHandleDecryptedOutput (const char *buf, int32_t buf_size, + void *output_closure) +{ + /* This method is invoked by the underlying decryption module. + The module is assumed to return a MIME object, and its associated + headers. For example, if a text/plain document was encrypted, + the encryption module would return the following data: + + Content-Type: text/plain + + Decrypted text goes here. + + This function will then extract a header block (up to the first + blank line, as usual) and will then handle the included data as + appropriate. + */ + MimeObject *obj = (MimeObject *) output_closure; + + /* Is it truly safe to use ibuffer here? I think so... */ + return mime_LineBuffer (buf, buf_size, + &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp, + true, + ((int (*) (char *, int32_t, void *)) + /* This cast is to turn void into MimeObject */ + MimeHandleDecryptedOutputLine), + obj); +} + +static int +MimeHandleDecryptedOutputLine (char *line, int32_t length, MimeObject *obj) +{ + /* Largely the same as MimeMessage_parse_line (the other MIME container + type which contains exactly one child.) + */ + MimeEncrypted *enc = (MimeEncrypted *) obj; + int status = 0; + + if (!line || !*line) return -1; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + obj->options->output_fn) + return MimeObject_write(obj, line, length, true); + + /* If we already have a child object in the buffer, then we're done parsing + headers, and all subsequent lines get passed to the inferior object + without further processing by us. (Our parent will stop feeding us + lines when this MimeMessage part is out of data.) + */ + if (enc->part_buffer) + return MimePartBufferWrite (enc->part_buffer, line, length); + + /* Otherwise we don't yet have a child object in the buffer, which means + we're not done parsing our headers yet. + */ + if (!enc->hdrs) + { + enc->hdrs = MimeHeaders_new(); + if (!enc->hdrs) return MIME_OUT_OF_MEMORY; + } + + status = MimeHeaders_parse_line(line, length, enc->hdrs); + if (status < 0) return status; + + /* If this line is blank, we're now done parsing headers, and should + examine our content-type to create our "body" part. + */ + if (*line == '\r' || *line == '\n') + { + status = MimeEncrypted_close_headers(obj); + if (status < 0) return status; + } + + return 0; +} + +static int +MimeEncrypted_close_headers (MimeObject *obj) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + + // Notify the JS Mime Emitter that this was an encrypted part that it should + // hopefully not analyze for indexing... + if (obj->options->notify_nested_bodies) + mimeEmitterAddHeaderField(obj->options, "x-jsemitter-encrypted", "1"); + + if (enc->part_buffer) return -1; + enc->part_buffer = MimePartBufferCreate(); + if (!enc->part_buffer) + return MIME_OUT_OF_MEMORY; + + return 0; +} + + +static int +MimeEncrypted_add_child (MimeObject *parent, MimeObject *child) +{ + MimeContainer *cont = (MimeContainer *) parent; + if (!parent || !child) return -1; + + /* Encryption containers can only have one child. */ + if (cont->nchildren != 0) return -1; + + return ((MimeContainerClass*)&MIME_SUPERCLASS)->add_child (parent, child); +} + + +static int +MimeEncrypted_emit_buffered_child(MimeObject *obj) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + int status = 0; + char *ct = 0; + MimeObject *body; + + NS_ASSERTION(enc->crypto_closure, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); + + /* Emit some HTML saying whether the signature was cool. + But don't emit anything if in FO_QUOTE_MESSAGE mode. + + Also, don't emit anything if the enclosed object is itself a signed + object -- in the case of an encrypted object which contains a signed + object, we only emit the HTML once (since the normal way of encrypting + and signing is to nest the signature inside the crypto envelope.) + */ + if (enc->crypto_closure && + obj->options && + obj->options->headers != MimeHeadersCitation && + obj->options->write_html_p && + obj->options->output_fn) + { + char *html; + + /* Now that we have written out the crypto stamp, the outermost header + block is well and truly closed. If this is in fact the outermost + message, then run the post_header_html_fn now. + */ + if (obj->options && + obj->options->state && + obj->options->generate_post_header_html_fn && + !obj->options->state->post_header_html_run_p) + { + MimeHeaders *outer_headers = nullptr; + MimeObject *p; + for (p = obj; p->parent; p = p->parent) + outer_headers = p->headers; + NS_ASSERTION(obj->options->state->first_data_written_p, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); + char *html = obj->options->generate_post_header_html_fn( + NULL, + obj->options->html_closure, + outer_headers); + obj->options->state->post_header_html_run_p = true; + if (html) + { + status = MimeObject_write(obj, html, strlen(html), false); + PR_FREEIF(html); + if (status < 0) return status; + } + } + } + else if (enc->crypto_closure && + obj->options && + obj->options->decrypt_p) + { + /* Do this just to cause `mime_set_crypto_stamp' to be called, and to + cause the various `decode_error' and `verify_error' slots to be set: + we don't actually use the returned HTML, because we're not emitting + HTML. It's maybe not such a good thing that the determination of + whether it was encrypted or not is tied up with generating HTML, + but oh well. */ + char *html = (((MimeEncryptedClass *) obj->clazz)->crypto_generate_html + (enc->crypto_closure)); + PR_FREEIF(html); + } + + if (enc->hdrs) + ct = MimeHeaders_get (enc->hdrs, HEADER_CONTENT_TYPE, true, false); + body = mime_create((ct ? ct : TEXT_PLAIN), enc->hdrs, obj->options); + +#ifdef MIME_DRAFTS + if (obj->options->decompose_file_p) { + if (mime_typep (body, (MimeObjectClass*) &mimeMultipartClass) ) + obj->options->is_multipart_msg = true; + else if (obj->options->decompose_file_init_fn) + obj->options->decompose_file_init_fn(obj->options->stream_closure, + enc->hdrs); + } +#endif /* MIME_DRAFTS */ + + PR_FREEIF(ct); + + if (!body) return MIME_OUT_OF_MEMORY; + status = ((MimeContainerClass *) obj->clazz)->add_child (obj, body); + if (status < 0) + { + mime_free(body); + return status; + } + + /* Now that we've added this new object to our list of children, + start its parser going. */ + status = body->clazz->parse_begin(body); + if (status < 0) return status; + + /* If this object (or the parent) is being output, then by definition + the child is as well. (This is only necessary because this is such + a funny sort of container...) + */ + if (!body->output_p && + (obj->output_p || + (obj->parent && obj->parent->output_p))) + body->output_p = true; + + /* If the body is being written raw (not as HTML) then make sure to + write its headers as well. */ + if (body->output_p && obj->output_p && !obj->options->write_html_p) + { + status = MimeObject_write(body, "", 0, false); /* initialize */ + if (status < 0) return status; + status = MimeHeaders_write_raw_headers(body->headers, obj->options, + false); + if (status < 0) return status; + } + + if (enc->part_buffer) /* part_buffer is 0 for 0-length encrypted data. */ + { +#ifdef MIME_DRAFTS + if (obj->options->decompose_file_p && !obj->options->is_multipart_msg) + { + status = MimePartBufferRead(enc->part_buffer, + /* The (MimeConverterOutputCallback) cast is to turn the `void' + argument into `MimeObject'. */ + ((MimeConverterOutputCallback) + obj->options->decompose_file_output_fn), + obj->options->stream_closure); + } + else + { +#endif /* MIME_DRAFTS */ + + status = MimePartBufferRead(enc->part_buffer, + /* The (MimeConverterOutputCallback) cast is to turn the `void' + argument into `MimeObject'. */ + ((MimeConverterOutputCallback) body->clazz->parse_buffer), + body); +#ifdef MIME_DRAFTS + } +#endif /* MIME_DRAFTS */ + } + if (status < 0) return status; + + /* The child has been fully processed. Close it off. + */ + status = body->clazz->parse_eof(body, false); + if (status < 0) return status; + + status = body->clazz->parse_end(body, false); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if (obj->options->decompose_file_p && !obj->options->is_multipart_msg) + obj->options->decompose_file_close_fn(obj->options->stream_closure); +#endif /* MIME_DRAFTS */ + + /* Put out a separator after every encrypted object. */ + status = MimeObject_write_separator(obj); + if (status < 0) return status; + + MimeEncrypted_cleanup (obj, false); + + return 0; +} diff --git a/mailnews/mime/src/mimecryp.h b/mailnews/mime/src/mimecryp.h new file mode 100644 index 000000000..c74770b18 --- /dev/null +++ b/mailnews/mime/src/mimecryp.h @@ -0,0 +1,140 @@ +/* -*- 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/. */ + +#ifndef _MIMECRYP_H_ +#define _MIMECRYP_H_ + +#include "mimecont.h" +// #include "mimeenc.h" +#include "modmimee.h" +#include "mimepbuf.h" + +/* The MimeEncrypted class implements a type of MIME object where the object + is passed to some other routine, which then returns a new MIME object. + This is the basis of a decryption module. + + Oddly, this class behaves both as a container and as a leaf: it acts as a + container in that it parses out data in order to eventually present a + contained object; however, it acts as a leaf in that this container may + itself have a Content-Transfer-Encoding applied to its body. This violates + the cardinal rule of MIME containers, which is that encodings don't nest, + and therefore containers can't have encodings. But, the fact that the + S/MIME spec doesn't follow the groundwork laid down by previous MIME specs + isn't something we can do anything about at this point... + + Therefore, this class duplicates some of the work done by the MimeLeaf + class, to meet its dual goals of container-hood and leaf-hood. (We could + alternately have made this class be a subclass of leaf, and had it duplicate + the effort of MimeContainer, but that seemed like the harder approach.) + + The MimeEncrypted class provides the following methods: + + void *crypto_init(MimeObject *obj, + int (*output_fn) (const char *data, int32 data_size, + void *output_closure), + void *output_closure) + + This is called with the MimeObject representing the encrypted data. + The obj->headers should be used to initialize the decryption engine. + NULL indicates failure; otherwise, an opaque closure object should + be returned. + + output_fn is what the decryption module should use to write a new MIME + object (the decrypted data.) output_closure should be passed along to + every call to the output routine. + + The data sent to output_fn should begin with valid MIME headers indicating + the type of the data. For example, if decryption resulted in a text + document, the data fed through to the output_fn might minimally look like + + Content-Type: text/plain + + This is the decrypted data. + It is only two lines long. + + Of course, the data may be of any MIME type, including multipart/mixed. + Any returned MIME object will be recursively processed and presented + appropriately. (This also imples that encrypted objects may nest, and + thus that the underlying decryption module must be reentrant.) + + int crypto_write (const char *data, int32 data_size, void *crypto_closure) + + This is called with the raw encrypted data. This data might not come + in line-based chunks: if there was a Content-Transfer-Encoding applied + to the data (base64 or quoted-printable) then it will have been decoded + first (handing binary data to the filter_fn.) `crypto_closure' is the + object that `crypto_init' returned. This may return negative on error. + + int crypto_eof (void *crypto_closure, bool abort_p) + + This is called when no more data remains. It may call `output_fn' again + to flush out any buffered data. If `abort_p' is true, then it may choose + to discard any data rather than processing it, as we're terminating + abnormally. + + char * crypto_generate_html (void *crypto_closure) + + This is called after `crypto_eof' but before `crypto_free'. The crypto + module should return a newly-allocated string of HTML code which + explains the status of the decryption to the user (whether the signature + checked out, etc.) + + void crypto_free (void *crypto_closure) + + This will be called when we're all done, after `crypto_eof' and + `crypto_emit_html'. It is intended to free any data represented + by the crypto_closure. output_fn may not be called. + + + int (*parse_decoded_buffer) (const char *buf, int32 size, MimeObject *obj) + + This method, of the same name as one in MimeLeaf, is a part of the + afforementioned leaf/container hybridization. This method is invoked + with the content-transfer-decoded body of this part (without line + buffering.) The default behavior of this method is to simply invoke + `crypto_write' on the data with which it is called. It's unlikely that + a subclass will need to specialize this. + */ + +typedef struct MimeEncryptedClass MimeEncryptedClass; +typedef struct MimeEncrypted MimeEncrypted; + +struct MimeEncryptedClass { + MimeContainerClass container; + + /* Duplicated from MimeLeaf, see comments above. + This is the callback that is handed to the decoder. */ + int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj); + + + /* Callbacks used by decryption module. */ + void * (*crypto_init) (MimeObject *obj, + int (*output_fn) (const char *data, int32_t data_size, + void *output_closure), + void *output_closure); + int (*crypto_write) (const char *data, int32_t data_size, + void *crypto_closure); + int (*crypto_eof) (void *crypto_closure, bool abort_p); + char * (*crypto_generate_html) (void *crypto_closure); + void (*crypto_free) (void *crypto_closure); +}; + +extern MimeEncryptedClass mimeEncryptedClass; + +struct MimeEncrypted { + MimeContainer container; /* superclass variables */ + void *crypto_closure; /* Opaque data used by decryption module. */ + MimeDecoderData *decoder_data; /* Opaque data for the Transfer-Encoding + decoder. */ + MimeHeaders *hdrs; /* Headers of the enclosed object (including + the type of the *decrypted* data.) */ + MimePartBufferData *part_buffer; /* The data of the decrypted enclosed + object (see mimepbuf.h) */ +}; + +#define MimeEncryptedClassInitializer(ITYPE,CSUPER) \ + { MimeContainerClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMECRYP_H_ */ diff --git a/mailnews/mime/src/mimecth.cpp b/mailnews/mime/src/mimecth.cpp new file mode 100644 index 000000000..1cfaba82c --- /dev/null +++ b/mailnews/mime/src/mimecth.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mimecth.h" + +/* + * These calls are necessary to expose the object class hierarchy + * to externally developed content type handlers. + */ +MimeInlineTextClass * +MIME_GetmimeInlineTextClass(void) +{ + return &mimeInlineTextClass; +} + +MimeLeafClass * +MIME_GetmimeLeafClass(void) +{ + return &mimeLeafClass; +} + +MimeObjectClass * +MIME_GetmimeObjectClass(void) +{ + return &mimeObjectClass; +} + +MimeContainerClass * +MIME_GetmimeContainerClass(void) +{ + return &mimeContainerClass; +} + +MimeMultipartClass * +MIME_GetmimeMultipartClass(void) +{ + return &mimeMultipartClass; +} + +MimeMultipartSignedClass * +MIME_GetmimeMultipartSignedClass(void) +{ + return &mimeMultipartSignedClass; +} + +MimeEncryptedClass * +MIME_GetmimeEncryptedClass(void) +{ + return &mimeEncryptedClass; +} diff --git a/mailnews/mime/src/mimecth.h b/mailnews/mime/src/mimecth.h new file mode 100644 index 000000000..1f5d89663 --- /dev/null +++ b/mailnews/mime/src/mimecth.h @@ -0,0 +1,135 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This is the definitions for the Content Type Handler plugins for + * libmime. This will allow developers the dynamically add the ability + * for libmime to render new content types in the MHTML rendering of + * HTML messages. + */ + +#ifndef _MIMECTH_H_ +#define _MIMECTH_H_ + +#include "mimei.h" +#include "mimeobj.h" /* MimeObject (abstract) */ +#include "mimecont.h" /* |--- MimeContainer (abstract) */ +#include "mimemult.h" /* | |--- MimeMultipart (abstract) */ +#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/ +#include "mimetext.h" /* | |--- MimeInlineText (abstract) */ +#include "mimecryp.h" + +/* + This header exposes functions that are necessary to access the + object hierarchy for the mime chart. The class hierarchy is: + + MimeObject (abstract) + | + |--- MimeContainer (abstract) + | | + | |--- MimeMultipart (abstract) + | | | + | | |--- MimeMultipartMixed + | | | + | | |--- MimeMultipartDigest + | | | + | | |--- MimeMultipartParallel + | | | + | | |--- MimeMultipartAlternative + | | | + | | |--- MimeMultipartRelated + | | | + | | |--- MimeMultipartAppleDouble + | | | + | | |--- MimeSunAttachment + | | | + | | |--- MimeMultipartSigned (abstract) + | | | + | | |--- MimeMultipartSigned + | | + | |--- MimeXlateed (abstract) + | | | + | | |--- MimeXlateed + | | + | |--- MimeMessage + | | + | |--- MimeUntypedText + | + |--- MimeLeaf (abstract) + | | + | |--- MimeInlineText (abstract) + | | | + | | |--- MimeInlineTextPlain + | | | + | | |--- MimeInlineTextHTML + | | | + | | |--- MimeInlineTextRichtext + | | | | + | | | |--- MimeInlineTextEnriched + | | | + | | |--- MimeInlineTextVCard + | | + | |--- MimeInlineImage + | | + | |--- MimeExternalObject + | + |--- MimeExternalBody + */ + +#include "nsIMimeContentTypeHandler.h" + +/* + * These functions are exposed by libmime to be used by content type + * handler plugins for processing stream data. + */ +/* + * This is the write call for outputting processed stream data. + */ +extern int MIME_MimeObject_write(MimeObject *, + const char *data, + int32_t length, + bool user_visible_p); +/* + * The following group of calls expose the pointers for the object + * system within libmime. + */ +extern MimeInlineTextClass *MIME_GetmimeInlineTextClass(void); +extern MimeLeafClass *MIME_GetmimeLeafClass(void); +extern MimeObjectClass *MIME_GetmimeObjectClass(void); +extern MimeContainerClass *MIME_GetmimeContainerClass(void); +extern MimeMultipartClass *MIME_GetmimeMultipartClass(void); +extern MimeMultipartSignedClass *MIME_GetmimeMultipartSignedClass(void); +extern MimeEncryptedClass *MIME_GetmimeEncryptedClass(void); + +/* + * These are the functions that need to be implemented by the + * content type handler plugin. They will be called by by libmime + * when the module is loaded at runtime. + */ + +/* + * MIME_GetContentType() is called by libmime to identify the content + * type handled by this plugin. + */ +extern "C" +char *MIME_GetContentType(void); + +/* + * This will create the MimeObjectClass object to be used by the libmime + * object system. + */ +extern "C" +MimeObjectClass *MIME_CreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct); + +/* + * Typedefs for libmime to use when locating and calling the above + * defined functions. + */ +typedef char * (*mime_get_ct_fn_type)(void); +typedef MimeObjectClass * (*mime_create_class_fn_type) + (const char *, contentTypeHandlerInitStruct *); + +#endif /* _MIMECTH_H_ */ diff --git a/mailnews/mime/src/mimedrft.cpp b/mailnews/mime/src/mimedrft.cpp new file mode 100644 index 000000000..90ca027d8 --- /dev/null +++ b/mailnews/mime/src/mimedrft.cpp @@ -0,0 +1,2084 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +#include "nsCOMPtr.h" +#include "modmimee.h" +#include "mimeobj.h" +#include "modlmime.h" +#include "mimei.h" +#include "mimebuf.h" +#include "mimemoz2.h" +#include "mimemsg.h" +#include "nsMimeTypes.h" +#include <ctype.h> + +#include "prmem.h" +#include "plstr.h" +#include "prprf.h" +#include "prio.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "msgCore.h" +#include "nsIMsgSend.h" +#include "nsMimeStringResources.h" +#include "nsIIOService.h" +#include "nsNetUtil.h" +#include "comi18n.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgCompFields.h" +#include "nsMsgCompCID.h" +#include "nsIMsgComposeService.h" +#include "nsMsgAttachmentData.h" +#include "nsMsgI18N.h" +#include "nsNativeCharsetUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsIMimeConverter.h" // for MimeConverterOutputCallback +#include "mozilla/mailnews/MimeHeaderParser.h" + +using namespace mozilla::mailnews; + +// +// Header strings... +// +#define HEADER_NNTP_POSTING_HOST "NNTP-Posting-Host" +#define MIME_HEADER_TABLE "<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0 class=\"moz-email-headers-table\">" +#define HEADER_START_JUNK "<TR><TH VALIGN=BASELINE ALIGN=RIGHT NOWRAP>" +#define HEADER_MIDDLE_JUNK ": </TH><TD>" +#define HEADER_END_JUNK "</TD></TR>" + +// +// Forward declarations... +// +extern "C" char *MIME_StripContinuations(char *original); +int mime_decompose_file_init_fn(void *stream_closure, MimeHeaders *headers); +int mime_decompose_file_output_fn(const char *buf, int32_t size, void *stream_closure); +int mime_decompose_file_close_fn(void *stream_closure); +extern int MimeHeaders_build_heads_list(MimeHeaders *hdrs); + +// CID's +static NS_DEFINE_CID(kCMsgComposeServiceCID, NS_MSGCOMPOSESERVICE_CID); + +mime_draft_data::mime_draft_data() : url_name(nullptr), format_out(0), + stream(nullptr), obj(nullptr), options(nullptr), headers(nullptr), + messageBody(nullptr), curAttachment(nullptr), + decoder_data(nullptr), mailcharset(nullptr), forwardInline(false), + forwardInlineFilter(false), overrideComposeFormat(false), + originalMsgURI(nullptr) +{ +} +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// +// THIS SHOULD ALL MOVE TO ANOTHER FILE AFTER LANDING! +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +// safe filename for all OSes +#define SAFE_TMP_FILENAME "nsmime.tmp" + +// +// Create a file for the a unique temp file +// on the local machine. Caller must free memory +// +nsresult +nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile) +{ + if (!tFileName || !*tFileName) + tFileName = SAFE_TMP_FILENAME; + + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + tFileName, + tFile); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = (*tFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) + NS_RELEASE(*tFile); + + return rv; +} + + +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// +// END OF - THIS SHOULD ALL MOVE TO ANOTHER FILE AFTER LANDING! +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +typedef enum { + nsMsg_RETURN_RECEIPT_BOOL_HEADER_MASK = 0, + nsMsg_ENCRYPTED_BOOL_HEADER_MASK, + nsMsg_SIGNED_BOOL_HEADER_MASK, + nsMsg_UUENCODE_BINARY_BOOL_HEADER_MASK, + nsMsg_ATTACH_VCARD_BOOL_HEADER_MASK, + nsMsg_LAST_BOOL_HEADER_MASK // last boolean header mask; must be the last one + // DON'T remove. +} nsMsgBoolHeaderSet; + +#ifdef NS_DEBUG +extern "C" void +mime_dump_attachments(nsMsgAttachmentData *attachData) +{ + int32_t i = 0; + class nsMsgAttachmentData *tmp = attachData; + + while (tmp && tmp->m_url) + { + printf("Real Name : %s\n", tmp->m_realName.get()); + + if (tmp->m_url) + { + ; + printf("URL : %s\n", tmp->m_url->GetSpecOrDefault().get()); + } + + printf("Desired Type : %s\n", tmp->m_desiredType.get()); + printf("Real Type : %s\n", tmp->m_realType.get()); + printf("Real Encoding : %s\n", tmp->m_realEncoding.get()); + printf("Description : %s\n", tmp->m_description.get()); + printf("Mac Type : %s\n", tmp->m_xMacType.get()); + printf("Mac Creator : %s\n", tmp->m_xMacCreator.get()); + printf("Size in bytes : %d\n", tmp->m_size); + i++; + tmp++; + } +} +#endif + +nsresult CreateComposeParams(nsCOMPtr<nsIMsgComposeParams> &pMsgComposeParams, + nsIMsgCompFields * compFields, + nsMsgAttachmentData *attachmentList, + MSG_ComposeType composeType, + MSG_ComposeFormat composeFormat, + nsIMsgIdentity * identity, + const char *originalMsgURI, + nsIMsgDBHdr *origMsgHdr) +{ +#ifdef NS_DEBUG + mime_dump_attachments(attachmentList); +#endif + + nsresult rv; + nsMsgAttachmentData *curAttachment = attachmentList; + if (curAttachment) + { + nsAutoCString spec; + + while (curAttachment && curAttachment->m_url) + { + rv = curAttachment->m_url->GetSpec(spec); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && attachment) + { + nsAutoString nameStr; + rv = ConvertToUnicode("UTF-8", curAttachment->m_realName.get(), nameStr); + if (NS_FAILED(rv)) + CopyASCIItoUTF16(curAttachment->m_realName, nameStr); + attachment->SetName(nameStr); + attachment->SetUrl(spec); + attachment->SetTemporary(true); + attachment->SetContentType(curAttachment->m_realType.get()); + attachment->SetMacType(curAttachment->m_xMacType.get()); + attachment->SetMacCreator(curAttachment->m_xMacCreator.get()); + attachment->SetSize(curAttachment->m_size); + if (!curAttachment->m_cloudPartInfo.IsEmpty()) + { + nsCString provider; + nsCString cloudUrl; + attachment->SetSendViaCloud(true); + provider.Adopt( + MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(), + "provider", nullptr, nullptr)); + cloudUrl.Adopt( + MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(), + "url", nullptr, nullptr)); + attachment->SetCloudProviderKey(provider); + attachment->SetContentLocation(cloudUrl); + } + compFields->AddAttachment(attachment); + } + } + curAttachment++; + } + } + + MSG_ComposeFormat format = composeFormat; // Format to actually use. + if (identity && composeType == nsIMsgCompType::ForwardInline) + { + bool composeHtml = false; + identity->GetComposeHtml(&composeHtml); + if (composeHtml) + format = (composeFormat == nsIMsgCompFormat::OppositeOfDefault) ? + nsIMsgCompFormat::PlainText : nsIMsgCompFormat::HTML; + else + format = (composeFormat == nsIMsgCompFormat::OppositeOfDefault) ? + nsIMsgCompFormat::HTML : nsIMsgCompFormat::PlainText; + } + + pMsgComposeParams = do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + pMsgComposeParams->SetType(composeType); + pMsgComposeParams->SetFormat(format); + pMsgComposeParams->SetIdentity(identity); + pMsgComposeParams->SetComposeFields(compFields); + if (originalMsgURI) + pMsgComposeParams->SetOriginalMsgURI(originalMsgURI); + if (origMsgHdr) + pMsgComposeParams->SetOrigMsgHdr(origMsgHdr); + return NS_OK; +} + +nsresult +CreateTheComposeWindow(nsIMsgCompFields * compFields, + nsMsgAttachmentData *attachmentList, + MSG_ComposeType composeType, + MSG_ComposeFormat composeFormat, + nsIMsgIdentity * identity, + const char * originalMsgURI, + nsIMsgDBHdr * origMsgHdr + ) +{ + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams; + nsresult rv = CreateComposeParams(pMsgComposeParams, compFields, + attachmentList, + composeType, + composeFormat, + identity, + originalMsgURI, + origMsgHdr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgComposeService> msgComposeService = + do_GetService(kCMsgComposeServiceCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return msgComposeService->OpenComposeWindowWithParams(nullptr /* default chrome */, pMsgComposeParams); +} + +nsresult +ForwardMsgInline(nsIMsgCompFields *compFields, + nsMsgAttachmentData *attachmentList, + MSG_ComposeFormat composeFormat, + nsIMsgIdentity *identity, + const char *originalMsgURI, + nsIMsgDBHdr *origMsgHdr) +{ + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams; + nsresult rv = CreateComposeParams(pMsgComposeParams, compFields, + attachmentList, + nsIMsgCompType::ForwardInline, + composeFormat, + identity, + originalMsgURI, + origMsgHdr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgComposeService> msgComposeService = + do_GetService(kCMsgComposeServiceCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose(do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + /** initialize nsIMsgCompose, Send the message, wait for send completion response **/ + rv = pMsgCompose->Initialize(pMsgComposeParams, nullptr, nullptr); + NS_ENSURE_SUCCESS(rv,rv); + + rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr, nullptr, nullptr); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgFolder> origFolder; + origMsgHdr->GetFolder(getter_AddRefs(origFolder)); + if (origFolder) + origFolder->AddMessageDispositionState( + origMsgHdr, nsIMsgFolder::nsMsgDispositionState_Forwarded); + } + return rv; +} + +nsresult +CreateCompositionFields(const char *from, + const char *reply_to, + const char *to, + const char *cc, + const char *bcc, + const char *fcc, + const char *newsgroups, + const char *followup_to, + const char *organization, + const char *subject, + const char *references, + const char *priority, + const char *newspost_url, + char *charset, + nsIMsgCompFields **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + *_retval = nullptr; + + nsCOMPtr<nsIMsgCompFields> cFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(cFields, NS_ERROR_OUT_OF_MEMORY); + + // Now set all of the passed in stuff... + cFields->SetCharacterSet(!PL_strcasecmp("us-ascii", charset) ? "ISO-8859-1" : charset); + + nsAutoCString val; + nsAutoString outString; + + if (from) { + ConvertRawBytesToUTF16(from, charset, outString); + cFields->SetFrom(outString); + } + + if (subject) { + MIME_DecodeMimeHeader(subject, charset, false, true, val); + cFields->SetSubject(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : subject)); + } + + if (reply_to) { + ConvertRawBytesToUTF16(reply_to, charset, outString); + cFields->SetReplyTo(outString); + } + + if (to) { + ConvertRawBytesToUTF16(to, charset, outString); + cFields->SetTo(outString); + } + + if (cc) { + ConvertRawBytesToUTF16(cc, charset, outString); + cFields->SetCc(outString); + } + + if (bcc) { + ConvertRawBytesToUTF16(bcc, charset, outString); + cFields->SetBcc(outString); + } + + if (fcc) { + MIME_DecodeMimeHeader(fcc, charset, false, true, val); + cFields->SetFcc(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : fcc)); + } + + if (newsgroups) { + // fixme: the newsgroups header had better be decoded using the server-side + // character encoding,but this |charset| might be different from it. + MIME_DecodeMimeHeader(newsgroups, charset, false, true, val); + cFields->SetNewsgroups(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : newsgroups)); + } + + if (followup_to) { + MIME_DecodeMimeHeader(followup_to, charset, false, true, val); + cFields->SetFollowupTo(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : followup_to)); + } + + if (organization) { + MIME_DecodeMimeHeader(organization, charset, false, true, val); + cFields->SetOrganization(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : organization)); + } + + if (references) { + MIME_DecodeMimeHeader(references, charset, false, true, val); + cFields->SetReferences(!val.IsEmpty() ? val.get() : references); + } + + if (priority) { + MIME_DecodeMimeHeader(priority, charset, false, true, val); + nsMsgPriorityValue priorityValue; + NS_MsgGetPriorityFromString(!val.IsEmpty() ? val.get() : priority, priorityValue); + nsAutoCString priorityName; + NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName); + cFields->SetPriority(priorityName.get()); + } + + if (newspost_url) { + MIME_DecodeMimeHeader(newspost_url, charset, false, true, val); + cFields->SetNewspostUrl(!val.IsEmpty() ? val.get() : newspost_url); + } + + *_retval = cFields; + NS_IF_ADDREF(*_retval); + + return rv; +} + +static int +dummy_file_write(char *buf, int32_t size, void *fileHandle) +{ + if (!fileHandle) + return -1; + + nsIOutputStream *tStream = (nsIOutputStream *)fileHandle; + uint32_t bytesWritten; + tStream->Write(buf, size, &bytesWritten); + return (int)bytesWritten; +} + +static int +mime_parse_stream_write(nsMIMESession *stream, const char *buf, int32_t size) +{ + mime_draft_data *mdd = (mime_draft_data *)stream->data_object; + NS_ASSERTION(mdd, "null mime draft data!"); + + if (!mdd || !mdd->obj) + return -1; + + return mdd->obj->clazz->parse_buffer((char *)buf, size, mdd->obj); +} + +static void +mime_free_attachments(nsTArray<nsMsgAttachedFile *> &attachments) +{ + if (attachments.Length() <= 0) + return; + + for (uint32_t i = 0; i < attachments.Length(); i++) + { + if (attachments[i]->m_tmpFile) + { + attachments[i]->m_tmpFile->Remove(false); + attachments[i]->m_tmpFile = nullptr; + } + delete attachments[i]; + } +} + +static nsMsgAttachmentData * +mime_draft_process_attachments(mime_draft_data *mdd) +{ + if (!mdd) + return nullptr; + + nsMsgAttachmentData *attachData = NULL, *tmp = NULL; + nsMsgAttachedFile *tmpFile = NULL; + + //It's possible we must treat the message body as attachment! + bool bodyAsAttachment = false; + if (mdd->messageBody && + !mdd->messageBody->m_type.IsEmpty() && + mdd->messageBody->m_type.Find("text/html", CaseInsensitiveCompare) == -1 && + mdd->messageBody->m_type.Find("text/plain", CaseInsensitiveCompare) == -1 && + !mdd->messageBody->m_type.LowerCaseEqualsLiteral("text")) + bodyAsAttachment = true; + + if (!mdd->attachments.Length() && !bodyAsAttachment) + return nullptr; + + int32_t totalCount = mdd->attachments.Length(); + if (bodyAsAttachment) + totalCount++; + attachData = new nsMsgAttachmentData[totalCount + 1]; + if (!attachData) + return nullptr; + + tmp = attachData; + + for (int i = 0, attachmentsIndex = 0; i < totalCount; i++, tmp++) + { + if (bodyAsAttachment && i == 0) + tmpFile = mdd->messageBody; + else + tmpFile = mdd->attachments[attachmentsIndex++]; + + if (tmpFile->m_type.LowerCaseEqualsLiteral("text/x-vcard")) + tmp->m_realName = tmpFile->m_description; + + if (tmpFile->m_origUrl) + { + nsAutoCString tmpSpec; + if (NS_FAILED(tmpFile->m_origUrl->GetSpec(tmpSpec))) + goto FAIL; + + if (NS_FAILED(nsMimeNewURI(getter_AddRefs(tmp->m_url), tmpSpec.get(), nullptr))) + goto FAIL; + + if (tmp->m_realName.IsEmpty()) + { + if (!tmpFile->m_realName.IsEmpty()) + tmp->m_realName = tmpFile->m_realName; + else { + if (tmpFile->m_type.Find(MESSAGE_RFC822, CaseInsensitiveCompare) != -1) + // we have the odd case of processing an e-mail that had an unnamed + // eml message attached + tmp->m_realName = "ForwardedMessage.eml"; + + else + tmp->m_realName = tmpSpec.get(); + } + } + } + + tmp->m_desiredType = tmpFile->m_type; + tmp->m_realType = tmpFile->m_type; + tmp->m_realEncoding = tmpFile->m_encoding; + tmp->m_description = tmpFile->m_description; + tmp->m_cloudPartInfo = tmpFile->m_cloudPartInfo; + tmp->m_xMacType = tmpFile->m_xMacType; + tmp->m_xMacCreator = tmpFile->m_xMacCreator; + tmp->m_size = tmpFile->m_size; + } + return attachData; + +FAIL: + delete [] attachData; + return nullptr; +} + +static void +mime_intl_insert_message_header_1(char **body, + const char *hdr_value, + const char *hdr_str, + const char *html_hdr_str, + const char *mailcharset, + bool htmlEdit) +{ + if (!body || !hdr_value || !hdr_str) + return; + + if (htmlEdit) + { + NS_MsgSACat(body, HEADER_START_JUNK); + } + else + { + NS_MsgSACat(body, MSG_LINEBREAK); + } + if (!html_hdr_str) + html_hdr_str = hdr_str; + NS_MsgSACat(body, html_hdr_str); + if (htmlEdit) + { + NS_MsgSACat(body, HEADER_MIDDLE_JUNK); + } + else + NS_MsgSACat(body, ": "); + + // MIME decode header + nsAutoCString utf8Value; + MIME_DecodeMimeHeader(hdr_value, mailcharset, false, true, utf8Value); + if (!utf8Value.IsEmpty()) { + char *escaped = nullptr; + if (htmlEdit) + escaped = MsgEscapeHTML(utf8Value.get()); + NS_MsgSACat(body, escaped ? escaped : utf8Value.get()); + NS_Free(escaped); + } else { + NS_MsgSACat(body, hdr_value); // raw MIME encoded string + } + + if (htmlEdit) + NS_MsgSACat(body, HEADER_END_JUNK); +} + +char * +MimeGetNamedString(int32_t id) +{ + static char retString[256]; + + retString[0] = '\0'; + char *tString = MimeGetStringByID(id); + if (tString) + { + PL_strncpy(retString, tString, sizeof(retString)); + PR_Free(tString); + } + return retString; +} + +void +MimeGetForwardHeaderDelimiter(nsACString &retString) +{ + nsCString defaultValue; + defaultValue.Adopt(MimeGetStringByID(MIME_FORWARDED_MESSAGE_HTML_USER_WROTE)); + + nsString tmpRetString; + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, + "mailnews.forward_header_originalmessage", + NS_ConvertUTF8toUTF16(defaultValue), + tmpRetString); + + CopyUTF16toUTF8(tmpRetString, retString); +} + +/* given an address string passed though parameter "address", this one will be converted + and returned through the same parameter. The original string will be destroyed +*/ +static void UnquoteMimeAddress(nsACString &mimeHeader, const char *charset) +{ + if (!mimeHeader.IsEmpty()) + { + nsTArray<nsCString> addresses; + ExtractDisplayAddresses(EncodedHeader(mimeHeader, charset), + UTF16ArrayAdapter<>(addresses)); + mimeHeader.Truncate(); + + uint32_t count = addresses.Length(); + for (uint32_t i = 0; i < count; i++) + { + if (i != 0) + mimeHeader.AppendASCII(", "); + mimeHeader += addresses[i]; + } + } +} + +static void +mime_insert_all_headers(char **body, + MimeHeaders *headers, + MSG_ComposeFormat composeFormat, + char *mailcharset) +{ + bool htmlEdit = (composeFormat == nsIMsgCompFormat::HTML); + char *newBody = NULL; + char *html_tag = nullptr; + if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0) + html_tag = PL_strchr(*body, '>') + 1; + int i; + + if (!headers->done_p) + { + MimeHeaders_build_heads_list(headers); + headers->done_p = true; + } + + nsCString replyHeader; + MimeGetForwardHeaderDelimiter(replyHeader); + if (htmlEdit) + { + NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX); + NS_MsgSACat(&newBody, replyHeader.get()); + NS_MsgSACat(&newBody, MIME_HEADER_TABLE); + } + else + { + NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK); + NS_MsgSACat(&newBody, replyHeader.get()); + } + + for (i = 0; i < headers->heads_size; i++) + { + char *head = headers->heads[i]; + char *end = (i == headers->heads_size-1 + ? headers->all_headers + headers->all_headers_fp + : headers->heads[i+1]); + char *colon, *ocolon; + char *contents; + char *name = 0; + + // Hack for BSD Mailbox delimiter. + if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5)) + { + colon = head + 4; + contents = colon + 1; + } + else + { + /* Find the colon. */ + for (colon = head; colon < end; colon++) + if (*colon == ':') break; + + if (colon >= end) continue; /* junk */ + + /* Back up over whitespace before the colon. */ + ocolon = colon; + for (; colon > head && IS_SPACE(colon[-1]); colon--) + ; + + contents = ocolon + 1; + } + + /* Skip over whitespace after colon. */ + while (contents <= end && IS_SPACE(*contents)) + contents++; + + /* Take off trailing whitespace... */ + while (end > contents && IS_SPACE(end[-1])) + end--; + + name = (char *)PR_MALLOC(colon - head + 1); + if (!name) + return /* MIME_OUT_OF_MEMORY */; + memcpy(name, head, colon - head); + name[colon - head] = 0; + + nsAutoCString headerValue; + headerValue.Assign(contents, end - contents); + + /* Do not reveal bcc recipients when forwarding a message! + See http://bugzilla.mozilla.org/show_bug.cgi?id=41150 + */ + if (PL_strcasecmp(name, "bcc") != 0) + { + if (!PL_strcasecmp(name, "resent-from") || !PL_strcasecmp(name, "from") || + !PL_strcasecmp(name, "resent-to") || !PL_strcasecmp(name, "to") || + !PL_strcasecmp(name, "resent-cc") || !PL_strcasecmp(name, "cc") || + !PL_strcasecmp(name, "reply-to")) + UnquoteMimeAddress(headerValue, mailcharset); + + mime_intl_insert_message_header_1(&newBody, headerValue.get(), name, name, + mailcharset, htmlEdit); + } + PR_Free(name); + } + + if (htmlEdit) + { + NS_MsgSACat(&newBody, "</TABLE>"); + NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>"); + if (html_tag) + NS_MsgSACat(&newBody, html_tag); + else if (*body) + NS_MsgSACat(&newBody, *body); + } + else + { + NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK); + if (*body) + NS_MsgSACat(&newBody, *body); + } + + if (newBody) + { + PR_FREEIF(*body); + *body = newBody; + } +} + +static void +mime_insert_normal_headers(char **body, + MimeHeaders *headers, + MSG_ComposeFormat composeFormat, + char *mailcharset) +{ + char *newBody = nullptr; + char *subject = MimeHeaders_get(headers, HEADER_SUBJECT, false, false); + char *resent_comments = MimeHeaders_get(headers, HEADER_RESENT_COMMENTS, false, false); + char *resent_date = MimeHeaders_get(headers, HEADER_RESENT_DATE, false, true); + nsCString resent_from(MimeHeaders_get(headers, HEADER_RESENT_FROM, false, true)); + nsCString resent_to(MimeHeaders_get(headers, HEADER_RESENT_TO, false, true)); + nsCString resent_cc(MimeHeaders_get(headers, HEADER_RESENT_CC, false, true)); + char *date = MimeHeaders_get(headers, HEADER_DATE, false, true); + nsCString from(MimeHeaders_get(headers, HEADER_FROM, false, true)); + nsCString reply_to(MimeHeaders_get(headers, HEADER_REPLY_TO, false, true)); + char *organization = MimeHeaders_get(headers, HEADER_ORGANIZATION, false, false); + nsCString to(MimeHeaders_get(headers, HEADER_TO, false, true)); + nsCString cc(MimeHeaders_get(headers, HEADER_CC, false, true)); + char *newsgroups = MimeHeaders_get(headers, HEADER_NEWSGROUPS, false, true); + char *followup_to = MimeHeaders_get(headers, HEADER_FOLLOWUP_TO, false, true); + char *references = MimeHeaders_get(headers, HEADER_REFERENCES, false, true); + const char *html_tag = nullptr; + if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0) + html_tag = PL_strchr(*body, '>') + 1; + bool htmlEdit = composeFormat == nsIMsgCompFormat::HTML; + + if (from.IsEmpty()) + from.Adopt(MimeHeaders_get(headers, HEADER_SENDER, false, true)); + if (resent_from.IsEmpty()) + resent_from.Adopt(MimeHeaders_get(headers, HEADER_RESENT_SENDER, false, + true)); + + UnquoteMimeAddress(resent_from, mailcharset); + UnquoteMimeAddress(resent_to, mailcharset); + UnquoteMimeAddress(resent_cc, mailcharset); + UnquoteMimeAddress(reply_to, mailcharset); + UnquoteMimeAddress(from, mailcharset); + UnquoteMimeAddress(to, mailcharset); + UnquoteMimeAddress(cc, mailcharset); + + nsCString replyHeader; + MimeGetForwardHeaderDelimiter(replyHeader); + if (htmlEdit) + { + NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX); + NS_MsgSACat(&newBody, replyHeader.get()); + NS_MsgSACat(&newBody, MIME_HEADER_TABLE); + } + else + { + NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK); + NS_MsgSACat(&newBody, replyHeader.get()); + } + if (subject) + mime_intl_insert_message_header_1(&newBody, subject, HEADER_SUBJECT, + MimeGetNamedString(MIME_MHTML_SUBJECT), + mailcharset, htmlEdit); + if (resent_comments) + mime_intl_insert_message_header_1(&newBody, resent_comments, + HEADER_RESENT_COMMENTS, + MimeGetNamedString(MIME_MHTML_RESENT_COMMENTS), + mailcharset, htmlEdit); + if (resent_date) + mime_intl_insert_message_header_1(&newBody, resent_date, + HEADER_RESENT_DATE, + MimeGetNamedString(MIME_MHTML_RESENT_DATE), + mailcharset, htmlEdit); + if (!resent_from.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, resent_from.get(), + HEADER_RESENT_FROM, + MimeGetNamedString(MIME_MHTML_RESENT_FROM), + mailcharset, htmlEdit); + } + if (!resent_to.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, resent_to.get(), + HEADER_RESENT_TO, + MimeGetNamedString(MIME_MHTML_RESENT_TO), + mailcharset, htmlEdit); + } + if (!resent_cc.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, resent_cc.get(), + HEADER_RESENT_CC, + MimeGetNamedString(MIME_MHTML_RESENT_CC), + mailcharset, htmlEdit); + } + if (date) + mime_intl_insert_message_header_1(&newBody, date, HEADER_DATE, + MimeGetNamedString(MIME_MHTML_DATE), + mailcharset, htmlEdit); + if (!from.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, from.get(), HEADER_FROM, + MimeGetNamedString(MIME_MHTML_FROM), + mailcharset, htmlEdit); + } + if (!reply_to.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, reply_to.get(), HEADER_REPLY_TO, + MimeGetNamedString(MIME_MHTML_REPLY_TO), + mailcharset, htmlEdit); + } + if (organization) + mime_intl_insert_message_header_1(&newBody, organization, + HEADER_ORGANIZATION, + MimeGetNamedString(MIME_MHTML_ORGANIZATION), + mailcharset, htmlEdit); + if (!to.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, to.get(), HEADER_TO, + MimeGetNamedString(MIME_MHTML_TO), + mailcharset, htmlEdit); + } + if (!cc.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, cc.get(), HEADER_CC, + MimeGetNamedString(MIME_MHTML_CC), + mailcharset, htmlEdit); + } + /* + Do not reveal bcc recipients when forwarding a message! + See http://bugzilla.mozilla.org/show_bug.cgi?id=41150 + */ + if (newsgroups) + mime_intl_insert_message_header_1(&newBody, newsgroups, HEADER_NEWSGROUPS, + MimeGetNamedString(MIME_MHTML_NEWSGROUPS), + mailcharset, htmlEdit); + if (followup_to) + { + mime_intl_insert_message_header_1(&newBody, followup_to, + HEADER_FOLLOWUP_TO, + MimeGetNamedString(MIME_MHTML_FOLLOWUP_TO), + mailcharset, htmlEdit); + } + // only show references for newsgroups + if (newsgroups && references) + { + mime_intl_insert_message_header_1(&newBody, references, + HEADER_REFERENCES, + MimeGetNamedString(MIME_MHTML_REFERENCES), + mailcharset, htmlEdit); + } + if (htmlEdit) + { + NS_MsgSACat(&newBody, "</TABLE>"); + NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>"); + if (html_tag) + NS_MsgSACat(&newBody, html_tag); + else if (*body) + NS_MsgSACat(&newBody, *body); + } + else + { + NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK); + if (*body) + NS_MsgSACat(&newBody, *body); + } + if (newBody) + { + PR_FREEIF(*body); + *body = newBody; + } + PR_FREEIF(subject); + PR_FREEIF(resent_comments); + PR_FREEIF(resent_date); + PR_FREEIF(date); + PR_FREEIF(organization); + PR_FREEIF(newsgroups); + PR_FREEIF(followup_to); + PR_FREEIF(references); +} + +static void +mime_insert_micro_headers(char **body, + MimeHeaders *headers, + MSG_ComposeFormat composeFormat, + char *mailcharset) +{ + char *newBody = NULL; + char *subject = MimeHeaders_get(headers, HEADER_SUBJECT, false, false); + nsCString from(MimeHeaders_get(headers, HEADER_FROM, false, true)); + nsCString resent_from(MimeHeaders_get(headers, HEADER_RESENT_FROM, false, true)); + char *date = MimeHeaders_get(headers, HEADER_DATE, false, true); + nsCString to(MimeHeaders_get(headers, HEADER_TO, false, true)); + nsCString cc(MimeHeaders_get(headers, HEADER_CC, false, true)); + char *newsgroups = MimeHeaders_get(headers, HEADER_NEWSGROUPS, false, + true); + const char *html_tag = nullptr; + if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0) + html_tag = PL_strchr(*body, '>') + 1; + bool htmlEdit = composeFormat == nsIMsgCompFormat::HTML; + + if (from.IsEmpty()) + from.Adopt(MimeHeaders_get(headers, HEADER_SENDER, false, true)); + if (resent_from.IsEmpty()) + resent_from.Adopt(MimeHeaders_get(headers, HEADER_RESENT_SENDER, false, true)); + if (!date) + date = MimeHeaders_get(headers, HEADER_RESENT_DATE, false, true); + + UnquoteMimeAddress(resent_from, mailcharset); + UnquoteMimeAddress(from, mailcharset); + UnquoteMimeAddress(to, mailcharset); + UnquoteMimeAddress(cc, mailcharset); + + nsCString replyHeader; + MimeGetForwardHeaderDelimiter(replyHeader); + if (htmlEdit) + { + NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX); + NS_MsgSACat(&newBody, replyHeader.get()); + NS_MsgSACat(&newBody, MIME_HEADER_TABLE); + } + else + { + NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK); + NS_MsgSACat(&newBody, replyHeader.get()); + } + + if (!from.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, from.get(), HEADER_FROM, + MimeGetNamedString(MIME_MHTML_FROM), + mailcharset, htmlEdit); + } + if (subject) + mime_intl_insert_message_header_1(&newBody, subject, HEADER_SUBJECT, + MimeGetNamedString(MIME_MHTML_SUBJECT), + mailcharset, htmlEdit); +/* + if (date) + mime_intl_insert_message_header_1(&newBody, date, HEADER_DATE, + MimeGetNamedString(MIME_MHTML_DATE), + mailcharset, htmlEdit); +*/ + if (!resent_from.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, resent_from.get(), + HEADER_RESENT_FROM, + MimeGetNamedString(MIME_MHTML_RESENT_FROM), + mailcharset, htmlEdit); + } + if (!to.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, to.get(), HEADER_TO, + MimeGetNamedString(MIME_MHTML_TO), + mailcharset, htmlEdit); + } + if (!cc.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, cc.get(), HEADER_CC, + MimeGetNamedString(MIME_MHTML_CC), + mailcharset, htmlEdit); + } + /* + Do not reveal bcc recipients when forwarding a message! + See http://bugzilla.mozilla.org/show_bug.cgi?id=41150 + */ + if (newsgroups) + mime_intl_insert_message_header_1(&newBody, newsgroups, HEADER_NEWSGROUPS, + MimeGetNamedString(MIME_MHTML_NEWSGROUPS), + mailcharset, htmlEdit); + if (htmlEdit) + { + NS_MsgSACat(&newBody, "</TABLE>"); + NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>"); + if (html_tag) + NS_MsgSACat(&newBody, html_tag); + else if (*body) + NS_MsgSACat(&newBody, *body); + } + else + { + NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK); + if (*body) + NS_MsgSACat(&newBody, *body); + } + if (newBody) + { + PR_FREEIF(*body); + *body = newBody; + } + PR_FREEIF(subject); + PR_FREEIF(date); + PR_FREEIF(newsgroups); + +} + +// body has to be encoded in UTF-8 +static void +mime_insert_forwarded_message_headers(char **body, + MimeHeaders *headers, + MSG_ComposeFormat composeFormat, + char *mailcharset) +{ + if (!body || !headers) + return; + + int32_t show_headers = 0; + nsresult res; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &res)); + if (NS_SUCCEEDED(res)) + prefBranch->GetIntPref("mail.show_headers", &show_headers); + + switch (show_headers) + { + case 0: + mime_insert_micro_headers(body, headers, composeFormat, mailcharset); + break; + default: + case 1: + mime_insert_normal_headers(body, headers, composeFormat, mailcharset); + break; + case 2: + mime_insert_all_headers(body, headers, composeFormat, mailcharset); + break; + } +} + +static void +mime_parse_stream_complete(nsMIMESession *stream) +{ + mime_draft_data *mdd = (mime_draft_data *)stream->data_object; + nsCOMPtr<nsIMsgCompFields> fields; + int htmlAction = 0; + int lineWidth = 0; + + char *host = 0; + char *news_host = 0; + char *to_and_cc = 0; + char *re_subject = 0; + char *new_refs = 0; + char *from = 0; + char *repl = 0; + char *subj = 0; + char *id = 0; + char *refs = 0; + char *to = 0; + char *cc = 0; + char *bcc = 0; + char *fcc = 0; + char *org = 0; + char *grps = 0; + char *foll = 0; + char *priority = 0; + char *draftInfo = 0; + char *contentLanguage = 0; + char *identityKey = 0; + + bool forward_inline = false; + bool bodyAsAttachment = false; + bool charsetOverride = false; + + NS_ASSERTION(mdd, "null mime draft data"); + + if (!mdd) return; + + if (mdd->obj) + { + int status; + + status = mdd->obj->clazz->parse_eof(mdd->obj, false); + mdd->obj->clazz->parse_end(mdd->obj, status < 0 ? true : false); + + // RICHIE + // We need to figure out how to pass the forwarded flag along with this + // operation. + + //forward_inline = (mdd->format_out != FO_CMDLINE_ATTACHMENTS); + forward_inline = mdd->forwardInline; + + NS_ASSERTION(mdd->options == mdd->obj->options, "mime draft options not same as obj->options"); + mime_free(mdd->obj); + mdd->obj = 0; + if (mdd->options) + { + // save the override flag before it's unavailable + charsetOverride = mdd->options->override_charset; + if ((!mdd->mailcharset || charsetOverride) && mdd->options->default_charset) + { + PR_Free(mdd->mailcharset); + mdd->mailcharset = strdup(mdd->options->default_charset); + } + + // mscott: aren't we leaking a bunch of strings here like the charset strings and such? + delete mdd->options; + mdd->options = 0; + } + if (mdd->stream) + { + mdd->stream->complete((nsMIMESession *)mdd->stream->data_object); + PR_Free(mdd->stream); + mdd->stream = 0; + } + } + + // + // Now, process the attachments that we have gathered from the message + // on disk + // + nsMsgAttachmentData *newAttachData = mime_draft_process_attachments(mdd); + + // + // time to bring up the compose windows with all the info gathered + // + if (mdd->headers) + { + subj = MimeHeaders_get(mdd->headers, HEADER_SUBJECT, false, false); + if (forward_inline) + { + if (subj) + { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString fwdPrefix; + prefBranch->GetCharPref("mail.forward_subject_prefix", + getter_Copies(fwdPrefix)); + char *newSubj = PR_smprintf("%s: %s", !fwdPrefix.IsEmpty() ? + fwdPrefix.get(): "Fwd", subj); + if (newSubj) + { + PR_Free(subj); + subj = newSubj; + } + } + } + } + else + { + from = MimeHeaders_get(mdd->headers, HEADER_FROM, false, false); + repl = MimeHeaders_get(mdd->headers, HEADER_REPLY_TO, false, false); + to = MimeHeaders_get(mdd->headers, HEADER_TO, false, true); + cc = MimeHeaders_get(mdd->headers, HEADER_CC, false, true); + bcc = MimeHeaders_get(mdd->headers, HEADER_BCC, false, true); + + /* These headers should not be RFC-1522-decoded. */ + grps = MimeHeaders_get(mdd->headers, HEADER_NEWSGROUPS, false, true); + foll = MimeHeaders_get(mdd->headers, HEADER_FOLLOWUP_TO, false, true); + + host = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_NEWSHOST, false, false); + if (!host) + host = MimeHeaders_get(mdd->headers, HEADER_NNTP_POSTING_HOST, false, false); + + id = MimeHeaders_get(mdd->headers, HEADER_MESSAGE_ID, false, false); + refs = MimeHeaders_get(mdd->headers, HEADER_REFERENCES, false, true); + priority = MimeHeaders_get(mdd->headers, HEADER_X_PRIORITY, false, false); + + + if (host) + { + char *secure = NULL; + + secure = PL_strcasestr(host, "secure"); + if (secure) + { + *secure = 0; + news_host = PR_smprintf ("snews://%s", host); + } + else + { + news_host = PR_smprintf ("news://%s", host); + } + } + } + + + CreateCompositionFields(from, repl, to, cc, bcc, fcc, grps, foll, + org, subj, refs, priority, news_host, + mdd->mailcharset, + getter_AddRefs(fields)); + + contentLanguage = MimeHeaders_get(mdd->headers, HEADER_CONTENT_LANGUAGE, false, false); + if (contentLanguage) { + fields->SetContentLanguage(contentLanguage); + } + + draftInfo = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_DRAFT_INFO, false, false); + + // Keep the same message id when editing a draft unless we're + // editing a message "as new message" (template) or forwarding inline. + if (mdd->format_out != nsMimeOutput::nsMimeMessageEditorTemplate && + fields && !forward_inline) { + fields->SetMessageId(id); + } + + if (draftInfo && fields && !forward_inline) + { + char *parm = 0; + parm = MimeHeaders_get_parameter(draftInfo, "vcard", NULL, NULL); + fields->SetAttachVCard(parm && !strcmp(parm, "1")); + PR_FREEIF(parm); + + parm = MimeHeaders_get_parameter(draftInfo, "receipt", NULL, NULL); + if (!parm || !strcmp(parm, "0")) + fields->SetReturnReceipt(false); + else + { + int receiptType = 0; + fields->SetReturnReceipt(true); + sscanf(parm, "%d", &receiptType); + // slight change compared to 4.x; we used to use receipt= to tell + // whether the draft/template has request for either MDN or DNS or both + // return receipt; since the DNS is out of the picture we now use the + // header type - 1 to tell whether user has requested the return receipt + fields->SetReceiptHeaderType(((int32_t)receiptType) - 1); + } + PR_FREEIF(parm); + parm = MimeHeaders_get_parameter(draftInfo, "DSN", NULL, NULL); + fields->SetDSN(parm && !strcmp(parm, "1")); + PR_Free(parm); + parm = MimeHeaders_get_parameter(draftInfo, "html", NULL, NULL); + if (parm) + sscanf(parm, "%d", &htmlAction); + PR_FREEIF(parm); + parm = MimeHeaders_get_parameter(draftInfo, "linewidth", NULL, NULL); + if (parm) + sscanf(parm, "%d", &lineWidth); + PR_FREEIF(parm); + parm = MimeHeaders_get_parameter(draftInfo, "attachmentreminder", NULL, NULL); + if (parm && !strcmp(parm, "1")) + fields->SetAttachmentReminder(true); + else + fields->SetAttachmentReminder(false); + PR_FREEIF(parm); + parm = MimeHeaders_get_parameter(draftInfo, "deliveryformat", NULL, NULL); + if (parm) { + int32_t deliveryFormat = nsIMsgCompSendFormat::AskUser; + sscanf(parm, "%d", &deliveryFormat); + fields->SetDeliveryFormat(deliveryFormat); + } + PR_FREEIF(parm); + } + + // identity to prefer when opening the message in the compose window? + identityKey = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_IDENTITY_KEY, false, false); + if (identityKey && *identityKey) + { + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && accountManager) + { + nsCOMPtr<nsIMsgIdentity> overrulingIdentity; + rv = accountManager->GetIdentity(nsDependentCString(identityKey), getter_AddRefs(overrulingIdentity)); + + if (NS_SUCCEEDED(rv) && overrulingIdentity) { + mdd->identity = overrulingIdentity; + fields->SetCreatorIdentityKey(identityKey); + } + } + } + + if (mdd->messageBody) + { + MSG_ComposeFormat composeFormat = nsIMsgCompFormat::Default; + if (!mdd->messageBody->m_type.IsEmpty()) + { + if(mdd->messageBody->m_type.Find("text/html", CaseInsensitiveCompare) != -1) + composeFormat = nsIMsgCompFormat::HTML; + else if (mdd->messageBody->m_type.Find("text/plain", CaseInsensitiveCompare) != -1 || + mdd->messageBody->m_type.LowerCaseEqualsLiteral("text")) + composeFormat = nsIMsgCompFormat::PlainText; + else + //We cannot use this kind of data for the message body! Therefore, move it as attachment + bodyAsAttachment = true; + } + else + composeFormat = nsIMsgCompFormat::PlainText; + + char *body = nullptr; + uint32_t bodyLen = 0; + + if (!bodyAsAttachment && mdd->messageBody->m_tmpFile) + { + int64_t fileSize; + nsCOMPtr<nsIFile> tempFileCopy; + mdd->messageBody->m_tmpFile->Clone(getter_AddRefs(tempFileCopy)); + mdd->messageBody->m_tmpFile = do_QueryInterface(tempFileCopy); + tempFileCopy = nullptr; + mdd->messageBody->m_tmpFile->GetFileSize(&fileSize); + + // The stream interface can only read up to 4GB (32bit uint). + // It is highly unlikely to encounter a body lager than that limit, + // so we just skip it instead of reading it in chunks. + if (fileSize < UINT32_MAX) + { + bodyLen = fileSize; + body = (char *)PR_MALLOC(bodyLen + 1); + } + if (body) + { + memset(body, 0, bodyLen+1); + + uint32_t bytesRead; + nsCOMPtr<nsIInputStream> inputStream; + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mdd->messageBody->m_tmpFile); + if (NS_FAILED(rv)) + return; + + inputStream->Read(body, bodyLen, &bytesRead); + + inputStream->Close(); + + // Convert the body to UTF-8 + char *mimeCharset = nullptr; + // Get a charset from the header if no override is set. + if (!charsetOverride) + mimeCharset = MimeHeaders_get_parameter(mdd->messageBody->m_type.get(), "charset", nullptr, nullptr); + // If no charset is specified in the header then use the default. + char *bodyCharset = mimeCharset ? mimeCharset : mdd->mailcharset; + if (bodyCharset) + { + nsAutoString tmpUnicodeBody; + rv = ConvertToUnicode(bodyCharset, body, tmpUnicodeBody); + if (NS_FAILED(rv)) // Tough luck, ASCII/ISO-8859-1 then... + CopyASCIItoUTF16(nsDependentCString(body), tmpUnicodeBody); + + char *newBody = ToNewUTF8String(tmpUnicodeBody); + if (newBody) + { + PR_Free(body); + body = newBody; + } + } + PR_FREEIF(mimeCharset); + } + } + + bool convertToPlainText = false; + if (forward_inline) + { + if (mdd->identity) + { + bool identityComposeHTML; + mdd->identity->GetComposeHtml(&identityComposeHTML); + if ((identityComposeHTML && !mdd->overrideComposeFormat) || + (!identityComposeHTML && mdd->overrideComposeFormat)) + { + // In the end, we're going to compose in HTML mode... + + if (body && composeFormat == nsIMsgCompFormat::PlainText) + { + // ... but the message body is currently plain text. + + //We need to convert the plain/text to HTML in order to escape any HTML markup + char *escapedBody = MsgEscapeHTML(body); + if (escapedBody) + { + PR_Free(body); + body = escapedBody; + bodyLen = strlen(body); + } + + //+13 chars for <pre> & </pre> tags and CRLF + uint32_t newbodylen = bodyLen + 14; + char* newbody = (char *)PR_MALLOC (newbodylen); + if (newbody) + { + *newbody = 0; + PL_strcatn(newbody, newbodylen, "<PRE>"); + PL_strcatn(newbody, newbodylen, body); + PL_strcatn(newbody, newbodylen, "</PRE>" CRLF); + PR_Free(body); + body = newbody; + } + } + // Body is now HTML, set the format too (so headers are inserted in + // correct format). + composeFormat = nsIMsgCompFormat::HTML; + } + else if ((identityComposeHTML && mdd->overrideComposeFormat) || !identityComposeHTML) + { + // In the end, we're going to compose in plain text mode... + + if (composeFormat == nsIMsgCompFormat::HTML) + { + // ... but the message body is currently HTML. + // We'll do the conversion later on when headers have been + // inserted, body has been set and converted to unicode. + convertToPlainText = true; + } + } + } + + mime_insert_forwarded_message_headers(&body, mdd->headers, composeFormat, + mdd->mailcharset); + + } + + // convert from UTF-8 to UTF-16 + if (body) + { + fields->SetBody(NS_ConvertUTF8toUTF16(body)); + PR_Free(body); + } + + // + // At this point, we need to create a message compose window or editor + // window via XP-COM with the information that we have retrieved from + // the message store. + // + if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate) + { + MSG_ComposeType msgComposeType = PL_strstr(mdd->url_name, + "&redirect=true") ? + nsIMsgCompType::Redirect : + nsIMsgCompType::Template; + CreateTheComposeWindow(fields, newAttachData, msgComposeType, + composeFormat, mdd->identity, + mdd->originalMsgURI, mdd->origMsgHdr); + } + else + { + if (mdd->forwardInline) + { + if (convertToPlainText) + fields->ConvertBodyToPlainText(); + if (mdd->overrideComposeFormat) + composeFormat = nsIMsgCompFormat::OppositeOfDefault; + if (mdd->forwardInlineFilter) + { + fields->SetTo(mdd->forwardToAddress); + ForwardMsgInline(fields, newAttachData, composeFormat, + mdd->identity, mdd->originalMsgURI, + mdd->origMsgHdr); + } + else + CreateTheComposeWindow(fields, newAttachData, + nsIMsgCompType::ForwardInline, composeFormat, + mdd->identity, mdd->originalMsgURI, + mdd->origMsgHdr); + } + else + { + fields->SetDraftId(mdd->url_name); + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Draft, composeFormat, mdd->identity, mdd->originalMsgURI, mdd->origMsgHdr); + } + } + } + else + { + // + // At this point, we need to create a message compose window via + // XP-COM with the information that we have retrieved from the message store. + // + if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate) + { +#ifdef NS_DEBUG + printf("RICHIE: Time to create the EDITOR with this template - NO body!!!!\n"); +#endif + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Template, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr); + } + else + { +#ifdef NS_DEBUG + printf("Time to create the composition window WITHOUT a body!!!!\n"); +#endif + if (mdd->forwardInline) + { + MSG_ComposeFormat composeFormat = (mdd->overrideComposeFormat) ? + nsIMsgCompFormat::OppositeOfDefault : nsIMsgCompFormat::Default; + CreateTheComposeWindow(fields, newAttachData, + nsIMsgCompType::ForwardInline, composeFormat, + mdd->identity, mdd->originalMsgURI, + mdd->origMsgHdr); + } + else + { + fields->SetDraftId(mdd->url_name); + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Draft, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr); + } + } + } + } + else + { + CreateCompositionFields(from, repl, to, cc, bcc, fcc, grps, foll, + org, subj, refs, priority, news_host, + mdd->mailcharset, + getter_AddRefs(fields)); + if (fields) + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::New, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr); + } + + if (mdd->headers) + MimeHeaders_free(mdd->headers); + + // + // Free the original attachment structure... + // Make sure we only cleanup the local copy of the memory and not kill + // files we need on disk + // + if (bodyAsAttachment) + mdd->messageBody->m_tmpFile = nullptr; + else if (mdd->messageBody && mdd->messageBody->m_tmpFile) + mdd->messageBody->m_tmpFile->Remove(false); + + delete mdd->messageBody; + + for (uint32_t i = 0; i < mdd->attachments.Length(); i++) + mdd->attachments[i]->m_tmpFile = nullptr; + + PR_FREEIF(mdd->mailcharset); + + mdd->identity = nullptr; + PR_Free(mdd->url_name); + PR_Free(mdd->originalMsgURI); + mdd->origMsgHdr = nullptr; + PR_Free(mdd); + + PR_FREEIF(host); + PR_FREEIF(to_and_cc); + PR_FREEIF(re_subject); + PR_FREEIF(new_refs); + PR_FREEIF(from); + PR_FREEIF(repl); + PR_FREEIF(subj); + PR_FREEIF(id); + PR_FREEIF(refs); + PR_FREEIF(to); + PR_FREEIF(cc); + PR_FREEIF(grps); + PR_FREEIF(foll); + PR_FREEIF(priority); + PR_FREEIF(draftInfo); + PR_Free(identityKey); + + delete [] newAttachData; +} + +static void +mime_parse_stream_abort(nsMIMESession *stream, int status) +{ + mime_draft_data *mdd = (mime_draft_data *)stream->data_object; + NS_ASSERTION(mdd, "null mime draft data"); + + if (!mdd) + return; + + if (mdd->obj) + { + int status=0; + + if (!mdd->obj->closed_p) + status = mdd->obj->clazz->parse_eof(mdd->obj, true); + if (!mdd->obj->parsed_p) + mdd->obj->clazz->parse_end(mdd->obj, true); + + NS_ASSERTION(mdd->options == mdd->obj->options, "draft display options not same as mime obj"); + mime_free (mdd->obj); + mdd->obj = 0; + if (mdd->options) + { + delete mdd->options; + mdd->options = 0; + } + + if (mdd->stream) + { + mdd->stream->abort((nsMIMESession *)mdd->stream->data_object, status); + PR_Free(mdd->stream); + mdd->stream = 0; + } + } + + if (mdd->headers) + MimeHeaders_free(mdd->headers); + + + mime_free_attachments(mdd->attachments); + + PR_FREEIF(mdd->mailcharset); + + PR_Free(mdd); +} + +static int +make_mime_headers_copy(void *closure, MimeHeaders *headers) +{ + mime_draft_data *mdd = (mime_draft_data *)closure; + + NS_ASSERTION(mdd && headers, "null mime draft data and/or headers"); + + if (!mdd || ! headers) + return 0; + + NS_ASSERTION(mdd->headers == NULL , "non null mime draft data headers"); + + mdd->headers = MimeHeaders_copy(headers); + mdd->options->done_parsing_outer_headers = true; + + return 0; +} + +int +mime_decompose_file_init_fn(void *stream_closure, MimeHeaders *headers) +{ + mime_draft_data *mdd = (mime_draft_data *)stream_closure; + nsMsgAttachedFile *newAttachment = 0; + int nAttachments = 0; + //char *hdr_value = NULL; + char *parm_value = NULL; + bool creatingMsgBody = true; + + NS_ASSERTION(mdd && headers, "null mime draft data and/or headers"); + if (!mdd || !headers) + return -1; + + if (mdd->options->decompose_init_count) + { + mdd->options->decompose_init_count++; + NS_ASSERTION(mdd->curAttachment, "missing attachment in mime_decompose_file_init_fn"); + if (mdd->curAttachment) + mdd->curAttachment->m_type.Adopt(MimeHeaders_get(headers, + HEADER_CONTENT_TYPE, + false, true)); + return 0; + } + else + mdd->options->decompose_init_count++; + + nAttachments = mdd->attachments.Length(); + + if (!nAttachments && !mdd->messageBody) + { + // if we've been told to use an override charset then do so....otherwise use the charset + // inside the message header... + if (mdd->options && mdd->options->override_charset) + mdd->mailcharset = strdup(mdd->options->default_charset); + else + { + char *contentType; + contentType = MimeHeaders_get(headers, HEADER_CONTENT_TYPE, false, false); + if (contentType) + { + mdd->mailcharset = MimeHeaders_get_parameter(contentType, "charset", NULL, NULL); + PR_FREEIF(contentType); + } + } + + mdd->messageBody = new nsMsgAttachedFile; + if (!mdd->messageBody) + return MIME_OUT_OF_MEMORY; + newAttachment = mdd->messageBody; + creatingMsgBody = true; + } + else + { + /* always allocate one more extra; don't ask me why */ + newAttachment = new nsMsgAttachedFile; + if (!newAttachment) + return MIME_OUT_OF_MEMORY; + mdd->attachments.AppendElement(newAttachment); + } + + char *workURLSpec = nullptr; + char *contLoc = nullptr; + + newAttachment->m_realName.Adopt(MimeHeaders_get_name(headers, mdd->options)); + contLoc = MimeHeaders_get(headers, HEADER_CONTENT_LOCATION, false, false); + if (!contLoc) + contLoc = MimeHeaders_get(headers, HEADER_CONTENT_BASE, false, false); + + if (!contLoc && !newAttachment->m_realName.IsEmpty()) + workURLSpec = ToNewCString(newAttachment->m_realName); + if (contLoc && !workURLSpec) + workURLSpec = strdup(contLoc); + + PR_FREEIF(contLoc); + + mdd->curAttachment = newAttachment; + newAttachment->m_type.Adopt(MimeHeaders_get(headers, HEADER_CONTENT_TYPE, false, false)); + + // + // This is to handle the degenerated Apple Double attachment. + // + parm_value = MimeHeaders_get(headers, HEADER_CONTENT_TYPE, false, false); + if (parm_value) + { + char *boundary = NULL; + char *tmp_value = NULL; + boundary = MimeHeaders_get_parameter(parm_value, "boundary", NULL, NULL); + if (boundary) + tmp_value = PR_smprintf("; boundary=\"%s\"", boundary); + if (tmp_value) + newAttachment->m_type = tmp_value; + newAttachment->m_xMacType.Adopt( + MimeHeaders_get_parameter(parm_value, "x-mac-type", NULL, NULL)); + newAttachment->m_xMacCreator.Adopt( + MimeHeaders_get_parameter(parm_value, "x-mac-creator", NULL, NULL)); + PR_FREEIF(parm_value); + PR_FREEIF(boundary); + PR_FREEIF(tmp_value); + } + + newAttachment->m_size = 0; + newAttachment->m_encoding.Adopt(MimeHeaders_get(headers, HEADER_CONTENT_TRANSFER_ENCODING, + false, false)); + newAttachment->m_description.Adopt(MimeHeaders_get(headers, HEADER_CONTENT_DESCRIPTION, + false, false)); + // + // If we came up empty for description or the orig URL, we should do something about it. + // + if (newAttachment->m_description.IsEmpty() && workURLSpec) + newAttachment->m_description = workURLSpec; + + PR_FREEIF(workURLSpec); // resource leak otherwise + + newAttachment->m_cloudPartInfo.Adopt(MimeHeaders_get(headers, + HEADER_X_MOZILLA_CLOUD_PART, + false, false)); + + // There's no file in the message if it's a cloud part. + if (!newAttachment->m_cloudPartInfo.IsEmpty()) + { + nsAutoCString fileURL; + fileURL.Adopt( + MimeHeaders_get_parameter(newAttachment->m_cloudPartInfo.get(), "file", + nullptr, nullptr)); + if (!fileURL.IsEmpty()) + nsMimeNewURI(getter_AddRefs(newAttachment->m_origUrl), fileURL.get(), + nullptr); + mdd->tmpFile = nullptr; + return 0; + } + + nsCOMPtr<nsIFile> tmpFile = nullptr; + { + // Let's build a temp file with an extension based on the content-type: nsmail.<extension> + + nsAutoCString newAttachName("nsmail"); + bool extensionAdded = false; + // the content type may contain a charset. i.e. text/html; ISO-2022-JP...we want to strip off the charset + // before we ask the mime service for a mime info for this content type. + nsAutoCString contentType(newAttachment->m_type); + int32_t pos = contentType.FindChar(';'); + if (pos > 0) + contentType.SetLength(pos); + nsresult rv = NS_OK; + nsCOMPtr<nsIMIMEService> mimeFinder(do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && mimeFinder) + { + nsAutoCString fileExtension; + rv = mimeFinder->GetPrimaryExtension(contentType, EmptyCString(), fileExtension); + + if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty()) + { + newAttachName.Append("."); + newAttachName.Append(fileExtension); + extensionAdded = true; + } + } + + if (!extensionAdded) + { + newAttachName.Append(".tmp"); + } + + nsMsgCreateTempFile(newAttachName.get(), getter_AddRefs(tmpFile)); + } + nsresult rv; + + // This needs to be done so the attachment structure has a handle + // on the temp file for this attachment... + if (tmpFile) + { + nsAutoCString fileURL; + rv = NS_GetURLSpecFromFile(tmpFile, fileURL); + if (NS_SUCCEEDED(rv)) + nsMimeNewURI(getter_AddRefs(newAttachment->m_origUrl), + fileURL.get(), nullptr); + } + + if (!tmpFile) + return MIME_OUT_OF_MEMORY; + + mdd->tmpFile = do_QueryInterface(tmpFile); + + newAttachment->m_tmpFile = mdd->tmpFile; + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mdd->tmpFileStream), tmpFile,PR_WRONLY | PR_CREATE_FILE, 00600); + if (NS_FAILED(rv)) + return MIME_UNABLE_TO_OPEN_TMP_FILE; + + // For now, we are always going to decode all of the attachments + // for the message. This way, we have native data + if (creatingMsgBody) + { + MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0; + + // + // Initialize a decoder if necessary. + // + if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + fn = &MimeB64DecoderInit; + else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE)) + { + mdd->decoder_data = MimeQPDecoderInit (/* The (MimeConverterOutputCallback) cast is to turn the `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback)dummy_file_write), + mdd->tmpFileStream); + if (!mdd->decoder_data) + return MIME_OUT_OF_MEMORY; + } + else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE) || + newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE2) || + newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE3) || + newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE4)) + fn = &MimeUUDecoderInit; + else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_YENCODE)) + fn = &MimeYDecoderInit; + + if (fn) + { + mdd->decoder_data = fn (/* The (MimeConverterOutputCallback) cast is to + turn the `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) dummy_file_write), + mdd->tmpFileStream); + if (!mdd->decoder_data) + return MIME_OUT_OF_MEMORY; + } + } + + return 0; +} + +int +mime_decompose_file_output_fn(const char *buf, + int32_t size, + void *stream_closure) +{ + mime_draft_data *mdd = (mime_draft_data *)stream_closure; + int ret = 0; + + NS_ASSERTION(mdd && buf, "missing mime draft data and/or buf"); + if (!mdd || !buf) return -1; + if (!size) return 0; + + if (!mdd->tmpFileStream) + return 0; + + if (mdd->decoder_data) { + int32_t outsize; + ret = MimeDecoderWrite(mdd->decoder_data, buf, size, &outsize); + if (ret == -1) return -1; + mdd->curAttachment->m_size += outsize; + } + else + { + uint32_t bytesWritten; + mdd->tmpFileStream->Write(buf, size, &bytesWritten); + if ((int32_t)bytesWritten < size) + return MIME_ERROR_WRITING_FILE; + mdd->curAttachment->m_size += size; + } + + return 0; +} + +int +mime_decompose_file_close_fn(void *stream_closure) +{ + mime_draft_data *mdd = (mime_draft_data *)stream_closure; + + if (!mdd) + return -1; + + if (--mdd->options->decompose_init_count > 0) + return 0; + + if (mdd->decoder_data) { + MimeDecoderDestroy(mdd->decoder_data, false); + mdd->decoder_data = 0; + } + + if (!mdd->tmpFileStream) { + // it's ok to have a null tmpFileStream if there's no tmpFile. + // This happens for cloud file attachments. + NS_ASSERTION(!mdd->tmpFile, "shouldn't have a tmp file bu no stream"); + return 0; + } + mdd->tmpFileStream->Close(); + + mdd->tmpFileStream = nullptr; + + mdd->tmpFile = nullptr; + + return 0; +} + +extern "C" void * +mime_bridge_create_draft_stream( + nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out) +{ + int status = 0; + nsMIMESession *stream = nullptr; + mime_draft_data *mdd = nullptr; + MimeObject *obj = nullptr; + + if (!uri) + return nullptr; + + mdd = new mime_draft_data; + if (!mdd) + return nullptr; + + nsAutoCString turl; + nsCOMPtr<nsIMsgMessageService> msgService; + nsCOMPtr<nsIURI> aURL; + nsAutoCString urlString; + nsresult rv; + + // first, convert the rdf msg uri into a url that represents the message... + if (NS_FAILED(uri->GetSpec(turl))) + goto FAIL; + + rv = GetMessageServiceFromURI(turl, getter_AddRefs(msgService)); + if (NS_FAILED(rv)) + goto FAIL; + + rv = msgService->GetUrlForUri(turl.get(), getter_AddRefs(aURL), nullptr); + if (NS_FAILED(rv)) + goto FAIL; + + if (NS_SUCCEEDED(aURL->GetSpec(urlString))) + { + int32_t typeIndex = urlString.Find("&type=application/x-message-display"); + if (typeIndex != -1) + urlString.Cut(typeIndex, sizeof("&type=application/x-message-display") - 1); + + mdd->url_name = ToNewCString(urlString); + if (!(mdd->url_name)) + goto FAIL; + } + + newPluginObj2->GetForwardInline(&mdd->forwardInline); + newPluginObj2->GetForwardInlineFilter(&mdd->forwardInlineFilter); + newPluginObj2->GetForwardToAddress(mdd->forwardToAddress); + newPluginObj2->GetOverrideComposeFormat(&mdd->overrideComposeFormat); + newPluginObj2->GetIdentity(getter_AddRefs(mdd->identity)); + newPluginObj2->GetOriginalMsgURI(&mdd->originalMsgURI); + newPluginObj2->GetOrigMsgHdr(getter_AddRefs(mdd->origMsgHdr)); + mdd->format_out = format_out; + mdd->options = new MimeDisplayOptions ; + if (!mdd->options) + goto FAIL; + + mdd->options->url = strdup(mdd->url_name); + mdd->options->format_out = format_out; // output format + mdd->options->decompose_file_p = true; /* new field in MimeDisplayOptions */ + mdd->options->stream_closure = mdd; + mdd->options->html_closure = mdd; + mdd->options->decompose_headers_info_fn = make_mime_headers_copy; + mdd->options->decompose_file_init_fn = mime_decompose_file_init_fn; + mdd->options->decompose_file_output_fn = mime_decompose_file_output_fn; + mdd->options->decompose_file_close_fn = mime_decompose_file_close_fn; + + mdd->options->m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + goto FAIL; + +#ifdef ENABLE_SMIME + /* If we're attaching a message (for forwarding) then we must eradicate all + traces of xlateion from it, since forwarding someone else a message + that wasn't xlated for them doesn't work. We have to dexlate it + before sending it. + */ + mdd->options->decrypt_p = true; +#endif /* ENABLE_SMIME */ + + obj = mime_new((MimeObjectClass *)&mimeMessageClass, (MimeHeaders *)NULL, MESSAGE_RFC822); + if (!obj) + goto FAIL; + + obj->options = mdd->options; + mdd->obj = obj; + + stream = PR_NEWZAP(nsMIMESession); + if (!stream) + goto FAIL; + + stream->name = "MIME To Draft Converter Stream"; + stream->complete = mime_parse_stream_complete; + stream->abort = mime_parse_stream_abort; + stream->put_block = mime_parse_stream_write; + stream->data_object = mdd; + + status = obj->clazz->initialize(obj); + if (status >= 0) + status = obj->clazz->parse_begin(obj); + if (status < 0) + goto FAIL; + + return stream; + +FAIL: + if (mdd) + { + PR_Free(mdd->url_name); + PR_Free(mdd->originalMsgURI); + if (mdd->options) + delete mdd->options; + PR_Free(mdd); + } + PR_Free(stream); + PR_Free(obj); + + return nullptr; +} diff --git a/mailnews/mime/src/mimeebod.cpp b/mailnews/mime/src/mimeebod.cpp new file mode 100644 index 000000000..2ca056feb --- /dev/null +++ b/mailnews/mime/src/mimeebod.cpp @@ -0,0 +1,509 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsIURL.h" +#include "mimeebod.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "prio.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "nsINetUtil.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeObjectClass +MimeDefClass(MimeExternalBody, MimeExternalBodyClass, + mimeExternalBodyClass, &MIME_SUPERCLASS); + +#ifdef XP_MACOSX +extern MimeObjectClass mimeMultipartAppleDoubleClass; +#endif + +static int MimeExternalBody_initialize (MimeObject *); +static void MimeExternalBody_finalize (MimeObject *); +static int MimeExternalBody_parse_line (const char *, int32_t, MimeObject *); +static int MimeExternalBody_parse_eof (MimeObject *, bool); +static bool MimeExternalBody_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +#if 0 +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeExternalBody_debug_print (MimeObject *, PRFileDesc *, int32_t); +#endif +#endif /* 0 */ + +static int +MimeExternalBodyClassInitialize(MimeExternalBodyClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeExternalBody_initialize; + oclass->finalize = MimeExternalBody_finalize; + oclass->parse_line = MimeExternalBody_parse_line; + oclass->parse_eof = MimeExternalBody_parse_eof; + oclass->displayable_inline_p = MimeExternalBody_displayable_inline_p; + +#if 0 +#if defined(DEBUG) && defined(XP_UNIX) + oclass->debug_print = MimeExternalBody_debug_print; +#endif +#endif /* 0 */ + + return 0; +} + + +static int +MimeExternalBody_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeExternalBody_finalize (MimeObject *object) +{ + MimeExternalBody *bod = (MimeExternalBody *) object; + if (bod->hdrs) + { + MimeHeaders_free(bod->hdrs); + bod->hdrs = 0; + } + PR_FREEIF(bod->body); + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeExternalBody_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeExternalBody *bod = (MimeExternalBody *) obj; + int status = 0; + + NS_ASSERTION(line && *line, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!line || !*line) return -1; + + if (!obj->output_p) return 0; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (obj->options && + !obj->options->write_html_p && + obj->options->output_fn) + return MimeObject_write(obj, line, length, true); + + + /* If we already have a `body' then we're done parsing headers, and all + subsequent lines get tacked onto the body. */ + if (bod->body) + { + int L = strlen(bod->body); + char *new_str = (char *)PR_Realloc(bod->body, L + length + 1); + if (!new_str) return MIME_OUT_OF_MEMORY; + bod->body = new_str; + memcpy(bod->body + L, line, length); + bod->body[L + length] = 0; + return 0; + } + + /* Otherwise we don't yet have a body, which means we're not done parsing + our headers. + */ + if (!bod->hdrs) + { + bod->hdrs = MimeHeaders_new(); + if (!bod->hdrs) return MIME_OUT_OF_MEMORY; + } + + status = MimeHeaders_parse_line(line, length, bod->hdrs); + if (status < 0) return status; + + /* If this line is blank, we're now done parsing headers, and should + create a dummy body to show that. Gag. + */ + if (*line == '\r' || *line == '\n') + { + bod->body = strdup(""); + if (!bod->body) return MIME_OUT_OF_MEMORY; + } + + return 0; +} + + +char * +MimeExternalBody_make_url(const char *ct, + const char *at, const char *lexp, const char *size, + const char *perm, const char *dir, const char *mode, + const char *name, const char *url, const char *site, + const char *svr, const char *subj, const char *body) +{ + char *s; + uint32_t slen; + if (!at) + { + return 0; + } + else if (!PL_strcasecmp(at, "ftp") || !PL_strcasecmp(at, "anon-ftp")) + { + if (!site || !name) + return 0; + + slen = strlen(name) + strlen(site) + (dir ? strlen(dir) : 0) + 20; + s = (char *) PR_MALLOC(slen); + + if (!s) return 0; + PL_strncpyz(s, "ftp://", slen); + PL_strcatn(s, slen, site); + PL_strcatn(s, slen, "/"); + if (dir) PL_strcatn(s, slen, (dir[0] == '/' ? dir+1 : dir)); + if (s[strlen(s)-1] != '/') + PL_strcatn(s, slen, "/"); + PL_strcatn(s, slen, name); + return s; + } + else if (!PL_strcasecmp(at, "local-file") || !PL_strcasecmp(at, "afs")) + { + if (!name) + return 0; + +#ifdef XP_UNIX + if (!PL_strcasecmp(at, "afs")) /* only if there is a /afs/ directory */ + { + nsCOMPtr <nsIFile> fs = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + bool exists = false; + if (fs) + { + fs->InitWithNativePath(NS_LITERAL_CSTRING("/afs/.")); + fs->Exists(&exists); + } + if (!exists) + return 0; + } +#else /* !XP_UNIX */ + return 0; /* never, if not Unix. */ +#endif /* !XP_UNIX */ + + slen = (strlen(name) * 3 + 20); + s = (char *) PR_MALLOC(slen); + if (!s) return 0; + PL_strncpyz(s, "file:", slen); + + nsCString s2; + MsgEscapeString(nsDependentCString(name), nsINetUtil::ESCAPE_URL_PATH, s2); + PL_strcatn(s, slen, s2.get()); + return s; + } +else if (!PL_strcasecmp(at, "mail-server")) +{ + if (!svr) + return 0; + + slen = (strlen(svr)*4 + (subj ? strlen(subj)*4 : 0) + + (body ? strlen(body)*4 : 0) + 25); // dpv xxx: why 4x? %xx escaping should be 3x + s = (char *) PR_MALLOC(slen); + if (!s) return 0; + PL_strncpyz(s, "mailto:", slen); + + nsCString s2; + MsgEscapeString(nsDependentCString(svr), nsINetUtil::ESCAPE_XALPHAS, s2); + PL_strcatn(s, slen, s2.get()); + + if (subj) + { + MsgEscapeString(nsDependentCString(subj), nsINetUtil::ESCAPE_XALPHAS, s2); + PL_strcatn(s, slen, "?subject="); + PL_strcatn(s, slen, s2.get()); + } + if (body) + { + MsgEscapeString(nsDependentCString(body), nsINetUtil::ESCAPE_XALPHAS, s2); + PL_strcatn(s, slen, (subj ? "&body=" : "?body=")); + PL_strcatn(s, slen, s2.get()); + } + return s; +} +else if (!PL_strcasecmp(at, "url")) /* RFC 2017 */ + { + if (url) + return strdup(url); /* it's already quoted and everything */ + else + return 0; + } + else + return 0; +} + +static int +MimeExternalBody_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + MimeExternalBody *bod = (MimeExternalBody *) obj; + + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + +#ifdef XP_MACOSX + if (obj->parent && mime_typep(obj->parent, + (MimeObjectClass*) &mimeMultipartAppleDoubleClass)) + goto done; +#endif /* XP_MACOSX */ + + if (!abort_p && + obj->output_p && + obj->options && + obj->options->write_html_p) + { + bool all_headers_p = obj->options->headers == MimeHeadersAll; + MimeDisplayOptions *newopt = obj->options; /* copy it */ + + char *ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, + false, false); + char *at, *lexp, *size, *perm; + char *url, *dir, *mode, *name, *site, *svr, *subj; + char *h = 0, *lname = 0, *lurl = 0, *body = 0; + MimeHeaders *hdrs = 0; + + if (!ct) return MIME_OUT_OF_MEMORY; + + at = MimeHeaders_get_parameter(ct, "access-type", NULL, NULL); + lexp = MimeHeaders_get_parameter(ct, "expiration", NULL, NULL); + size = MimeHeaders_get_parameter(ct, "size", NULL, NULL); + perm = MimeHeaders_get_parameter(ct, "permission", NULL, NULL); + dir = MimeHeaders_get_parameter(ct, "directory", NULL, NULL); + mode = MimeHeaders_get_parameter(ct, "mode", NULL, NULL); + name = MimeHeaders_get_parameter(ct, "name", NULL, NULL); + site = MimeHeaders_get_parameter(ct, "site", NULL, NULL); + svr = MimeHeaders_get_parameter(ct, "server", NULL, NULL); + subj = MimeHeaders_get_parameter(ct, "subject", NULL, NULL); + url = MimeHeaders_get_parameter(ct, "url", NULL, NULL); + PR_FREEIF(ct); + + /* the *internal* content-type */ + ct = MimeHeaders_get(bod->hdrs, HEADER_CONTENT_TYPE, + true, false); + + uint32_t hlen = ((at ? strlen(at) : 0) + + (lexp ? strlen(lexp) : 0) + + (size ? strlen(size) : 0) + + (perm ? strlen(perm) : 0) + + (dir ? strlen(dir) : 0) + + (mode ? strlen(mode) : 0) + + (name ? strlen(name) : 0) + + (site ? strlen(site) : 0) + + (svr ? strlen(svr) : 0) + + (subj ? strlen(subj) : 0) + + (ct ? strlen(ct) : 0) + + (url ? strlen(url) : 0) + 100); + + h = (char *) PR_MALLOC(hlen); + if (!h) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + + /* If there's a URL parameter, remove all whitespace from it. + (The URL parameter to one of these headers is stored with + lines broken every 40 characters or less; it's assumed that + all significant whitespace was URL-hex-encoded, and all the + rest of it was inserted just to keep the lines short.) + */ + if (url) + { + char *in, *out; + for (in = url, out = url; *in; in++) + if (!IS_SPACE(*in)) + *out++ = *in; + *out = 0; + } + + hdrs = MimeHeaders_new(); + if (!hdrs) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + +# define FROB(STR,VAR) \ + if (VAR) \ + { \ + PL_strncpyz(h, STR ": ", hlen); \ + PL_strcatn(h, hlen, VAR); \ + PL_strcatn(h, hlen, MSG_LINEBREAK); \ + status = MimeHeaders_parse_line(h, strlen(h), hdrs); \ + if (status < 0) goto FAIL; \ + } + FROB("Access-Type", at); + FROB("URL", url); + FROB("Site", site); + FROB("Server", svr); + FROB("Directory", dir); + FROB("Name", name); + FROB("Type", ct); + FROB("Size", size); + FROB("Mode", mode); + FROB("Permission", perm); + FROB("Expiration", lexp); + FROB("Subject", subj); +# undef FROB + PL_strncpyz(h, MSG_LINEBREAK, hlen); + status = MimeHeaders_parse_line(h, strlen(h), hdrs); + if (status < 0) goto FAIL; + + lurl = MimeExternalBody_make_url(ct, at, lexp, size, perm, dir, mode, + name, url, site, svr, subj, bod->body); + if (lurl) + { + lname = MimeGetStringByID(MIME_MSG_LINK_TO_DOCUMENT); + } + else + { + lname = MimeGetStringByID(MIME_MSG_DOCUMENT_INFO); + all_headers_p = true; + } + + all_headers_p = true; /* #### just do this all the time? */ + + if (bod->body && all_headers_p) + { + char *s = bod->body; + while (IS_SPACE(*s)) s++; + if (*s) + { + char *s2; + const char *pre = "<P><PRE>"; + const char *suf = "</PRE>"; + int32_t i; + for(i = strlen(s)-1; i >= 0 && IS_SPACE(s[i]); i--) + s[i] = 0; + s2 = MsgEscapeHTML(s); + if (!s2) goto FAIL; + body = (char *) PR_MALLOC(strlen(pre) + strlen(s2) + + strlen(suf) + 1); + if (!body) + { + NS_Free(s2); + goto FAIL; + } + PL_strcpy(body, pre); + PL_strcat(body, s2); + PL_strcat(body, suf); + } + } + + newopt->fancy_headers_p = true; + newopt->headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome); + +FAIL: + if (hdrs) + MimeHeaders_free(hdrs); + PR_FREEIF(h); + PR_FREEIF(lname); + PR_FREEIF(lurl); + PR_FREEIF(body); + PR_FREEIF(ct); + PR_FREEIF(at); + PR_FREEIF(lexp); + PR_FREEIF(size); + PR_FREEIF(perm); + PR_FREEIF(dir); + PR_FREEIF(mode); + PR_FREEIF(name); + PR_FREEIF(url); + PR_FREEIF(site); + PR_FREEIF(svr); + PR_FREEIF(subj); + } + +#ifdef XP_MACOSX +done: +#endif + + return status; +} + +#if 0 +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeExternalBody_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + MimeExternalBody *bod = (MimeExternalBody *) obj; + int i; + char *ct, *ct2; + char *addr = mime_part_address(obj); + + if (obj->headers) + ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false); + if (bod->hdrs) + ct2 = MimeHeaders_get (bod->hdrs, HEADER_CONTENT_TYPE, false, false); + + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); +/*** + fprintf(stream, + "<%s %s\n" + "\tcontent-type: %s\n" + "\tcontent-type: %s\n" + "\tBody:%s\n\t0x%08X>\n\n", + obj->clazz->class_name, + addr ? addr : "???", + ct ? ct : "<none>", + ct2 ? ct2 : "<none>", + bod->body ? bod->body : "<none>", + (uint32_t) obj); +***/ + PR_FREEIF(addr); + PR_FREEIF(ct); + PR_FREEIF(ct2); + return 0; +} +#endif +#endif /* 0 */ + +static bool +MimeExternalBody_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs) +{ + char *ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, false, false); + char *at = MimeHeaders_get_parameter(ct, "access-type", NULL, NULL); + bool inline_p = false; + + if (!at) + ; + else if (!PL_strcasecmp(at, "ftp") || + !PL_strcasecmp(at, "anon-ftp") || + !PL_strcasecmp(at, "local-file") || + !PL_strcasecmp(at, "mail-server") || + !PL_strcasecmp(at, "url")) + inline_p = true; +#ifdef XP_UNIX + else if (!PL_strcasecmp(at, "afs")) /* only if there is a /afs/ directory */ + { + nsCOMPtr <nsIFile> fs = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + bool exists = false; + if (fs) + { + fs->InitWithNativePath(NS_LITERAL_CSTRING("/afs/.")); + fs->Exists(&exists); + } + if (!exists) + return 0; + + inline_p = true; + } +#endif /* XP_UNIX */ + + PR_FREEIF(ct); + PR_FREEIF(at); + return inline_p; +} diff --git a/mailnews/mime/src/mimeebod.h b/mailnews/mime/src/mimeebod.h new file mode 100644 index 000000000..560bef77c --- /dev/null +++ b/mailnews/mime/src/mimeebod.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEEBOD_H_ +#define _MIMEEBOD_H_ + +#include "mimeobj.h" + +/* The MimeExternalBody class implements the message/external-body MIME type. + (This is not to be confused with MimeExternalObject, which implements the + handler for application/octet-stream and other types with no more specific + handlers.) + */ + +typedef struct MimeExternalBodyClass MimeExternalBodyClass; +typedef struct MimeExternalBody MimeExternalBody; + +struct MimeExternalBodyClass { + MimeObjectClass object; +}; + +extern MimeExternalBodyClass mimeExternalBodyClass; + +struct MimeExternalBody { + MimeObject object; /* superclass variables */ + MimeHeaders *hdrs; /* headers within this external-body, which + describe the network data which this body + is a pointer to. */ + char *body; /* The "phantom body" of this link. */ +}; + +#define MimeExternalBodyClassInitializer(ITYPE,CSUPER) \ + { MimeObjectClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEEBOD_H_ */ diff --git a/mailnews/mime/src/mimeenc.cpp b/mailnews/mime/src/mimeenc.cpp new file mode 100644 index 000000000..d565a6067 --- /dev/null +++ b/mailnews/mime/src/mimeenc.cpp @@ -0,0 +1,1107 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <stdio.h> +#include "mimei.h" +#include "prmem.h" +#include "mimeobj.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/mailnews/MimeEncoder.h" + +typedef enum mime_encoding { + mime_Base64, mime_QuotedPrintable, mime_uuencode, mime_yencode +} mime_encoding; + +typedef enum mime_decoder_state { + DS_BEGIN, DS_BODY, DS_END +} mime_decoder_state; + +struct MimeDecoderData { + mime_encoding encoding; /* Which encoding to use */ + + /* A read-buffer used for QP and B64. */ + char token[4]; + int token_size; + + /* State and read-buffer used for uudecode and yencode. */ + mime_decoder_state ds_state; + char *line_buffer; + int line_buffer_size; + + MimeObject *objectToDecode; // might be null, only used for QP currently + /* Where to write the decoded data */ + MimeConverterOutputCallback write_buffer; + void *closure; +}; + + +static int +mime_decode_qp_buffer (MimeDecoderData *data, const char *buffer, + int32_t length, int32_t *outSize) +{ + /* Warning, we are overwriting the buffer which was passed in. + This is ok, because decoding these formats will never result + in larger data than the input, only smaller. */ + const char *in = buffer; + char *out = (char *) buffer; + char token [3]; + int i; + + NS_ASSERTION(data->encoding == mime_QuotedPrintable, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (data->encoding != mime_QuotedPrintable) return -1; + + /* For the first pass, initialize the token from the unread-buffer. */ + i = 0; + while (i < 3 && data->token_size > 0) + { + token [i] = data->token[i]; + data->token_size--; + i++; + } + + /* #### BUG: when decoding quoted-printable, we are required to + strip trailing whitespace from lines -- since when encoding in + qp, one is required to quote such trailing whitespace, any + trailing whitespace which remains must have been introduced + by a stupid gateway. */ + + /* Treat null bytes as spaces when format_out is + nsMimeOutput::nsMimeMessageBodyDisplay (see bug 243199 comment 7) */ + bool treatNullAsSpace = data->objectToDecode && + data->objectToDecode->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay; + + while (length > 0 || i != 0) + { + while (i < 3 && length > 0) + { + token [i++] = *in; + in++; + length--; + } + + if (i < 3) + { + /* Didn't get enough for a complete token. + If it might be a token, unread it. + Otherwise, just dump it. + */ + memcpy (data->token, token, i); + data->token_size = i; + i = 0; + length = 0; + break; + } + i = 0; + + if (token [0] == '=') + { + unsigned char c = 0; + if (token[1] >= '0' && token[1] <= '9') + c = token[1] - '0'; + else if (token[1] >= 'A' && token[1] <= 'F') + c = token[1] - ('A' - 10); + else if (token[1] >= 'a' && token[1] <= 'f') + c = token[1] - ('a' - 10); + else if (token[1] == '\r' || token[1] == '\n') + { + /* =\n means ignore the newline. */ + if (token[1] == '\r' && token[2] == '\n') + ; /* swallow all three chars */ + else + { + in--; /* put the third char back */ + length++; + } + continue; + } + else + { + /* = followed by something other than hex or newline - + pass it through unaltered, I guess. (But, if + this bogus token happened to occur over a buffer + boundary, we can't do this, since we don't have + space for it. Oh well. Screw it.) */ + if (in > out) *out++ = token[0]; + if (in > out) *out++ = token[1]; + if (in > out) *out++ = token[2]; + continue; + } + + /* Second hex digit */ + c = (c << 4); + if (token[2] >= '0' && token[2] <= '9') + c += token[2] - '0'; + else if (token[2] >= 'A' && token[2] <= 'F') + c += token[2] - ('A' - 10); + else if (token[2] >= 'a' && token[2] <= 'f') + c += token[2] - ('a' - 10); + else + { + /* We got =xy where "x" was hex and "y" was not, so + treat that as a literal "=", x, and y. (But, if + this bogus token happened to occur over a buffer + boundary, we can't do this, since we don't have + space for it. Oh well. Screw it.) */ + if (in > out) *out++ = token[0]; + if (in > out) *out++ = token[1]; + if (in > out) *out++ = token[2]; + continue; + } + + *out++ = c ? (char) c : ((treatNullAsSpace) ? ' ' : (char) c); + } + else + { + *out++ = token[0]; + + token[0] = token[1]; + token[1] = token[2]; + i = 2; + } + } + + // Fill the size + if (outSize) + *outSize = out - buffer; + + /* Now that we've altered the data in place, write it. */ + if (out > buffer) + return data->write_buffer (buffer, (out - buffer), data->closure); + else + return 1; +} + + +static int +mime_decode_base64_token (const char *in, char *out) +{ + /* reads 4, writes 0-3. Returns bytes written. + (Writes less than 3 only at EOF.) */ + int j; + int eq_count = 0; + unsigned long num = 0; + + for (j = 0; j < 4; j++) + { + unsigned char c = 0; + if (in[j] >= 'A' && in[j] <= 'Z') c = in[j] - 'A'; + else if (in[j] >= 'a' && in[j] <= 'z') c = in[j] - ('a' - 26); + else if (in[j] >= '0' && in[j] <= '9') c = in[j] - ('0' - 52); + else if (in[j] == '+') c = 62; + else if (in[j] == '/') c = 63; + else if (in[j] == '=') c = 0, eq_count++; + else + NS_ERROR("Invalid character"); + num = (num << 6) | c; + } + + *out++ = (char) (num >> 16); + *out++ = (char) ((num >> 8) & 0xFF); + *out++ = (char) (num & 0xFF); + + if (eq_count == 0) + return 3; /* No "=" padding means 4 bytes mapped to 3. */ + else if (eq_count == 1) + return 2; /* "xxx=" means 3 bytes mapped to 2. */ + else if (eq_count == 2) + return 1; /* "xx==" means 2 bytes mapped to 1. */ + else + { + // "x===" can't happen, because "x" would then be encoding only + // 6 bits, not the min of 8. + NS_ERROR("Count is 6 bits, should be at least 8"); + return 1; + } +} + + +static int +mime_decode_base64_buffer (MimeDecoderData *data, + const char *buffer, int32_t length, int32_t *outSize) +{ + /* Warning, we are overwriting the buffer which was passed in. + This is ok, because decoding these formats will never result + in larger data than the input, only smaller. */ + const char *in = buffer; + char *out = (char *) buffer; + char token [4]; + int i; + bool leftover = (data->token_size > 0); + + NS_ASSERTION(data->encoding == mime_Base64, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + /* For the first pass, initialize the token from the unread-buffer. */ + i = 0; + while (i < 4 && data->token_size > 0) + { + token [i] = data->token[i]; + data->token_size--; + i++; + } + + while (length > 0) + { + while (i < 4 && length > 0) + { + if ((*in >= 'A' && *in <= 'Z') || + (*in >= 'a' && *in <= 'z') || + (*in >= '0' && *in <= '9') || + *in == '+' || *in == '/' || *in == '=') + token [i++] = *in; + in++; + length--; + } + + if (i < 4) + { + /* Didn't get enough for a complete token. */ + memcpy (data->token, token, i); + data->token_size = i; + length = 0; + break; + } + i = 0; + + if (leftover) + { + /* If there are characters left over from the last time around, + we might not have space in the buffer to do our dirty work + (if there were 2 or 3 left over, then there is only room for + 1 or 2 in the buffer right now, and we need 3.) This is only + a problem for the first chunk in each buffer, so in that + case, just write prematurely. */ + int n; + n = mime_decode_base64_token (token, token); + n = data->write_buffer (token, n, data->closure); + if (n < 0) /* abort */ + return n; + + /* increment buffer so that we don't write the 1 or 2 unused + characters now at the front. */ + buffer = in; + out = (char *) buffer; + + leftover = false; + } + else + { + int n = mime_decode_base64_token (token, out); + /* Advance "out" by the number of bytes just written to it. */ + out += n; + } + } + + if (outSize) + *outSize = out - buffer; + /* Now that we've altered the data in place, write it. */ + if (out > buffer) + return data->write_buffer (buffer, (out - buffer), data->closure); + else + return 1; +} + + +static int +mime_decode_uue_buffer (MimeDecoderData *data, + const char *input_buffer, int32_t input_length, int32_t *outSize) +{ + /* First, copy input_buffer into state->line_buffer until we have + a complete line. + + Then decode that line in place (in the line_buffer) and write + it out. + + Then pull the next line into line_buffer and continue. + */ + if (!data->line_buffer) + { + data->line_buffer_size = 128; + data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size); + if (!data->line_buffer) + return -1; + data->line_buffer[0] = 0; + } + + int status = 0; + char *line = data->line_buffer; + char *line_end = data->line_buffer + data->line_buffer_size - 1; + + NS_ASSERTION(data->encoding == mime_uuencode, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (data->encoding != mime_uuencode) return -1; + + if (data->ds_state == DS_END) + { + status = 0; + goto DONE; + } + + while (input_length > 0) + { + /* Copy data from input_buffer to `line' until we have a complete line, + or until we've run out of input. + + (line may have data in it already if the last time we were called, + we weren't called with a buffer that ended on a line boundary.) + */ + { + char *out = line + strlen(line); + while (input_length > 0 && + out < line_end) + { + *out++ = *input_buffer++; + input_length--; + + if (out[-1] == '\r' || out[-1] == '\n') + { + /* If we just copied a CR, and an LF is waiting, grab it too. + */ + if (out[-1] == '\r' && + input_length > 0 && + *input_buffer == '\n') + input_buffer++, input_length--; + + /* We have a line. */ + break; + } + } + *out = 0; + + /* Ignore blank lines. + */ + if (*line == '\r' || *line == '\n') + { + *line = 0; + continue; + } + + /* If this line was bigger than our buffer, truncate it. + (This means the data was way corrupted, and there's basically + no chance of decoding it properly, but give it a shot anyway.) + */ + if (out == line_end) + { + out--; + out[-1] = '\r'; + out[0] = 0; + } + + /* If we didn't get a complete line, simply return; we'll be called + with the rest of this line next time. + */ + if (out[-1] != '\r' && out[-1] != '\n') + { + NS_ASSERTION (input_length == 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + break; + } + } + + + /* Now we have a complete line. Deal with it. + */ + + + if (data->ds_state == DS_BODY && + line[0] == 'e' && + line[1] == 'n' && + line[2] == 'd' && + (line[3] == '\r' || + line[3] == '\n')) + { + /* done! */ + data->ds_state = DS_END; + *line = 0; + break; + } + else if (data->ds_state == DS_BEGIN) + { + if (!strncmp (line, "begin ", 6)) + data->ds_state = DS_BODY; + *line = 0; + continue; + } + else + { + /* We're in DS_BODY. Decode the line. */ + char *in, *out; + int32_t i; + long lost; + + NS_ASSERTION (data->ds_state == DS_BODY, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + /* We map down `line', reading four bytes and writing three. + That means that `out' always stays safely behind `in'. + */ + in = line; + out = line; + +# undef DEC +# define DEC(c) (((c) - ' ') & 077) + i = DEC (*in); /* get length */ + + /* all the parens and casts are because gcc was doing something evil. + */ + lost = ((long) i) - (((((long) strlen (in)) - 2L) * 3L) / 4L); + + if (lost > 0) /* Short line!! */ + { + /* If we get here, then the line is shorter than the length byte + at the beginning says it should be. However, the case where + the line is short because it was at the end of the buffer and + we didn't get the whole line was handled earlier (up by the + "didn't get a complete line" comment.) So if we've gotten + here, then this is a complete line which is internally + inconsistent. We will parse from it what we can... + + This probably happened because some gateway stripped trailing + whitespace from the end of the line -- so pretend the line + was padded with spaces (which map to \000.) + */ + i -= lost; + } + + for (++in; i > 0; in += 4, i -= 3) + { + char ch; + NS_ASSERTION(out <= in, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (i >= 3) + { + /* We read four; write three. */ + ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4; + *out++ = ch; + + NS_ASSERTION(out <= in+1, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2; + *out++ = ch; + + NS_ASSERTION(out <= in+2, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + ch = DEC (in[2]) << 6 | DEC (in[3]); + *out++ = ch; + + NS_ASSERTION(out <= in+3, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + else + { + /* Handle a line that isn't a multiple of 4 long. + (We read 1, 2, or 3, and will write 1 or 2.) + */ + NS_ASSERTION (i > 0 && i < 3, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4; + *out++ = ch; + + NS_ASSERTION(out <= in+1, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (i == 2) + { + ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2; + *out++ = ch; + + NS_ASSERTION(out <= in+2, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + } + } + + /* If the line was truncated, pad the missing bytes with 0 (SPC). */ + while (lost > 0) + { + *out++ = 0; + lost--; + in = out+1; /* just to prevent the assert, below. */ + } +# undef DEC + + /* Now write out what we decoded for this line. + */ + NS_ASSERTION(out >= line && out < in, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (out > line) + status = data->write_buffer (line, (out - line), data->closure); + + // The assertion above tells us this is >= 0 + if (outSize) + *outSize = out - line; + + /* Reset the line so that we don't think it's partial next time. */ + *line = 0; + + if (status < 0) /* abort */ + goto DONE; + } + } + + status = 1; + + DONE: + + return status; +} + +static int +mime_decode_yenc_buffer (MimeDecoderData *data, + const char *input_buffer, int32_t input_length, int32_t *outSize) +{ + /* First, copy input_buffer into state->line_buffer until we have + a complete line. + + Then decode that line in place (in the line_buffer) and write + it out. + + Then pull the next line into line_buffer and continue. + */ + if (!data->line_buffer) + { + data->line_buffer_size = 1000; // let make sure we have plenty of space for the header line + data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size); + if (!data->line_buffer) + return -1; + data->line_buffer[0] = 0; + } + + int status = 0; + char *line = data->line_buffer; + char *line_end = data->line_buffer + data->line_buffer_size - 1; + + NS_ASSERTION(data->encoding == mime_yencode, "wrong decoder!"); + if (data->encoding != mime_yencode) return -1; + + if (data->ds_state == DS_END) + return 0; + + while (input_length > 0) + { + /* Copy data from input_buffer to `line' until we have a complete line, + or until we've run out of input. + + (line may have data in it already if the last time we were called, + we weren't called with a buffer that ended on a line boundary.) + */ + { + char *out = line + strlen(line); + while (input_length > 0 && out < line_end) + { + *out++ = *input_buffer++; + input_length--; + + if (out[-1] == '\r' || out[-1] == '\n') + { + /* If we just copied a CR, and an LF is waiting, grab it too. */ + if (out[-1] == '\r' && + input_length > 0 && + *input_buffer == '\n') + input_buffer++, input_length--; + + /* We have a line. */ + break; + } + } + *out = 0; + + /* Ignore blank lines. */ + if (*line == '\r' || *line == '\n') + { + *line = 0; + continue; + } + + /* If this line was bigger than our buffer, truncate it. + (This means the data was way corrupted, and there's basically + no chance of decoding it properly, but give it a shot anyway.) + */ + if (out == line_end) + { + out--; + out[-1] = '\r'; + out[0] = 0; + } + + /* If we didn't get a complete line, simply return; we'll be called + with the rest of this line next time. + */ + if (out[-1] != '\r' && out[-1] != '\n') + { + NS_ASSERTION (input_length == 0, "empty buffer!"); + break; + } + } + + + /* Now we have a complete line. Deal with it. + */ + const char * endOfLine = line + strlen(line); + + if (data->ds_state == DS_BEGIN) + { + int new_line_size = 0; + /* this yenc decoder does not support yenc v2 or multipart yenc. + Therefore, we are looking first for "=ybegin line=" + */ + if ((endOfLine - line) >= 13 && !strncmp (line, "=ybegin line=", 13)) + { + /* ...then couple digits. */ + for (line += 13; line < endOfLine; line ++) + { + if (*line < '0' || *line > '9') + break; + new_line_size = (new_line_size * 10) + *line - '0'; + } + + /* ...next, look for <space>size= */ + if ((endOfLine - line) >= 6 && !strncmp (line, " size=", 6)) + { + /* ...then couple digits. */ + for (line += 6; line < endOfLine; line ++) + if (*line < '0' || *line > '9') + break; + + /* ...next, look for <space>name= */ + if ((endOfLine - line) >= 6 && !strncmp (line, " name=", 6)) + { + /* we have found the yenc header line. + Now check if we need to grow our buffer line + */ + data->ds_state = DS_BODY; + if (new_line_size > data->line_buffer_size && new_line_size <= 997) /* don't let bad value hurt us! */ + { + PR_Free(data->line_buffer); + data->line_buffer_size = new_line_size + 4; //extra chars for line ending and potential escape char + data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size); + if (!data->line_buffer) + return -1; + } + } + } + + } + *data->line_buffer = 0; + continue; + } + + if (data->ds_state == DS_BODY && line[0] == '=') + { + /* look if this this the final line */ + if (!strncmp (line, "=yend size=", 11)) + { + /* done! */ + data->ds_state = DS_END; + *line = 0; + break; + } + } + + /* We're in DS_BODY. Decode the line in place. */ + { + char *src = line; + char *dest = src; + char c; + for (; src < line_end; src ++) + { + c = *src; + if (!c || c == '\r' || c == '\n') + break; + + if (c == '=') + { + src++; + c = *src; + if (c == 0) + return -1; /* last character cannot be escape char */ + c -= 64; + } + c -= 42; + *dest = c; + dest ++; + } + + // The assertion below is helpful, too + if (outSize) + *outSize = dest - line; + + /* Now write out what we decoded for this line. */ + NS_ASSERTION(dest >= line && dest <= src, "nothing to write!"); + if (dest > line) + { + status = data->write_buffer (line, dest - line, data->closure); + if (status < 0) /* abort */ + return status; + } + + /* Reset the line so that we don't think it's partial next time. */ + *line = 0; + } + } + + return 1; +} + +int +MimeDecoderDestroy (MimeDecoderData *data, bool abort_p) +{ + int status = 0; + /* Flush out the last few buffered characters. */ + if (!abort_p && + data->token_size > 0 && + data->token[0] != '=') + { + if (data->encoding == mime_Base64) + while ((unsigned int)data->token_size < sizeof (data->token)) + data->token [data->token_size++] = '='; + + status = data->write_buffer (data->token, data->token_size, + data->closure); + } + + if (data->line_buffer) + PR_Free(data->line_buffer); + PR_Free (data); + return status; +} + + +static MimeDecoderData * +mime_decoder_init (mime_encoding which, + MimeConverterOutputCallback output_fn, + void *closure) +{ + MimeDecoderData *data = PR_NEW(MimeDecoderData); + if (!data) return 0; + memset(data, 0, sizeof(*data)); + data->encoding = which; + data->write_buffer = output_fn; + data->closure = closure; + data->line_buffer_size = 0; + data->line_buffer = nullptr; + + return data; +} + +MimeDecoderData * +MimeB64DecoderInit (MimeConverterOutputCallback output_fn, void *closure) +{ + return mime_decoder_init (mime_Base64, output_fn, closure); +} + +MimeDecoderData * +MimeQPDecoderInit (MimeConverterOutputCallback output_fn, + void *closure, MimeObject *object) +{ + MimeDecoderData *retData = mime_decoder_init (mime_QuotedPrintable, output_fn, closure); + if (retData) + retData->objectToDecode = object; + return retData; +} + +MimeDecoderData * +MimeUUDecoderInit (MimeConverterOutputCallback output_fn, + void *closure) +{ + return mime_decoder_init (mime_uuencode, output_fn, closure); +} + +MimeDecoderData * +MimeYDecoderInit (MimeConverterOutputCallback output_fn, + void *closure) +{ + return mime_decoder_init (mime_yencode, output_fn, closure); +} + +int +MimeDecoderWrite (MimeDecoderData *data, const char *buffer, int32_t size, + int32_t *outSize) +{ + NS_ASSERTION(data, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!data) return -1; + switch(data->encoding) + { + case mime_Base64: + return mime_decode_base64_buffer (data, buffer, size, outSize); + case mime_QuotedPrintable: + return mime_decode_qp_buffer (data, buffer, size, outSize); + case mime_uuencode: + return mime_decode_uue_buffer (data, buffer, size, outSize); + case mime_yencode: + return mime_decode_yenc_buffer (data, buffer, size, outSize); + default: + NS_ERROR("Invalid decoding"); + return -1; + } +} + + +namespace mozilla { +namespace mailnews { + +MimeEncoder::MimeEncoder(OutputCallback callback, void *closure) +: mCallback(callback), + mClosure(closure), + mCurrentColumn(0) +{} + +class Base64Encoder : public MimeEncoder { + unsigned char in_buffer[3]; + int32_t in_buffer_count; + +public: + Base64Encoder(OutputCallback callback, void *closure) + : MimeEncoder(callback, closure), + in_buffer_count(0) {} + virtual ~Base64Encoder() {} + + virtual nsresult Write(const char *buffer, int32_t size) override; + virtual nsresult Flush() override; + +private: + static void Base64EncodeBits(RangedPtr<char> &out, uint32_t bits); +}; + +nsresult Base64Encoder::Write(const char *buffer, int32_t size) +{ + if (size == 0) + return NS_OK; + else if (size < 0) + { + NS_ERROR("Size is less than 0"); + return NS_ERROR_FAILURE; + } + + // If this input buffer is too small, wait until next time. + if (size < (3 - in_buffer_count)) + { + NS_ASSERTION(size == 1 || size == 2, "Unexpected size"); + in_buffer[in_buffer_count++] = buffer[0]; + if (size == 2) + in_buffer[in_buffer_count++] = buffer[1]; + NS_ASSERTION(in_buffer_count < 3, "Unexpected out buffer size"); + return NS_OK; + } + + + // If there are bytes that were put back last time, take them now. + uint32_t i = in_buffer_count, bits = 0; + if (in_buffer_count > 0) bits = in_buffer[0]; + if (in_buffer_count > 1) bits = (bits << 8) + in_buffer[1]; + in_buffer_count = 0; + + // If this buffer is not a multiple of three, put one or two bytes back. + uint32_t excess = ((size + i) % 3); + if (excess) + { + in_buffer[0] = buffer[size - excess]; + if (excess > 1) + in_buffer [1] = buffer[size - excess + 1]; + in_buffer_count = excess; + size -= excess; + NS_ASSERTION (! ((size + i) % 3), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + + const uint8_t *in = (const uint8_t *)buffer; + const uint8_t *end = (const uint8_t *)(buffer + size); + MOZ_ASSERT((end - in + i) % 3 == 0, "Need a multiple of 3 bytes to decode"); + + // Populate the out_buffer with base64 data, one line at a time. + char out_buffer[80]; // Max line length will be 80, so this is safe. + RangedPtr<char> out(out_buffer); + while (in < end) + { + // Accumulate the input bits. + while (i < 3) + { + bits = (bits << 8) | *in++; + i++; + } + i = 0; + + Base64EncodeBits(out, bits); + + mCurrentColumn += 4; + if (mCurrentColumn >= 72) + { + // Do a linebreak before column 76. Flush out the line buffer. + mCurrentColumn = 0; + *out++ = '\x0D'; + *out++ = '\x0A'; + nsresult rv = mCallback(out_buffer, (out.get() - out_buffer), mClosure); + NS_ENSURE_SUCCESS(rv, rv); + out = out_buffer; + } + } + + // Write out the unwritten portion of the last line buffer. + if (out.get() > out_buffer) + { + nsresult rv = mCallback(out_buffer, out.get() - out_buffer, mClosure); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult Base64Encoder::Flush() +{ + if (in_buffer_count == 0) + return NS_OK; + + // Since we need to some buffering to get a multiple of three bytes on each + // block, there may be a few bytes left in the buffer after the last block has + // been written. We need to flush those out now. + char buf[4]; + RangedPtr<char> out(buf); + uint32_t bits = ((uint32_t)in_buffer[0]) << 16; + if (in_buffer_count > 1) + bits |= (((uint32_t)in_buffer[1]) << 8); + + Base64EncodeBits(out, bits); + + // Pad with equal-signs. + if (in_buffer_count == 1) + buf[2] = '='; + buf[3] = '='; + + return mCallback(buf, 4, mClosure); +} + +void Base64Encoder::Base64EncodeBits(RangedPtr<char> &out, uint32_t bits) +{ + // Convert 3 bytes to 4 base64 bytes + for (int32_t j = 18; j >= 0; j -= 6) + { + unsigned int k = (bits >> j) & 0x3F; + if (k < 26) *out++ = k + 'A'; + else if (k < 52) *out++ = k - 26 + 'a'; + else if (k < 62) *out++ = k - 52 + '0'; + else if (k == 62) *out++ = '+'; + else if (k == 63) *out++ = '/'; + else MOZ_CRASH("6 bits should only be between 0 and 64"); + } +} + +class QPEncoder : public MimeEncoder { +public: + QPEncoder(OutputCallback callback, void *closure) + : MimeEncoder(callback, closure) {} + virtual ~QPEncoder() {} + + virtual nsresult Write(const char *buffer, int32_t size) override; +}; + +nsresult QPEncoder::Write(const char *buffer, int32_t size) +{ + nsresult rv = NS_OK; + static const char *hexdigits = "0123456789ABCDEF"; + char out_buffer[80]; + RangedPtr<char> out(out_buffer); + bool white = false; + + // Populate the out_buffer with quoted-printable data, one line at a time. + const uint8_t *in = (uint8_t *)buffer; + const uint8_t *end = in + size; + for (; in < end; in++) + { + if (*in == '\r' || *in == '\n') + { + // If it's CRLF, swallow two chars instead of one. + if (in + 1 < end && in[0] == '\r' && in[1] == '\n') + in++; + + // Whitespace cannot be allowed to occur at the end of the line, so we + // back up and replace the whitespace with its code. + if (white) + { + out--; + char whitespace_char = *out; + *out++ = '='; + *out++ = hexdigits[whitespace_char >> 4]; + *out++ = hexdigits[whitespace_char & 0xF]; + } + + // Now write out the newline. + *out++ = '\r'; + *out++ = '\n'; + white = false; + + rv = mCallback(out_buffer, out.get() - out_buffer, mClosure); + NS_ENSURE_SUCCESS(rv, rv); + out = out_buffer; + mCurrentColumn = 0; + } + else if (mCurrentColumn == 0 && *in == '.') + { + // Just to be SMTP-safe, if "." appears in column 0, encode it. + goto HEX; + } + else if (mCurrentColumn == 0 && *in == 'F' + && (in >= end-1 || in[1] == 'r') + && (in >= end-2 || in[2] == 'o') + && (in >= end-3 || in[3] == 'm') + && (in >= end-4 || in[4] == ' ')) + { + // If this line begins with "From " (or it could but we don't have enough + // data in the buffer to be certain), encode the 'F' in hex to avoid + // potential problems with BSD mailbox formats. + goto HEX; + } + else if ((*in >= 33 && *in <= 60) | + (*in >= 62 && *in <= 126)) // Printable characters except for '=' + { + white = false; + *out++ = *in; + mCurrentColumn++; + } + else if (*in == ' ' || *in == '\t') // Whitespace + { + white = true; + *out++ = *in; + mCurrentColumn++; + } + else + { + // Encode the characters here +HEX: + white = false; + *out++ = '='; + *out++ = hexdigits[*in >> 4]; + *out++ = hexdigits[*in & 0xF]; + mCurrentColumn += 3; + } + + MOZ_ASSERT(mCurrentColumn <= 76, "Why haven't we added a line break yet?"); + + if (mCurrentColumn >= 73) // Soft line break for readability + { + *out++ = '='; + *out++ = '\r'; + *out++ = '\n'; + + rv = mCallback(out_buffer, out.get() - out_buffer, mClosure); + NS_ENSURE_SUCCESS(rv, rv); + out = out_buffer; + white = false; + mCurrentColumn = 0; + } + } + + // Write out the unwritten portion of the last line buffer. + if (out.get() != out_buffer) + { + rv = mCallback(out_buffer, out.get() - out_buffer, mClosure); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +MimeEncoder *MimeEncoder::GetBase64Encoder(OutputCallback callback, + void *closure) +{ + return new Base64Encoder(callback, closure); +} + +MimeEncoder *MimeEncoder::GetQPEncoder(OutputCallback callback, void *closure) +{ + return new QPEncoder(callback, closure); +} + +} // namespace mailnews +} // namespace mozilla diff --git a/mailnews/mime/src/mimeeobj.cpp b/mailnews/mime/src/mimeeobj.cpp new file mode 100644 index 000000000..7d8b2f8a6 --- /dev/null +++ b/mailnews/mime/src/mimeeobj.cpp @@ -0,0 +1,246 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "mimeeobj.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "mimemapl.h" +#include "nsMimeTypes.h" + + +#define MIME_SUPERCLASS mimeLeafClass +MimeDefClass(MimeExternalObject, MimeExternalObjectClass, + mimeExternalObjectClass, &MIME_SUPERCLASS); + +static int MimeExternalObject_initialize (MimeObject *); +static void MimeExternalObject_finalize (MimeObject *); +static int MimeExternalObject_parse_begin (MimeObject *); +static int MimeExternalObject_parse_buffer (const char *, int32_t, MimeObject *); +static int MimeExternalObject_parse_line (const char *, int32_t, MimeObject *); +static int MimeExternalObject_parse_decoded_buffer (const char*, int32_t, MimeObject*); +static bool MimeExternalObject_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +static int +MimeExternalObjectClassInitialize(MimeExternalObjectClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeLeafClass *lclass = (MimeLeafClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeExternalObject_initialize; + oclass->finalize = MimeExternalObject_finalize; + oclass->parse_begin = MimeExternalObject_parse_begin; + oclass->parse_buffer = MimeExternalObject_parse_buffer; + oclass->parse_line = MimeExternalObject_parse_line; + oclass->displayable_inline_p = MimeExternalObject_displayable_inline_p; + lclass->parse_decoded_buffer = MimeExternalObject_parse_decoded_buffer; + return 0; +} + + +static int +MimeExternalObject_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeExternalObject_finalize (MimeObject *object) +{ + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + + +static int +MimeExternalObject_parse_begin (MimeObject *obj) +{ + int status; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + // If we're writing this object, and we're doing it in raw form, then + // now is the time to inform the backend what the type of this data is. + // + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + !obj->options->state->first_data_written_p) + { + 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"); + } + + // + // If we're writing this object as HTML, do all the work now -- just write + // out a table with a link in it. (Later calls to the `parse_buffer' method + // will simply discard the data of the object itself.) + // + if (obj->options && + obj->output_p && + obj->options->write_html_p && + obj->options->output_fn) + { + MimeDisplayOptions newopt = *obj->options; // copy it + char *id = 0; + char *id_url = 0; + char *id_name = 0; + nsCString id_imap; + bool all_headers_p = obj->options->headers == MimeHeadersAll; + + id = mime_part_address (obj); + if (obj->options->missing_parts) + id_imap.Adopt(mime_imap_part_address (obj)); + if (! id) return MIME_OUT_OF_MEMORY; + + if (obj->options && obj->options->url) + { + const char *url = obj->options->url; + if (!id_imap.IsEmpty() && id) + { + // if this is an IMAP part. + id_url = mime_set_url_imap_part(url, id_imap.get(), id); + } + else + { + // This is just a normal MIME part as usual. + id_url = mime_set_url_part(url, id, true); + } + if (!id_url) + { + PR_Free(id); + return MIME_OUT_OF_MEMORY; + } + } + if (!strcmp (id, "0")) + { + PR_Free(id); + id = MimeGetStringByID(MIME_MSG_ATTACHMENT); + } + else + { + const char *p = "Part "; + uint32_t slen = strlen(p) + strlen(id) + 1; + char *s = (char *)PR_MALLOC(slen); + if (!s) + { + PR_Free(id); + PR_Free(id_url); + return MIME_OUT_OF_MEMORY; + } + // we have a valid id + if (id) + id_name = mime_find_suggested_name_of_part(id, obj); + PL_strncpyz(s, p, slen); + PL_strcatn(s, slen, id); + PR_Free(id); + id = s; + } + + if (all_headers_p && + // Don't bother showing all headers on this part if it's the only + // part in the message: in that case, we've already shown these + // headers. + obj->options->state && + obj->options->state->root == obj->parent) + all_headers_p = false; + + newopt.fancy_headers_p = true; + newopt.headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome); + +/****** +RICHIE SHERRY +GOTTA STILL DO THIS FOR QUOTING! + status = MimeHeaders_write_attachment_box (obj->headers, &newopt, + obj->content_type, + obj->encoding, + id_name? id_name : id, id_url, 0) +*****/ + + // obj->options really owns the storage for this. + newopt.part_to_load = nullptr; + newopt.default_charset = nullptr; + PR_FREEIF(id); + PR_FREEIF(id_url); + PR_FREEIF(id_name); + if (status < 0) return status; + } + + return 0; +} + +static int +MimeExternalObject_parse_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + NS_ASSERTION(!obj->closed_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (obj->closed_p) return -1; + + // Currently, we always want to stream, in order to determine the size of the + // MIME object. + + /* The data will be base64-decoded and passed to + MimeExternalObject_parse_decoded_buffer. */ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_buffer(buffer, size, obj); +} + + +static int +MimeExternalObject_parse_decoded_buffer (const char *buf, int32_t size, + MimeObject *obj) +{ + /* This is called (by MimeLeafClass->parse_buffer) with blocks of data + that have already been base64-decoded. This will only be called in + the case where we're not emitting HTML, and want access to the raw + data itself. + + We override the `parse_decoded_buffer' method provided by MimeLeaf + because, unlike most children of MimeLeaf, we do not want to line- + buffer the decoded data -- we want to simply pass it along to the + backend, without going through our `parse_line' method. + */ + + /* Don't do a roundtrip through XPConnect when we're only interested in + * metadata and size. This includes when we are writing HTML (otherwise, the + * contents of binary attachments will just get dumped into messages when + * reading them) and the JS emitter (which doesn't care about attachment data + * at all). 0 means ok, the caller just checks for negative return value. + */ + if (obj->options && (obj->options->metadata_only || + obj->options->write_html_p)) + return 0; + else + return MimeObject_write(obj, buf, size, true); +} + + +static int +MimeExternalObject_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("This method should never be called (externals do no line buffering)."); + return -1; +} + +static bool +MimeExternalObject_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs) +{ + return false; +} + +#undef MIME_SUPERCLASS +#define MIME_SUPERCLASS mimeExternalObjectClass +MimeDefClass(MimeSuppressedCrypto, MimeSuppressedCryptoClass, + mimeSuppressedCryptoClass, &MIME_SUPERCLASS); + +static int MimeSuppressedCryptoClassInitialize(MimeSuppressedCryptoClass *clazz) { + MimeExternalObjectClass *lclass = (MimeExternalObjectClass *)clazz; + return MimeExternalObjectClassInitialize(lclass); +} diff --git a/mailnews/mime/src/mimeeobj.h b/mailnews/mime/src/mimeeobj.h new file mode 100644 index 000000000..076f8f9bf --- /dev/null +++ b/mailnews/mime/src/mimeeobj.h @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEEOBJ_H_ +#define _MIMEEOBJ_H_ + +#include "mimeleaf.h" + +/* The MimeExternalObject class represents MIME parts which contain data + which cannot be displayed inline -- application/octet-stream and any + other type that is not otherwise specially handled. (This is not to + be confused with MimeExternalBody, which is the handler for the + message/external-object MIME type only.) + */ + +typedef struct MimeExternalObjectClass MimeExternalObjectClass; +typedef struct MimeExternalObject MimeExternalObject; + +struct MimeExternalObjectClass { + MimeLeafClass leaf; +}; + +extern "C" MimeExternalObjectClass mimeExternalObjectClass; + +struct MimeExternalObject { + MimeLeaf leaf; +}; + +#define MimeExternalObjectClassInitializer(ITYPE,CSUPER) \ + { MimeLeafClassInitializer(ITYPE,CSUPER) } + +typedef struct MimeSuppressedCryptoClass MimeSuppressedCryptoClass; +typedef struct MimeSuppressedCrypto MimeSuppressedCrypto; + +struct MimeSuppressedCryptoClass { + MimeExternalObjectClass eobj; +}; + +extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass; + +struct MimeSuppressedCrypto { + MimeExternalObject eobj; +}; + +#define MimeSuppressedCryptoClassInitializer(ITYPE, CSUPER) \ + { MimeExternalObjectClassInitializer(ITYPE, CSUPER) } + +#endif /* _MIMEEOBJ_H_ */ diff --git a/mailnews/mime/src/mimefilt.cpp b/mailnews/mime/src/mimefilt.cpp new file mode 100644 index 000000000..9ea4996b5 --- /dev/null +++ b/mailnews/mime/src/mimefilt.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* mimefilt.c --- test harness for libmime.a + + This program reads a message from stdin and writes the output of the MIME + parser on stdout. + + Parameters can be passed to the parser through the usual URL mechanism: + + mimefilt BASE-URL?headers=all&rot13 < in > out + + Some parameters can't be affected that way, so some additional switches + may be passed on the command line after the URL: + + -fancy whether fancy headers should be generated (default) + + -no-fancy opposite; this uses the headers used in the cases of + FO_SAVE_AS_TEXT or FO_QUOTE_MESSAGE + + -html whether we should convert to HTML (like FO_PRESENT); + this is the default if no ?part= is specified. + + -raw don't convert to HTML (FO_SAVE_AS); + this is the default if a ?part= is specified. + + -outline at the end, print a debugging overview of the MIME structure + + Before any output comes a blurb listing the content-type, charset, and + various other info that would have been put in the generated URL struct. + It's printed to the beginning of the output because otherwise this out- + of-band data would have been lost. (So the output of this program is, + in fact, a raw HTTP response.) + */ + +#include "mimemsg.h" +#include "prglobal.h" + +#include "key.h" +#include "cert.h" +#include "secrng.h" +#include "secmod.h" +#include "pk11func.h" +#include "nsMimeStringResources.h" + +#ifndef XP_UNIX +ERROR! This is a unix-only file for the "mimefilt" standalone program. + This does not go into libmime.a. +#endif + + +static char * +test_file_type (const char *filename, void *stream_closure) +{ + const char *suf = PL_strrchr(filename, '.'); + if (!suf) + return 0; + suf++; + + if (!PL_strcasecmp(suf, "txt") || + !PL_strcasecmp(suf, "text")) + return strdup("text/plain"); + else if (!PL_strcasecmp(suf, "htm") || + !PL_strcasecmp(suf, "html")) + return strdup("text/html"); + else if (!PL_strcasecmp(suf, "gif")) + return strdup("image/gif"); + else if (!PL_strcasecmp(suf, "svg")) + return strdup("image/svg+xml"); + else if (!PL_strcasecmp(suf, "jpg") || + !PL_strcasecmp(suf, "jpeg")) + return strdup("image/jpeg"); + else if (!PL_strcasecmp(suf, "pjpg") || + !PL_strcasecmp(suf, "pjpeg")) + return strdup("image/pjpeg"); + else if (!PL_strcasecmp(suf, "xbm")) + return strdup("image/x-xbitmap"); + else if (!PL_strcasecmp(suf, "xpm")) + return strdup("image/x-xpixmap"); + else if (!PL_strcasecmp(suf, "xwd")) + return strdup("image/x-xwindowdump"); + else if (!PL_strcasecmp(suf, "bmp")) + return strdup("image/x-MS-bmp"); + else if (!PL_strcasecmp(suf, "au")) + return strdup("audio/basic"); + else if (!PL_strcasecmp(suf, "aif") || + !PL_strcasecmp(suf, "aiff") || + !PL_strcasecmp(suf, "aifc")) + return strdup("audio/x-aiff"); + else if (!PL_strcasecmp(suf, "ps")) + return strdup("application/postscript"); + else + return 0; +} + +static int +test_output_fn(char *buf, int32_t size, void *closure) +{ + FILE *out = (FILE *) closure; + if (out) + return fwrite(buf, sizeof(*buf), size, out); + else + return 0; +} + +static int +test_output_init_fn (const char *type, + const char *charset, + const char *name, + const char *x_mac_type, + const char *x_mac_creator, + void *stream_closure) +{ + FILE *out = (FILE *) stream_closure; + fprintf(out, "CONTENT-TYPE: %s", type); + if (charset) + fprintf(out, "; charset=\"%s\"", charset); + if (name) + fprintf(out, "; name=\"%s\"", name); + if (x_mac_type || x_mac_creator) + fprintf(out, "; x-mac-type=\"%s\"; x-mac-creator=\"%s\"", + x_mac_type ? x_mac_type : "", + x_mac_creator ? x_mac_type : ""); + fprintf(out, CRLF CRLF); + return 0; +} + +static void * +test_image_begin(const char *image_url, const char *content_type, + void *stream_closure) +{ + return ((void *) strdup(image_url)); +} + +static void +test_image_end(void *image_closure, int status) +{ + char *url = (char *) image_closure; + if (url) PR_Free(url); +} + +static char * +test_image_make_image_html(void *image_data) +{ + char *url = (char *) image_data; +#if 0 + const char *prefix = "<P><CENTER><IMG SRC=\""; + const char *suffix = "\"></CENTER><P>"; +#else + const char *prefix = ("<P><CENTER><TABLE BORDER=2 CELLPADDING=20" + " BGCOLOR=WHITE>" + "<TR><TD ALIGN=CENTER>" + "an inlined image would have gone here for<BR>"); + const char *suffix = "</TD></TR></TABLE></CENTER><P>"; +#endif + uint32_t buflen = strlen (prefix) + strlen (suffix) + strlen (url) + 20; + char *buf = (char *) PR_MALLOC (buflen); + if (!buf) return 0; + *buf = 0; + PL_strcatn (buf, buflen, prefix); + PL_strcatn (buf, buflen, url); + PL_strcatn (buf, buflen, suffix); + return buf; +} + +static int test_image_write_buffer(const char *buf, int32_t size, void *image_closure) +{ + return 0; +} + +static char * +test_passwd_prompt (PK11SlotInfo *slot, void *wincx) +{ + char buf[2048], *s; + fprintf(stdout, "#### Password required: "); + s = fgets(buf, sizeof(buf)-1, stdin); + if (!s) return s; + if (s[strlen(s)-1] == '\r' || + s[strlen(s)-1] == '\n') + s[strlen(s)-1] = '\0'; + return s; +} + + +int +test(FILE *in, FILE *out, + const char *url, + bool fancy_headers_p, + bool html_p, + bool outline_p, + bool dexlate_p, + bool variable_width_plaintext_p) +{ + int status = 0; + MimeObject *obj = 0; + MimeDisplayOptions *opt = new MimeDisplayOptions; +// memset(opt, 0, sizeof(*opt)); + + if (dexlate_p) html_p = false; + + opt->fancy_headers_p = fancy_headers_p; + opt->headers = MimeHeadersSome; + opt->rot13_p = false; + + status = mime_parse_url_options(url, opt); + if (status < 0) + { + PR_Free(opt); + return MIME_OUT_OF_MEMORY; + } + + opt->url = url; + opt->write_html_p = html_p; + opt->dexlate_p = dexlate_p; + opt->output_init_fn = test_output_init_fn; + opt->output_fn = test_output_fn; + opt->charset_conversion_fn= 0; + opt->rfc1522_conversion_p = false; + opt->file_type_fn = test_file_type; + opt->stream_closure = out; + + opt->image_begin = test_image_begin; + opt->image_end = test_image_end; + opt->make_image_html = test_image_make_image_html; + opt->image_write_buffer = test_image_write_buffer; + + opt->variable_width_plaintext_p = variable_width_plaintext_p; + + obj = mime_new ((MimeObjectClass *)&mimeMessageClass, + (MimeHeaders *) NULL, + MESSAGE_RFC822); + if (!obj) + { + PR_Free(opt); + return MIME_OUT_OF_MEMORY; + } + obj->options = opt; + + status = obj->class->initialize(obj); + if (status >= 0) + status = obj->class->parse_begin(obj); + if (status < 0) + { + PR_Free(opt); + PR_Free(obj); + return MIME_OUT_OF_MEMORY; + } + + while (1) + { + char buf[255]; + int size = fread(buf, sizeof(*buf), sizeof(buf), stdin); + if (size <= 0) break; + status = obj->class->parse_buffer(buf, size, obj); + if (status < 0) + { + mime_free(obj); + PR_Free(opt); + return status; + } + } + + status = obj->class->parse_eof(obj, false); + if (status >= 0) + status = obj->class->parse_end(obj, false); + if (status < 0) + { + mime_free(obj); + PR_Free(opt); + return status; + } + + if (outline_p) + { + fprintf(out, "\n\n" + "###############################################################\n"); + obj->class->debug_print(obj, stderr, 0); + fprintf(out, + "###############################################################\n"); + } + + mime_free (obj); + PR_Free(opt); + return 0; +} + + +static char * +test_cdb_name_cb (void *arg, int vers) +{ + static char f[1024]; + if (vers <= 4) + sprintf(f, "%s/.netscape/cert.db", getenv("HOME")); + else + sprintf(f, "%s/.netscape/cert%d.db", getenv("HOME"), vers); + return f; +} + +static char * +test_kdb_name_cb (void *arg, int vers) +{ + static char f[1024]; + if (vers <= 2) + sprintf(f, "%s/.netscape/key.db", getenv("HOME")); + else + sprintf(f, "%s/.netscape/key%d.db", getenv("HOME"), vers); + return f; +} + +extern void SEC_Init(void); + +int +main (int argc, char **argv) +{ + int32_t i = 1; + char *url = ""; + bool fancy_p = true; + bool html_p = true; + bool outline_p = false; + bool dexlate_p = false; + char filename[1000]; + CERTCertDBHandle *cdb_handle; + SECKEYKeyDBHandle *kdb_handle; + + PR_Init("mimefilt", 24, 1, 0); + + cdb_handle = (CERTCertDBHandle *) calloc(1, sizeof(*cdb_handle)); + + if (SECSuccess != CERT_OpenCertDB(cdb_handle, false, test_cdb_name_cb, NULL)) + CERT_OpenVolatileCertDB(cdb_handle); + CERT_SetDefaultCertDB(cdb_handle); + + RNG_RNGInit(); + + kdb_handle = SECKEY_OpenKeyDB(false, test_kdb_name_cb, NULL); + SECKEY_SetDefaultKeyDB(kdb_handle); + + PK11_SetPasswordFunc(test_passwd_prompt); + + sprintf(filename, "%s/.netscape/secmodule.db", getenv("HOME")); + SECMOD_init(filename); + + SEC_Init(); + + + if (i < argc) + { + if (argv[i][0] == '-') + url = strdup(""); + else + url = argv[i++]; + } + + if (url && + (PL_strstr(url, "?part=") || + PL_strstr(url, "&part="))) + html_p = false; + + while (i < argc) + { + if (!strcmp(argv[i], "-fancy")) + fancy_p = true; + else if (!strcmp(argv[i], "-no-fancy")) + fancy_p = false; + else if (!strcmp(argv[i], "-html")) + html_p = true; + else if (!strcmp(argv[i], "-raw")) + html_p = false; + else if (!strcmp(argv[i], "-outline")) + outline_p = true; + else if (!strcmp(argv[i], "-dexlate")) + dexlate_p = true; + else + { + fprintf(stderr, + "usage: %s [ URL [ -fancy | -no-fancy | -html | -raw | -outline | -dexlate ]]\n" + " < message/rfc822 > output\n", + (PL_strrchr(argv[0], '/') ? + PL_strrchr(argv[0], '/') + 1 : + argv[0])); + i = 1; + goto FAIL; + } + i++; + } + + i = test(stdin, stdout, url, fancy_p, html_p, outline_p, dexlate_p, true); + fprintf(stdout, "\n"); + fflush(stdout); + + FAIL: + + CERT_ClosePermCertDB(cdb_handle); + SECKEY_CloseKeyDB(kdb_handle); + + exit(i); +} diff --git a/mailnews/mime/src/mimehdrs.cpp b/mailnews/mime/src/mimehdrs.cpp new file mode 100644 index 000000000..6d187ed52 --- /dev/null +++ b/mailnews/mime/src/mimehdrs.cpp @@ -0,0 +1,888 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "msgCore.h" +#include "mimei.h" +#include "prmem.h" +#include "prlog.h" +#include "plstr.h" +#include "mimebuf.h" +#include "mimemoz2.h" +#include "nsIMimeEmitter.h" +#include "nsMsgMessageFlags.h" +#include "comi18n.h" +#include "nsMailHeaders.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsMsgI18N.h" +#include "mimehdrs.h" +#include "nsIMIMEHeaderParam.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include <ctype.h> +#include "nsMsgUtils.h" + +// Forward declares... +int32_t MimeHeaders_build_heads_list(MimeHeaders *hdrs); + +void +MimeHeaders_convert_header_value(MimeDisplayOptions *opt, nsCString &value, + bool convert_charset_only) +{ + if (value.IsEmpty()) + return; + + if (convert_charset_only) + { + nsAutoCString output; + ConvertRawBytesToUTF8(value, opt->default_charset, output); + value.Assign(output); + return; + } + + if (opt && opt->rfc1522_conversion_p) + { + nsAutoCString temporary; + MIME_DecodeMimeHeader(value.get(), opt->default_charset, + opt->override_charset, true, temporary); + + if (!temporary.IsEmpty()) + { + value = temporary; + } + } + else + { + // This behavior, though highly unusual, was carefully preserved + // from the previous implementation. It may be that this is dead + // code, in which case opt->rfc1522_conversion_p is no longer + // needed. + value.Truncate(); + } +} + +MimeHeaders * +MimeHeaders_new (void) +{ + MimeHeaders *hdrs = (MimeHeaders *) PR_MALLOC(sizeof(MimeHeaders)); + if (!hdrs) return 0; + + memset(hdrs, 0, sizeof(*hdrs)); + hdrs->done_p = false; + + return hdrs; +} + +void +MimeHeaders_free (MimeHeaders *hdrs) +{ + if (!hdrs) return; + PR_FREEIF(hdrs->all_headers); + PR_FREEIF(hdrs->heads); + PR_FREEIF(hdrs->obuffer); + PR_FREEIF(hdrs->munged_subject); + hdrs->obuffer_fp = 0; + hdrs->obuffer_size = 0; + +# ifdef DEBUG__ + { + int i, size = sizeof(*hdrs); + uint32_t *array = (uint32_t*) hdrs; + for (i = 0; i < (size / sizeof(*array)); i++) + array[i] = (uint32_t) 0xDEADBEEF; + } +# endif /* DEBUG */ + + PR_Free(hdrs); +} + +int +MimeHeaders_parse_line (const char *buffer, int32_t size, MimeHeaders *hdrs) +{ + int status = 0; + int desired_size; + + NS_ASSERTION(hdrs, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); + if (!hdrs) return -1; + + /* Don't try and feed me more data after having fed me a blank line... */ + NS_ASSERTION(!hdrs->done_p, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); + if (hdrs->done_p) return -1; + + if (!buffer || size == 0 || *buffer == '\r' || *buffer == '\n') + { + /* If this is a blank line, we're done. + */ + hdrs->done_p = true; + return MimeHeaders_build_heads_list(hdrs); + } + + /* Tack this data on to the end of our copy. + */ + desired_size = hdrs->all_headers_fp + size + 1; + if (desired_size >= hdrs->all_headers_size) + { + status = mime_GrowBuffer (desired_size, sizeof(char), 255, + &hdrs->all_headers, &hdrs->all_headers_size); + if (status < 0) return status; + } + memcpy(hdrs->all_headers+hdrs->all_headers_fp, buffer, size); + hdrs->all_headers_fp += size; + + return 0; +} + +MimeHeaders * +MimeHeaders_copy (MimeHeaders *hdrs) +{ + MimeHeaders *hdrs2; + if (!hdrs) return 0; + + hdrs2 = (MimeHeaders *) PR_MALLOC(sizeof(*hdrs)); + if (!hdrs2) return 0; + memset(hdrs2, 0, sizeof(*hdrs2)); + + if (hdrs->all_headers) + { + hdrs2->all_headers = (char *) PR_MALLOC(hdrs->all_headers_fp); + if (!hdrs2->all_headers) + { + PR_Free(hdrs2); + return 0; + } + memcpy(hdrs2->all_headers, hdrs->all_headers, hdrs->all_headers_fp); + + hdrs2->all_headers_fp = hdrs->all_headers_fp; + hdrs2->all_headers_size = hdrs->all_headers_fp; + } + + hdrs2->done_p = hdrs->done_p; + + if (hdrs->heads) + { + int i; + hdrs2->heads = (char **) PR_MALLOC(hdrs->heads_size + * sizeof(*hdrs->heads)); + if (!hdrs2->heads) + { + PR_FREEIF(hdrs2->all_headers); + PR_Free(hdrs2); + return 0; + } + hdrs2->heads_size = hdrs->heads_size; + for (i = 0; i < hdrs->heads_size; i++) + { + hdrs2->heads[i] = (hdrs2->all_headers + + (hdrs->heads[i] - hdrs->all_headers)); + } + } + return hdrs2; +} + +int +MimeHeaders_build_heads_list(MimeHeaders *hdrs) +{ + char *s; + char *end; + int i; + NS_ASSERTION(hdrs, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!hdrs) return -1; + + NS_ASSERTION(hdrs->done_p && !hdrs->heads, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!hdrs->done_p || hdrs->heads) + return -1; + + if (hdrs->all_headers_fp == 0) + { + /* Must not have been any headers (we got the blank line right away.) */ + PR_FREEIF (hdrs->all_headers); + hdrs->all_headers_size = 0; + return 0; + } + + /* At this point, we might as well realloc all_headers back down to the + minimum size it must be (it could be up to 1k bigger.) But don't + bother if we're only off by a tiny bit. */ + NS_ASSERTION(hdrs->all_headers_fp <= hdrs->all_headers_size, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (hdrs->all_headers_fp + 60 <= hdrs->all_headers_size) + { + char *ls = (char *)PR_Realloc(hdrs->all_headers, hdrs->all_headers_fp); + if (ls) /* can this ever fail? we're making it smaller... */ + { + hdrs->all_headers = ls; /* in case it got relocated */ + hdrs->all_headers_size = hdrs->all_headers_fp; + } + } + + /* First go through and count up the number of headers in the block. + */ + end = hdrs->all_headers + hdrs->all_headers_fp; + for (s = hdrs->all_headers; s < end; s++) + { + if (s < (end-1) && s[0] == '\r' && s[1] == '\n') /* CRLF -> LF */ + s++; + + if ((s[0] == '\r' || s[0] == '\n') && /* we're at a newline, and */ + (s >= (end-1) || /* we're at EOF, or */ + !(s[1] == ' ' || s[1] == '\t'))) /* next char is nonwhite */ + hdrs->heads_size++; + } + + /* Now allocate storage for the pointers to each of those headers. + */ + hdrs->heads = (char **) PR_MALLOC((hdrs->heads_size + 1) * sizeof(char *)); + if (!hdrs->heads) + return MIME_OUT_OF_MEMORY; + memset(hdrs->heads, 0, (hdrs->heads_size + 1) * sizeof(char *)); + + /* Now make another pass through the headers, and this time, record the + starting position of each header. + */ + + i = 0; + hdrs->heads[i++] = hdrs->all_headers; + s = hdrs->all_headers; + + while (s < end) + { + SEARCH_NEWLINE: + while (s < end && *s != '\r' && *s != '\n') + s++; + + if (s >= end) + break; + + /* If "\r\n " or "\r\n\t" is next, that doesn't terminate the header. */ + else if (s+2 < end && + (s[0] == '\r' && s[1] == '\n') && + (s[2] == ' ' || s[2] == '\t')) + { + s += 3; + goto SEARCH_NEWLINE; + } + /* If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate + the header either. */ + else if (s+1 < end && + (s[0] == '\r' || s[0] == '\n') && + (s[1] == ' ' || s[1] == '\t')) + { + s += 2; + goto SEARCH_NEWLINE; + } + + /* At this point, `s' points before a header-terminating newline. + Move past that newline, and store that new position in `heads'. + */ + if (*s == '\r') + s++; + + if (s >= end) + break; + + if (*s == '\n') + s++; + + if (s < end) + { + NS_ASSERTION(! (i > hdrs->heads_size), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (i > hdrs->heads_size) + return -1; + hdrs->heads[i++] = s; + } + } + + return 0; +} + +char * +MimeHeaders_get (MimeHeaders *hdrs, const char *header_name, + bool strip_p, bool all_p) +{ + int i; + int name_length; + char *result = 0; + + if (!hdrs) return 0; + NS_ASSERTION(header_name, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!header_name) return 0; + + /* Specifying strip_p and all_p at the same time doesn't make sense... */ + NS_ASSERTION(!(strip_p && all_p), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + /* One shouldn't be trying to read headers when one hasn't finished + parsing them yet... but this can happen if the message ended + prematurely, and has no body at all (as opposed to a null body, + which is more normal.) So, if we try to read from the headers, + let's assume that the headers are now finished. If they aren't + in fact finished, then a later attempt to write to them will assert. + */ + if (!hdrs->done_p) + { + int status; + hdrs->done_p = true; + status = MimeHeaders_build_heads_list(hdrs); + if (status < 0) return 0; + } + + if (!hdrs->heads) /* Must not have been any headers. */ + { + NS_ASSERTION(hdrs->all_headers_fp == 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + return 0; + } + + name_length = strlen(header_name); + + for (i = 0; i < hdrs->heads_size; i++) + { + char *head = hdrs->heads[i]; + char *end = (i == hdrs->heads_size-1 + ? hdrs->all_headers + hdrs->all_headers_fp + : hdrs->heads[i+1]); + char *colon, *ocolon; + + NS_ASSERTION(head, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!head) continue; + + /* Quick hack to skip over BSD Mailbox delimiter. */ + if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5)) + continue; + + /* Find the colon. */ + for (colon = head; colon < end; colon++) + if (*colon == ':') break; + + if (colon >= end) continue; + + /* Back up over whitespace before the colon. */ + ocolon = colon; + for (; colon > head && IS_SPACE(colon[-1]); colon--) + ; + + /* If the strings aren't the same length, it doesn't match. */ + if (name_length != colon - head ) + continue; + + /* If the strings differ, it doesn't match. */ + if (PL_strncasecmp(header_name, head, name_length)) + continue; + + /* Otherwise, we've got a match. */ + { + char *contents = ocolon + 1; + char *s; + + /* Skip over whitespace after colon. */ + while (contents < end && IS_SPACE(contents[0])) { + /* Mac or Unix style line break, followed by space or tab. */ + if (contents < (end - 1) && + (contents[0] == '\r' || contents[0] == '\n') && + (contents[1] == ' ' || contents[1] == '\t')) + contents += 2; + /* Windows style line break, followed by space or tab. */ + else if (contents < (end - 2) && + contents[0] == '\r' && contents[1] == '\n' && + (contents[2] == ' ' || contents[2] == '\t')) + contents += 3; + /* Any space or tab. */ + else if (contents[0] == ' ' || contents[0] == '\t') + contents++; + /* If we get here, it's because this character is a line break + followed by non-whitespace, or a line break followed by + another line break + */ + else { + end = contents; + break; + } + } + + /* If we're supposed to strip at the first token, pull `end' back to + the first whitespace or ';' after the first token. + */ + if (strip_p) + { + for (s = contents; + s < end && *s != ';' && *s != ',' && !IS_SPACE(*s); + s++) + ; + end = s; + } + + /* Now allocate some storage. + If `result' already has a value, enlarge it. + Otherwise, just allocate a block. + `s' gets set to the place where the new data goes. + */ + if (!result) + { + result = (char *) PR_MALLOC(end - contents + 1); + if (!result) + return 0; + s = result; + } + else + { + int32_t L = strlen(result); + s = (char *) PR_Realloc(result, (L + (end - contents + 10))); + if (!s) + { + PR_Free(result); + return 0; + } + result = s; + s = result + L; + + /* Since we are tacking more data onto the end of the header + field, we must make it be a well-formed continuation line, + by separating the old and new data with CR-LF-TAB. + */ + *s++ = ','; /* #### only do this for addr headers? */ + *s++ = MSG_LINEBREAK[0]; +# if (MSG_LINEBREAK_LEN == 2) + *s++ = MSG_LINEBREAK[1]; +# endif + *s++ = '\t'; + } + + + /* Take off trailing whitespace... */ + while (end > contents && IS_SPACE(end[-1])) + end--; + + if (end > contents) + { + /* Now copy the header's contents in... + */ + memcpy(s, contents, end - contents); + s[end - contents] = 0; + } + else + { + s[0] = 0; + } + + /* If we only wanted the first occurence of this header, we're done. */ + if (!all_p) break; + } + } + + if (result && !*result) /* empty string */ + { + PR_Free(result); + return 0; + } + + return result; +} + +char * +MimeHeaders_get_parameter (const char *header_value, const char *parm_name, + char **charset, char **language) +{ + if (!header_value || !parm_name || !*header_value || !*parm_name) + return nullptr; + + nsresult rv; + nsCOMPtr <nsIMIMEHeaderParam> mimehdrpar = + do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); + + if (NS_FAILED(rv)) + return nullptr; + + nsCString result; + rv = mimehdrpar->GetParameterInternal(header_value, parm_name, charset, + language, getter_Copies(result)); + return NS_SUCCEEDED(rv) ? PL_strdup(result.get()) : nullptr; +} + +#define MimeHeaders_write(HDRS,OPT,DATA,LENGTH) \ + MimeOptions_write((HDRS), (OPT), (DATA), (LENGTH), true); + + +#define MimeHeaders_grow_obuffer(hdrs, desired_size) \ + ((((long) (desired_size)) >= ((long) (hdrs)->obuffer_size)) ? \ + mime_GrowBuffer ((desired_size), sizeof(char), 255, \ + &(hdrs)->obuffer, &(hdrs)->obuffer_size) \ + : 0) + +int +MimeHeaders_write_all_headers (MimeHeaders *hdrs, MimeDisplayOptions *opt, bool attachment) +{ + int status = 0; + int i; + bool wrote_any_p = false; + + NS_ASSERTION(hdrs, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!hdrs) + return -1; + + /* One shouldn't be trying to read headers when one hasn't finished + parsing them yet... but this can happen if the message ended + prematurely, and has no body at all (as opposed to a null body, + which is more normal.) So, if we try to read from the headers, + let's assume that the headers are now finished. If they aren't + in fact finished, then a later attempt to write to them will assert. + */ + if (!hdrs->done_p) + { + hdrs->done_p = true; + status = MimeHeaders_build_heads_list(hdrs); + if (status < 0) return 0; + } + + char *charset = nullptr; + if (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs) + { + if (opt->override_charset) + charset = PL_strdup(opt->default_charset); + else + { + char *contentType = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (contentType) + charset = MimeHeaders_get_parameter(contentType, HEADER_PARM_CHARSET, nullptr, nullptr); + PR_FREEIF(contentType); + } + } + + for (i = 0; i < hdrs->heads_size; i++) + { + char *head = hdrs->heads[i]; + char *end = (i == hdrs->heads_size-1 + ? hdrs->all_headers + hdrs->all_headers_fp + : hdrs->heads[i+1]); + char *colon, *ocolon; + char *contents = end; + + /* Hack for BSD Mailbox delimiter. */ + if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5)) + { + /* For now, we don't really want this header to be output so + we are going to just continue */ + continue; + /* colon = head + 4; contents = colon + 1; */ + } + else + { + /* Find the colon. */ + for (colon = head; colon < end && *colon != ':'; colon++) + ; + + /* Back up over whitespace before the colon. */ + ocolon = colon; + for (; colon > head && IS_SPACE(colon[-1]); colon--) + ; + + contents = ocolon + 1; + } + + /* Skip over whitespace after colon. */ + while (contents < end && IS_SPACE(*contents)) + contents++; + + /* Take off trailing whitespace... */ + while (end > contents && IS_SPACE(end[-1])) + end--; + + nsAutoCString name(Substring(head, colon)); + nsAutoCString hdr_value; + + if ( (end - contents) > 0 ) + { + hdr_value = Substring(contents, end); + } + + // MW Fixme: more? + bool convert_charset_only = + MsgLowerCaseEqualsLiteral(name, "to") || MsgLowerCaseEqualsLiteral(name, "from") || + MsgLowerCaseEqualsLiteral(name, "cc") || MsgLowerCaseEqualsLiteral(name, "bcc") || + MsgLowerCaseEqualsLiteral(name, "reply-to") || MsgLowerCaseEqualsLiteral(name, "sender"); + MimeHeaders_convert_header_value(opt, hdr_value, convert_charset_only); + // if we're saving as html, we need to convert headers from utf8 to message charset, if any + if (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs && charset) + { + nsAutoCString convertedStr; + if (NS_SUCCEEDED(ConvertFromUnicode(charset, NS_ConvertUTF8toUTF16(hdr_value), + convertedStr))) + { + hdr_value = convertedStr; + } + } + + if (attachment) { + if (NS_FAILED(mimeEmitterAddAttachmentField(opt, name.get(), hdr_value.get()))) + status = -1; + } + else { + if (NS_FAILED(mimeEmitterAddHeaderField(opt, name.get(), hdr_value.get()))) + status = -1; + } + + if (status < 0) return status; + if (!wrote_any_p) + wrote_any_p = (status > 0); + } + mimeEmitterAddAllHeaders(opt, hdrs->all_headers, hdrs->all_headers_fp); + PR_FREEIF(charset); + + return 1; +} + +/* Strip CR+LF runs within (original). + Since the string at (original) can only shrink, + this conversion is done in place. (original) + is returned. */ +extern "C" char * +MIME_StripContinuations(char *original) +{ + char *p1, *p2; + + /* If we were given a null string, return it as is */ + if (!original) return NULL; + + /* Start source and dest pointers at the beginning */ + p1 = p2 = original; + + while (*p2) { + /* p2 runs ahead at (CR and/or LF) */ + if ((p2[0] == '\r') || (p2[0] == '\n')) + p2++; + else if (p2 > p1) + *p1++ = *p2++; + else { + p1++; + p2++; + } + } + *p1 = '\0'; + + return original; +} + +extern int16_t INTL_DefaultMailToWinCharSetID(int16_t csid); + +/* Given text purporting to be a qtext header value, strip backslashes that + may be escaping other chars in the string. */ +char * +mime_decode_filename(const char *name, const char *charset, + MimeDisplayOptions *opt) +{ + nsresult rv; + nsCOMPtr <nsIMIMEHeaderParam> mimehdrpar = + do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); + + if (NS_FAILED(rv)) + return nullptr; + nsAutoCString result; + rv = mimehdrpar->DecodeParameter(nsDependentCString(name), charset, + opt ? opt->default_charset : nullptr, + opt ? opt->override_charset : false, + result); + return NS_SUCCEEDED(rv) ? PL_strdup(result.get()) : nullptr; +} + +/* Pull the name out of some header or another. Order is: + Content-Disposition: XXX; filename=NAME (RFC 1521/1806) + Content-Type: XXX; name=NAME (RFC 1341) + Content-Name: NAME (no RFC, but seen to occur) + X-Sun-Data-Name: NAME (no RFC, but used by MailTool) + */ +char * +MimeHeaders_get_name(MimeHeaders *hdrs, MimeDisplayOptions *opt) +{ + char *s = 0, *name = 0, *cvt = 0; + char *charset = nullptr; // for RFC2231 support + + s = MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION, false, false); + if (s) + { + name = MimeHeaders_get_parameter(s, HEADER_PARM_FILENAME, &charset, NULL); + PR_Free(s); + } + + if (! name) + { + s = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (s) + { + free(charset); + + name = MimeHeaders_get_parameter(s, HEADER_PARM_NAME, &charset, NULL); + PR_Free(s); + } + } + + if (! name) + name = MimeHeaders_get (hdrs, HEADER_CONTENT_NAME, false, false); + + if (! name) + name = MimeHeaders_get (hdrs, HEADER_X_SUN_DATA_NAME, false, false); + + if (name) + { + /* First remove continuation delimiters (CR+LF+space), then + remove escape ('\\') characters, then attempt to decode + mime-2 encoded-words. The latter two are done in + mime_decode_filename. + */ + MIME_StripContinuations(name); + + /* Argh. What we should do if we want to be robust is to decode qtext + in all appropriate headers. Unfortunately, that would be too scary + at this juncture. So just decode qtext/mime2 here. */ + cvt = mime_decode_filename(name, charset, opt); + + free(charset); + + if (cvt && cvt != name) + { + PR_Free(name); + name = cvt; + } + } + + return name; +} + +#ifdef XP_UNIX +/* This piece of junk is so that I can use BBDB with Mozilla. + = Put bbdb-srv.perl on your path. + = Put bbdb-srv.el on your lisp path. + = Make sure gnudoit (comes with xemacs) is on your path. + = Put (gnuserv-start) in ~/.emacs + = setenv NS_MSG_DISPLAY_HOOK bbdb-srv.perl + */ +void +MimeHeaders_do_unix_display_hook_hack(MimeHeaders *hdrs) +{ + static const char *cmd = 0; + if (!cmd) + { + /* The first time we're invoked, look up the command in the + environment. Use "" as the `no command' tag. */ + cmd = getenv("NS_MSG_DISPLAY_HOOK"); + if (!cmd) + cmd = ""; + } + + /* Invoke "cmd" at the end of a pipe, and give it the headers on stdin. + The command is expected to be safe from hostile input!! + */ + if (cmd && *cmd) + { + FILE *fp = popen(cmd, "w"); + if (fp) + { + fwrite(hdrs->all_headers, 1, hdrs->all_headers_fp, fp); + pclose(fp); + } + } +} +#endif /* XP_UNIX */ + +static void +MimeHeaders_compact (MimeHeaders *hdrs) +{ + NS_ASSERTION(hdrs, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); + if (!hdrs) return; + + PR_FREEIF(hdrs->obuffer); + hdrs->obuffer_fp = 0; + hdrs->obuffer_size = 0; + + /* These really shouldn't have gotten out of whack again. */ + NS_ASSERTION(hdrs->all_headers_fp <= hdrs->all_headers_size && + hdrs->all_headers_fp + 100 > hdrs->all_headers_size, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); +} + +/* Writes the headers as text/plain. + This writes out a blank line after the headers, unless + dont_write_content_type is true, in which case the header-block + is not closed off, and none of the Content- headers are written. + */ +int +MimeHeaders_write_raw_headers (MimeHeaders *hdrs, MimeDisplayOptions *opt, + bool dont_write_content_type) +{ + int status; + + if (hdrs && !hdrs->done_p) + { + hdrs->done_p = true; + status = MimeHeaders_build_heads_list(hdrs); + if (status < 0) return 0; + } + + if (!dont_write_content_type) + { + char nl[] = MSG_LINEBREAK; + if (hdrs) + { + status = MimeHeaders_write(hdrs, opt, hdrs->all_headers, + hdrs->all_headers_fp); + if (status < 0) return status; + } + status = MimeHeaders_write(hdrs, opt, nl, strlen(nl)); + if (status < 0) return status; + } + else if (hdrs) + { + int32_t i; + for (i = 0; i < hdrs->heads_size; i++) + { + char *head = hdrs->heads[i]; + char *end = (i == hdrs->heads_size-1 + ? hdrs->all_headers + hdrs->all_headers_fp + : hdrs->heads[i+1]); + + NS_ASSERTION(head, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); + if (!head) continue; + + /* Don't write out any Content- header. */ + if (!PL_strncasecmp(head, "Content-", 8)) + continue; + + /* Write out this (possibly multi-line) header. */ + status = MimeHeaders_write(hdrs, opt, head, end - head); + if (status < 0) return status; + } + } + + if (hdrs) + MimeHeaders_compact(hdrs); + + return 0; +} + +// XXX Fix this XXX // +char * +MimeHeaders_open_crypto_stamp(void) +{ + return nullptr; +} + +char * +MimeHeaders_finish_open_crypto_stamp(void) +{ + return nullptr; +} + +char * +MimeHeaders_close_crypto_stamp(void) +{ + return nullptr; +} + +char * +MimeHeaders_make_crypto_stamp(bool encrypted_p, + bool signed_p, + bool good_p, + bool unverified_p, + bool close_parent_stamp_p, + const char *stamp_url) +{ + return nullptr; +} diff --git a/mailnews/mime/src/mimehdrs.h b/mailnews/mime/src/mimehdrs.h new file mode 100644 index 000000000..e854633af --- /dev/null +++ b/mailnews/mime/src/mimehdrs.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEHDRS_H_ +#define _MIMEHDRS_H_ + +#include "modlmime.h" + +/* This file defines the interface to message-header parsing and formatting + code, including conversion to HTML. */ + +/* Other structs defined later in this file. + */ + +/* Creation and destruction. + */ +extern MimeHeaders *MimeHeaders_new (void); +//extern void MimeHeaders_free (MimeHeaders *); +//extern MimeHeaders *MimeHeaders_copy (MimeHeaders *); + + +/* Feed this method the raw data from which you would like a header + block to be parsed, one line at a time. Feed it a blank line when + you're done. Returns negative on allocation-related failure. + */ +extern int MimeHeaders_parse_line (const char *buffer, int32_t size, + MimeHeaders *hdrs); + + +/* Converts a MimeHeaders object into HTML, by writing to the provided + output function. + */ +extern int MimeHeaders_write_headers_html (MimeHeaders *hdrs, + MimeDisplayOptions *opt, + bool attachment); + +/* + * Writes all headers to the mime emitter. + */ +extern int +MimeHeaders_write_all_headers (MimeHeaders *, MimeDisplayOptions *, bool); + +/* Writes the headers as text/plain. + This writes out a blank line after the headers, unless + dont_write_content_type is true, in which case the header-block + is not closed off, and none of the Content- headers are written. + */ +extern int MimeHeaders_write_raw_headers (MimeHeaders *hdrs, + MimeDisplayOptions *opt, + bool dont_write_content_type); + + +/* Some crypto-related HTML-generated utility routines. + * XXX This may not be needed. XXX + */ +extern char *MimeHeaders_open_crypto_stamp(void); +extern char *MimeHeaders_finish_open_crypto_stamp(void); +extern char *MimeHeaders_close_crypto_stamp(void); +extern char *MimeHeaders_make_crypto_stamp(bool encrypted_p, + + bool signed_p, + + bool good_p, + + bool unverified_p, + + bool close_parent_stamp_p, + + const char *stamp_url); + +/* Does all the heuristic silliness to find the filename in the given headers. + */ +extern char *MimeHeaders_get_name(MimeHeaders *hdrs, MimeDisplayOptions *opt); + +extern char *mime_decode_filename(const char *name, const char* charset, + MimeDisplayOptions *opt); + +extern "C" char * MIME_StripContinuations(char *original); + +/** + * Convert this value to a unicode string, based on the charset. + */ +extern void MimeHeaders_convert_header_value(MimeDisplayOptions *opt, + nsCString &value, + bool convert_charset_only); +#endif /* _MIMEHDRS_H_ */ diff --git a/mailnews/mime/src/mimei.cpp b/mailnews/mime/src/mimei.cpp new file mode 100644 index 000000000..015830a80 --- /dev/null +++ b/mailnews/mime/src/mimei.cpp @@ -0,0 +1,1910 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#include "nsCOMPtr.h" +#include "mimeobj.h" /* MimeObject (abstract) */ +#include "mimecont.h" /* |--- MimeContainer (abstract) */ +#include "mimemult.h" /* | |--- MimeMultipart (abstract) */ +#include "mimemmix.h" /* | | |--- MimeMultipartMixed */ +#include "mimemdig.h" /* | | |--- MimeMultipartDigest */ +#include "mimempar.h" /* | | |--- MimeMultipartParallel */ +#include "mimemalt.h" /* | | |--- MimeMultipartAlternative */ +#include "mimemrel.h" /* | | |--- MimeMultipartRelated */ +#include "mimemapl.h" /* | | |--- MimeMultipartAppleDouble */ +#include "mimesun.h" /* | | |--- MimeSunAttachment */ +#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/ +#ifdef ENABLE_SMIME +#include "mimemcms.h" /* | | |---MimeMultipartSignedCMS */ +#endif +#include "mimecryp.h" /* | |--- MimeEncrypted (abstract) */ +#ifdef ENABLE_SMIME +#include "mimecms.h" /* | | |--- MimeEncryptedPKCS7 */ +#endif +#include "mimemsg.h" /* | |--- MimeMessage */ +#include "mimeunty.h" /* | |--- MimeUntypedText */ +#include "mimeleaf.h" /* |--- MimeLeaf (abstract) */ +#include "mimetext.h" /* | |--- MimeInlineText (abstract) */ +#include "mimetpla.h" /* | | |--- MimeInlineTextPlain */ +#include "mimethpl.h" /* | | | |--- M.I.TextHTMLAsPlaintext */ +#include "mimetpfl.h" /* | | |--- MimeInlineTextPlainFlowed */ +#include "mimethtm.h" /* | | |--- MimeInlineTextHTML */ +#include "mimethsa.h" /* | | | |--- M.I.TextHTMLSanitized */ +#include "mimeTextHTMLParsed.h" /*| | |--- M.I.TextHTMLParsed */ +#include "mimetric.h" /* | | |--- MimeInlineTextRichtext */ +#include "mimetenr.h" /* | | | |--- MimeInlineTextEnriched */ +/* SUPPORTED VIA PLUGIN | | |--- MimeInlineTextVCard */ +#include "mimeiimg.h" /* | |--- MimeInlineImage */ +#include "mimeeobj.h" /* | |--- MimeExternalObject */ +#include "mimeebod.h" /* |--- MimeExternalBody */ + /* If you add classes here,also add them to mimei.h */ +#include "prlog.h" +#include "prmem.h" +#include "prenv.h" +#include "plstr.h" +#include "prlink.h" +#include "prprf.h" +#include "mimecth.h" +#include "mimebuf.h" +#include "nsIServiceManager.h" +#include "mimemoz2.h" +#include "nsIMimeContentTypeHandler.h" +#include "nsIComponentManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsXPCOMCID.h" +#include "nsISimpleMimeConverter.h" +#include "nsSimpleMimeConverterStub.h" +#include "nsTArray.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include "nsMsgUtils.h" +#include "nsIPrefBranch.h" +#include "mozilla/Preferences.h" +#include "imgLoader.h" + +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgHdr.h" + +using namespace mozilla; + +// forward declaration +void getMsgHdrForCurrentURL(MimeDisplayOptions *opts, nsIMsgDBHdr ** aMsgHdr); + +#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part" +#define EXTERNAL_ATTACHMENT_URL_HEADER "X-Mozilla-External-Attachment-URL" + +/* ========================================================================== + Allocation and destruction + ========================================================================== + */ +static int mime_classinit(MimeObjectClass *clazz); + +/* + * These are the necessary defines/variables for doing + * content type handlers in external plugins. + */ +typedef struct { + char content_type[128]; + bool force_inline_display; +} cthandler_struct; + +nsTArray<cthandler_struct*> *ctHandlerList = NULL; + +/* + * This will return TRUE if the content_type is found in the + * list, FALSE if it is not found. + */ +bool +find_content_type_attribs(const char *content_type, + bool *force_inline_display) +{ + *force_inline_display = false; + if (!ctHandlerList) + return false; + + for (size_t i = 0; i < ctHandlerList->Length(); i++) + { + cthandler_struct *ptr = ctHandlerList->ElementAt(i); + if (PL_strcasecmp(content_type, ptr->content_type) == 0) + { + *force_inline_display = ptr->force_inline_display; + return true; + } + } + + return false; +} + +void +add_content_type_attribs(const char *content_type, + contentTypeHandlerInitStruct *ctHandlerInfo) +{ + cthandler_struct *ptr = NULL; + bool force_inline_display; + + if (find_content_type_attribs(content_type, &force_inline_display)) + return; + + if ( (!content_type) || (!ctHandlerInfo) ) + return; + + if (!ctHandlerList) + ctHandlerList = new nsTArray<cthandler_struct*>(); + + if (!ctHandlerList) + return; + + ptr = (cthandler_struct *) PR_MALLOC(sizeof(cthandler_struct)); + if (!ptr) + return; + + PL_strncpy(ptr->content_type, content_type, sizeof(ptr->content_type)); + ptr->force_inline_display = ctHandlerInfo->force_inline_display; + ctHandlerList->AppendElement(ptr); +} + +/* + * This routine will find all content type handler for a specifc content + * type (if it exists) + */ +bool +force_inline_display(const char *content_type) +{ + bool force_inline_disp; + + find_content_type_attribs(content_type, &force_inline_disp); + return (force_inline_disp); +} + +/* + * This routine will find all content type handler for a specifc content + * type (if it exists) and is defined to the nsRegistry + */ +MimeObjectClass * +mime_locate_external_content_handler(const char *content_type, + contentTypeHandlerInitStruct *ctHandlerInfo) +{ + if (!content_type || !*(content_type)) // null or empty content type + return nullptr; + + MimeObjectClass *newObj = NULL; + nsresult rv; + + nsAutoCString lookupID("@mozilla.org/mimecth;1?type="); + nsAutoCString contentType; + ToLowerCase(nsDependentCString(content_type), contentType); + lookupID += contentType; + + nsCOMPtr<nsIMimeContentTypeHandler> ctHandler = do_CreateInstance(lookupID.get(), &rv); + if (NS_FAILED(rv) || !ctHandler) { + nsCOMPtr<nsICategoryManager> catman = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return nullptr; + + nsCString value; + rv = catman->GetCategoryEntry(NS_SIMPLEMIMECONVERTERS_CATEGORY, + contentType.get(), getter_Copies(value)); + if (NS_FAILED(rv) || value.IsEmpty()) + return nullptr; + rv = MIME_NewSimpleMimeConverterStub(contentType.get(), + getter_AddRefs(ctHandler)); + if (NS_FAILED(rv) || !ctHandler) + return nullptr; + } + + rv = ctHandler->CreateContentTypeHandlerClass(contentType.get(), ctHandlerInfo, &newObj); + if (NS_FAILED(rv)) + return nullptr; + + add_content_type_attribs(contentType.get(), ctHandlerInfo); + return newObj; +} + +/* This is necessary to expose the MimeObject method outside of this DLL */ +int +MIME_MimeObject_write(MimeObject *obj, const char *output, int32_t length, bool user_visible_p) +{ + return MimeObject_write(obj, output, length, user_visible_p); +} + +MimeObject * +mime_new (MimeObjectClass *clazz, MimeHeaders *hdrs, + const char *override_content_type) +{ + int size = clazz->instance_size; + MimeObject *object; + int status; + + /* Some assertions to verify that this isn't random junk memory... */ + NS_ASSERTION(clazz->class_name && strlen(clazz->class_name) > 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + NS_ASSERTION(size > 0 && size < 1000, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (!clazz->class_initialized) + { + status = mime_classinit(clazz); + if (status < 0) return 0; + } + + NS_ASSERTION(clazz->initialize && clazz->finalize, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (hdrs) + { + hdrs = MimeHeaders_copy (hdrs); + if (!hdrs) return 0; + } + + object = (MimeObject *) PR_MALLOC(size); + if (!object) return 0; + + memset(object, 0, size); + object->clazz = clazz; + object->headers = hdrs; + object->dontShowAsAttachment = false; + + if (override_content_type && *override_content_type) + object->content_type = strdup(override_content_type); + + status = clazz->initialize(object); + if (status < 0) + { + clazz->finalize(object); + PR_Free(object); + return 0; + } + + return object; +} + +void +mime_free (MimeObject *object) +{ +# ifdef DEBUG__ + int i, size = object->clazz->instance_size; + uint32_t *array = (uint32_t*) object; +# endif /* DEBUG */ + + object->clazz->finalize(object); + +# ifdef DEBUG__ + for (i = 0; i < (size / sizeof(*array)); i++) + array[i] = (uint32_t) 0xDEADBEEF; +# endif /* DEBUG */ + + PR_Free(object); +} + + +bool mime_is_allowed_class(const MimeObjectClass *clazz, + int32_t types_of_classes_to_disallow) +{ + if (types_of_classes_to_disallow == 0) + return true; + bool avoid_html = (types_of_classes_to_disallow >= 1); + bool avoid_images = (types_of_classes_to_disallow >= 2); + bool avoid_strange_content = (types_of_classes_to_disallow >= 3); + bool allow_only_vanilla_classes = (types_of_classes_to_disallow == 100); + + if (allow_only_vanilla_classes) + /* A "safe" class is one that is unlikely to have security bugs or to + allow security exploits or one that is essential for the usefulness + of the application, even for paranoid users. + What's included here is more personal judgement than following + strict rules, though, unfortunately. + The function returns true only for known good classes, i.e. is a + "whitelist" in this case. + This idea comes from Georgi Guninski. + */ + return + ( + clazz == (MimeObjectClass *)&mimeInlineTextPlainClass || + clazz == (MimeObjectClass *)&mimeInlineTextPlainFlowedClass || + clazz == (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass || + clazz == (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass || + /* The latter 2 classes bear some risk, because they use the Gecko + HTML parser, but the user has the option to make an explicit + choice in this case, via html_as. */ + clazz == (MimeObjectClass *)&mimeMultipartMixedClass || + clazz == (MimeObjectClass *)&mimeMultipartAlternativeClass || + clazz == (MimeObjectClass *)&mimeMultipartDigestClass || + clazz == (MimeObjectClass *)&mimeMultipartAppleDoubleClass || + clazz == (MimeObjectClass *)&mimeMessageClass || + clazz == (MimeObjectClass *)&mimeExternalObjectClass || + /* mimeUntypedTextClass? -- does uuencode */ +#ifdef ENABLE_SMIME + clazz == (MimeObjectClass *)&mimeMultipartSignedCMSClass || + clazz == (MimeObjectClass *)&mimeEncryptedCMSClass || +#endif + clazz == 0 + ); + + /* Contrairy to above, the below code is a "blacklist", i.e. it + *excludes* some "bad" classes. */ + return + !( + (avoid_html + && ( + clazz == (MimeObjectClass *)&mimeInlineTextHTMLParsedClass + /* Should not happen - we protect against that in + mime_find_class(). Still for safety... */ + )) || + (avoid_images + && ( + clazz == (MimeObjectClass *)&mimeInlineImageClass + )) || + (avoid_strange_content + && ( + clazz == (MimeObjectClass *)&mimeInlineTextEnrichedClass || + clazz == (MimeObjectClass *)&mimeInlineTextRichtextClass || + clazz == (MimeObjectClass *)&mimeSunAttachmentClass || + clazz == (MimeObjectClass *)&mimeExternalBodyClass + )) + ); +} + +void getMsgHdrForCurrentURL(MimeDisplayOptions *opts, nsIMsgDBHdr ** aMsgHdr) +{ + *aMsgHdr = nullptr; + + if (!opts) + return; + + mime_stream_data *msd = (mime_stream_data *) (opts->stream_closure); + if (!msd) + return; + + nsCOMPtr<nsIChannel> channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgMessageUrl> msgURI; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + msgURI = do_QueryInterface(uri); + if (msgURI) + { + msgURI->GetMessageHeader(aMsgHdr); + if (*aMsgHdr) + return; + nsCString rdfURI; + msgURI->GetUri(getter_Copies(rdfURI)); + if (!rdfURI.IsEmpty()) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + GetMsgDBHdrFromURI(rdfURI.get(), getter_AddRefs(msgHdr)); + NS_IF_ADDREF(*aMsgHdr = msgHdr); + } + } + } + } + + return; +} + +MimeObjectClass * +mime_find_class (const char *content_type, MimeHeaders *hdrs, + MimeDisplayOptions *opts, bool exact_match_p) +{ + MimeObjectClass *clazz = 0; + MimeObjectClass *tempClass = 0; + contentTypeHandlerInitStruct ctHandlerInfo; + + // Read some prefs + nsIPrefBranch *prefBranch = GetPrefBranch(opts); + int32_t html_as = 0; // def. see below + int32_t types_of_classes_to_disallow = 0; /* Let only a few libmime classes + process incoming data. This protects from bugs (e.g. buffer overflows) + and from security loopholes (e.g. allowing unchecked HTML in some + obscure classes, although the user has html_as > 0). + This option is mainly for the UI of html_as. + 0 = allow all available classes + 1 = Use hardcoded blacklist to avoid rendering (incoming) HTML + 2 = ... and images + 3 = ... and some other uncommon content types + 4 = show all body parts + 100 = Use hardcoded whitelist to avoid even more bugs(buffer overflows). + This mode will limit the features available (e.g. uncommon + attachment types and inline images) and is for paranoid users. + */ + if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer && + opts->format_out != nsMimeOutput::nsMimeMessageDecrypt + && opts->format_out != nsMimeOutput::nsMimeMessageAttach) + if (prefBranch) + { + prefBranch->GetIntPref("mailnews.display.html_as", &html_as); + prefBranch->GetIntPref("mailnews.display.disallow_mime_handlers", + &types_of_classes_to_disallow); + if (types_of_classes_to_disallow > 0 && html_as == 0) + // We have non-sensical prefs. Do some fixup. + html_as = 1; + } + + // First, check to see if the message has been marked as JUNK. If it has, + // then force the message to be rendered as simple, unless this has been + // called by a filtering routine. + bool sanitizeJunkMail = false; + + // it is faster to read the pref first then figure out the msg hdr for the current url only if we have to + // XXX instead of reading this pref every time, part of mime should be an observer listening to this pref change + // and updating internal state accordingly. But none of the other prefs in this file seem to be doing that...=( + if (prefBranch) + prefBranch->GetBoolPref("mail.spam.display.sanitize", &sanitizeJunkMail); + + if (sanitizeJunkMail && + !(opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer)) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr)); + if (msgHdr) + { + nsCString junkScoreStr; + (void) msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + if (html_as == 0 && junkScoreStr.get() && atoi(junkScoreStr.get()) > 50) + html_as = 3; // 3 == Simple HTML + } // if msgHdr + } // if we are supposed to sanitize junk mail + + /* + * What we do first is check for an external content handler plugin. + * This will actually extend the mime handling by calling a routine + * which will allow us to load an external content type handler + * for specific content types. If one is not found, we will drop back + * to the default handler. + */ + if ((tempClass = mime_locate_external_content_handler(content_type, &ctHandlerInfo)) != NULL) + { +#ifdef MOZ_THUNDERBIRD + // This is a case where we only want to add this property if we are a thunderbird build AND + // we have found an external mime content handler for text/calendar + // This will enable iMIP support in Lightning + if ( hdrs && (!PL_strncasecmp(content_type, "text/calendar", 13))) + { + char *full_content_type = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (full_content_type) + { + char *imip_method = MimeHeaders_get_parameter(full_content_type, "method", NULL, NULL); + nsCOMPtr<nsIMsgDBHdr> msgHdr; + getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr)); + if (msgHdr) + msgHdr->SetStringProperty("imip_method", (imip_method) ? imip_method : "nomethod"); + // PR_Free checks for null + PR_Free(imip_method); + PR_Free(full_content_type); + } + } +#endif + + if (types_of_classes_to_disallow > 0 + && (!PL_strncasecmp(content_type, "text/x-vcard", 12)) + ) + /* Use a little hack to prevent some dangerous plugins, which ship + with Mozilla, to run. + For the truely user-installed plugins, we rely on the judgement + of the user. */ + { + if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; // As attachment + } + else + clazz = (MimeObjectClass *)tempClass; + } + else + { + if (!content_type || !*content_type || + !PL_strcasecmp(content_type, "text")) /* with no / in the type */ + clazz = (MimeObjectClass *)&mimeUntypedTextClass; + + /* Subtypes of text... + */ + else if (!PL_strncasecmp(content_type, "text/", 5)) + { + if (!PL_strcasecmp(content_type+5, "html")) + { + if (opts && + (opts->format_out == nsMimeOutput::nsMimeMessageSaveAs || + opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer || + opts->format_out == nsMimeOutput::nsMimeMessageDecrypt || + opts->format_out == nsMimeOutput::nsMimeMessageAttach)) + // SaveAs in new modes doesn't work yet. + { + // Don't use the parsed HTML class if we're ... + // - saving the HTML of a message + // - getting message content for filtering + // - snarfing attachments (nsMimeMessageDecrypt used in SnarfMsgAttachment) + // - processing attachments (like deleting attachments). + clazz = (MimeObjectClass *)&mimeInlineTextHTMLClass; + types_of_classes_to_disallow = 0; + } + else if (html_as == 0 || html_as == 4) // Render sender's HTML + clazz = (MimeObjectClass *)&mimeInlineTextHTMLParsedClass; + else if (html_as == 1) // convert HTML to plaintext + // Do a HTML->TXT->HTML conversion, see mimethpl.h. + clazz = (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass; + else if (html_as == 2) // display HTML source + /* This is for the freaks. Treat HTML as plaintext, + which will cause the HTML source to be displayed. + Not very user-friendly, but some seem to want this. */ + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + else if (html_as == 3) // Sanitize + // Strip all but allowed HTML + clazz = (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass; + else // Goofy pref + /* User has an unknown pref value. Maybe he used a newer Mozilla + with a new alternative to avoid HTML. Defaulting to option 1, + which is less dangerous than defaulting to the raw HTML. */ + clazz = (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass; + } + else if (!PL_strcasecmp(content_type+5, "enriched")) + clazz = (MimeObjectClass *)&mimeInlineTextEnrichedClass; + else if (!PL_strcasecmp(content_type+5, "richtext")) + clazz = (MimeObjectClass *)&mimeInlineTextRichtextClass; + else if (!PL_strcasecmp(content_type+5, "rtf")) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else if (!PL_strcasecmp(content_type+5, "plain")) + { + // Preliminary use the normal plain text + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + + if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer + && opts->format_out != nsMimeOutput::nsMimeMessageAttach + && opts->format_out != nsMimeOutput::nsMimeMessageRaw) + { + bool disable_format_flowed = false; + if (prefBranch) + prefBranch->GetBoolPref("mailnews.display.disable_format_flowed_support", + &disable_format_flowed); + + if(!disable_format_flowed) + { + // Check for format=flowed, damn, it is already stripped away from + // the contenttype! + // Look in headers instead even though it's expensive and clumsy + // First find Content-Type: + char *content_type_row = + (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : 0); + // Then the format parameter if there is one. + // I would rather use a PARAM_FORMAT but I can't find the right + // place to put the define. The others seems to be in net.h + // but is that really really the right place? There is also + // a nsMimeTypes.h but that one isn't included. Bug? + char *content_type_format = + (content_type_row + ? MimeHeaders_get_parameter(content_type_row, "format", NULL,NULL) + : 0); + + if (content_type_format && !PL_strcasecmp(content_type_format, + "flowed")) + clazz = (MimeObjectClass *)&mimeInlineTextPlainFlowedClass; + PR_FREEIF(content_type_format); + PR_FREEIF(content_type_row); + } + } + } + else if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + } + + /* Subtypes of multipart... + */ + else if (!PL_strncasecmp(content_type, "multipart/", 10)) + { + // When html_as is 4, we want all MIME parts of the message to + // show up in the displayed message body, if they are MIME types + // that we know how to display, and also in the attachment pane + // if it's appropriate to put them there. Both + // multipart/alternative and multipart/related play games with + // hiding various MIME parts, and we don't want that to happen, + // so we prevent that by parsing those MIME types as + // multipart/mixed, which won't mess with anything. + // + // When our output format is nsMimeOutput::nsMimeMessageAttach, + // i.e., we are reformatting the message to remove attachments, + // we are in a similar boat. The code for deleting + // attachments properly in that mode is in mimemult.cpp + // functions which are inherited by mimeMultipartMixedClass but + // not by mimeMultipartAlternativeClass or + // mimeMultipartRelatedClass. Therefore, to ensure that + // everything is handled properly, in this context too we parse + // those MIME types as multipart/mixed. + bool basic_formatting = (html_as == 4) || + (opts && opts->format_out == nsMimeOutput::nsMimeMessageAttach); + if (!PL_strcasecmp(content_type+10, "alternative")) + clazz = basic_formatting ? (MimeObjectClass *)&mimeMultipartMixedClass : + (MimeObjectClass *)&mimeMultipartAlternativeClass; + else if (!PL_strcasecmp(content_type+10, "related")) + clazz = basic_formatting ? (MimeObjectClass *)&mimeMultipartMixedClass : + (MimeObjectClass *)&mimeMultipartRelatedClass; + else if (!PL_strcasecmp(content_type+10, "digest")) + clazz = (MimeObjectClass *)&mimeMultipartDigestClass; + else if (!PL_strcasecmp(content_type+10, "appledouble") || + !PL_strcasecmp(content_type+10, "header-set")) + clazz = (MimeObjectClass *)&mimeMultipartAppleDoubleClass; + else if (!PL_strcasecmp(content_type+10, "parallel")) + clazz = (MimeObjectClass *)&mimeMultipartParallelClass; + else if (!PL_strcasecmp(content_type+10, "mixed")) + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; +#ifdef ENABLE_SMIME + else if (!PL_strcasecmp(content_type+10, "signed")) + { + /* Check that the "protocol" and "micalg" parameters are ones we + know about. */ + char *ct = (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : 0); + char *proto = (ct + ? MimeHeaders_get_parameter(ct, PARAM_PROTOCOL, NULL, NULL) + : 0); + char *micalg = (ct + ? MimeHeaders_get_parameter(ct, PARAM_MICALG, NULL, NULL) + : 0); + + if (proto + && ( + (/* is a signature */ + !PL_strcasecmp(proto, APPLICATION_XPKCS7_SIGNATURE) + || + !PL_strcasecmp(proto, APPLICATION_PKCS7_SIGNATURE)) + && micalg + && (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_4) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_5) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD2)))) + clazz = (MimeObjectClass *)&mimeMultipartSignedCMSClass; + else + clazz = 0; + PR_FREEIF(proto); + PR_FREEIF(micalg); + PR_FREEIF(ct); + } +#endif + + if (!clazz && !exact_match_p) + /* Treat all unknown multipart subtypes as "multipart/mixed" */ + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; + + /* If we are sniffing a message, let's treat alternative parts as mixed */ + if (opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer) + if (clazz == (MimeObjectClass *)&mimeMultipartAlternativeClass) + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; + } + + /* Subtypes of message... + */ + else if (!PL_strncasecmp(content_type, "message/", 8)) + { + if (!PL_strcasecmp(content_type+8, "rfc822") || + !PL_strcasecmp(content_type+8, "news")) + clazz = (MimeObjectClass *)&mimeMessageClass; + else if (!PL_strcasecmp(content_type+8, "external-body")) + clazz = (MimeObjectClass *)&mimeExternalBodyClass; + else if (!PL_strcasecmp(content_type+8, "partial")) + /* I guess these are most useful as externals, for now... */ + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else if (!exact_match_p) + /* Treat all unknown message subtypes as "text/plain" */ + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + } + + /* The magic image types which we are able to display internally... + */ + else if (!PL_strncasecmp(content_type, "image/", 6)) { + if (imgLoader::SupportImageWithMimeType(content_type, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) + clazz = (MimeObjectClass *)&mimeInlineImageClass; + else + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + +#ifdef ENABLE_SMIME + else if (!PL_strcasecmp(content_type, APPLICATION_XPKCS7_MIME) + || !PL_strcasecmp(content_type, APPLICATION_PKCS7_MIME)) { + + if (Preferences::GetBool("mailnews.p7m_subparts_external", false) && + opts->is_child) { + // We do not allow encrypted parts except as top level. + // Allowing them would leak the plain text in case the part is + // cleverly hidden and the decrypted content gets included in + // replies and forwards. + clazz = (MimeObjectClass *)&mimeSuppressedCryptoClass; + return clazz; + } + + char *ct = (hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : nullptr); + char *st = (ct ? MimeHeaders_get_parameter(ct, "smime-type", NULL, NULL) + : nullptr); + + /* by default, assume that it is an encrypted message */ + clazz = (MimeObjectClass *)&mimeEncryptedCMSClass; + + /* if the smime-type parameter says that it's a certs-only or + compressed file, then show it as an attachment, however + (MimeEncryptedCMS doesn't handle these correctly) */ + if (st && + (!PL_strcasecmp(st, "certs-only") || + !PL_strcasecmp(st, "compressed-data"))) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else { + /* look at the file extension... less reliable, but still covered + by the S/MIME specification (RFC 3851, section 3.2.1) */ + char *name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr); + if (name) { + char *suf = PL_strrchr(name, '.'); + bool p7mExternal = false; + + if (prefBranch) + prefBranch->GetBoolPref("mailnews.p7m_external", &p7mExternal); + if (suf && + ((!PL_strcasecmp(suf, ".p7m") && p7mExternal) || + !PL_strcasecmp(suf, ".p7c") || + !PL_strcasecmp(suf, ".p7z"))) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + PR_Free(name); + } + PR_Free(st); + PR_Free(ct); + } +#endif + /* A few types which occur in the real world and which we would otherwise + treat as non-text types (which would be bad) without this special-case... + */ + else if (!PL_strcasecmp(content_type, APPLICATION_PGP) || + !PL_strcasecmp(content_type, APPLICATION_PGP2)) + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + + else if (!PL_strcasecmp(content_type, SUN_ATTACHMENT)) + clazz = (MimeObjectClass *)&mimeSunAttachmentClass; + + /* Everything else gets represented as a clickable link. + */ + else if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + + if (!mime_is_allowed_class(clazz, types_of_classes_to_disallow)) + { + /* Do that check here (not after the if block), because we want to allow + user-installed plugins. */ + if(!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else + clazz = 0; + } + } + +#ifdef ENABLE_SMIME + // see bug #189988 + if (opts && opts->format_out == nsMimeOutput::nsMimeMessageDecrypt && + (clazz != (MimeObjectClass *)&mimeEncryptedCMSClass)) { + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } +#endif + + if (!exact_match_p) + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz) return 0; + + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (clazz && !clazz->class_initialized) + { + int status = mime_classinit(clazz); + if (status < 0) return 0; + } + + return clazz; +} + + +MimeObject * +mime_create (const char *content_type, MimeHeaders *hdrs, + MimeDisplayOptions *opts, bool forceInline /* = false */) +{ + /* If there is no Content-Disposition header, or if the Content-Disposition + is ``inline'', then we display the part inline (and let mime_find_class() + decide how.) + + If there is any other Content-Disposition (either ``attachment'' or some + disposition that we don't recognise) then we always display the part as + an external link, by using MimeExternalObject to display it. + + But Content-Disposition is ignored for all containers except `message'. + (including multipart/mixed, and multipart/digest.) It's not clear if + this is to spec, but from a usability standpoint, I think it's necessary. + */ + + MimeObjectClass *clazz = 0; + char *content_disposition = 0; + MimeObject *obj = 0; + char *override_content_type = 0; + + /* We've had issues where the incoming content_type is invalid, of a format: + content_type="=?windows-1252?q?application/pdf" (bug 659355) + We decided to fix that by simply trimming the stuff before the ? + */ + if (content_type) + { + const char *lastQuestion = strrchr(content_type, '?'); + if (lastQuestion) + content_type = lastQuestion + 1; // the substring after the last '?' + } + + /* There are some clients send out all attachments with a content-type + of application/octet-stream. So, if we have an octet-stream attachment, + try to guess what type it really is based on the file extension. I HATE + that we have to do this... + */ + if (hdrs && opts && opts->file_type_fn && + + /* ### mwelch - don't override AppleSingle */ + (content_type ? PL_strcasecmp(content_type, APPLICATION_APPLEFILE) : true) && + /* ## davidm Apple double shouldn't use this #$%& either. */ + (content_type ? PL_strcasecmp(content_type, MULTIPART_APPLEDOUBLE) : true) && + (!content_type || + !PL_strcasecmp(content_type, APPLICATION_OCTET_STREAM) || + !PL_strcasecmp(content_type, UNKNOWN_CONTENT_TYPE))) + { + char *name = MimeHeaders_get_name(hdrs, opts); + if (name) + { + override_content_type = opts->file_type_fn (name, opts->stream_closure); + // appledouble isn't a valid override content type, and makes + // attachments invisible. + if (!PL_strcasecmp(override_content_type, MULTIPART_APPLEDOUBLE)) + override_content_type = nullptr; + PR_FREEIF(name); + + // Workaroung for saving '.eml" file encoded with base64. + // Do not override with message/rfc822 whenever Transfer-Encoding is + // base64 since base64 encoding of message/rfc822 is invalid. + // Our MimeMessageClass has no capability to decode it. + if (!PL_strcasecmp(override_content_type, MESSAGE_RFC822)) { + nsCString encoding; + encoding.Adopt(MimeHeaders_get(hdrs, + HEADER_CONTENT_TRANSFER_ENCODING, + true, false)); + if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + override_content_type = nullptr; + } + + // If we get here and it is not the unknown content type from the + // file name, let's do some better checking not to inline something bad + if (override_content_type && + *override_content_type && + (PL_strcasecmp(override_content_type, UNKNOWN_CONTENT_TYPE))) + content_type = override_content_type; + } + } + + clazz = mime_find_class(content_type, hdrs, opts, false); + + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz) goto FAIL; + + if (opts && opts->part_to_load) + /* Always ignore Content-Disposition when we're loading some specific + sub-part (which may be within some container that we wouldn't otherwise + descend into, if the container itself had a Content-Disposition of + `attachment'. */ + content_disposition = 0; + + else if (mime_subclass_p(clazz,(MimeObjectClass *)&mimeContainerClass) && + !mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass)) + /* Ignore Content-Disposition on all containers except `message'. + That is, Content-Disposition is ignored for multipart/mixed objects, + but is obeyed for message/rfc822 objects. */ + content_disposition = 0; + + else + { + /* Check to see if the plugin should override the content disposition + to make it appear inline. One example is a vcard which has a content + disposition of an "attachment;" */ + if (force_inline_display(content_type)) + NS_MsgSACopy(&content_disposition, "inline"); + else + content_disposition = (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION, true, false) + : 0); + } + + if (!content_disposition || !PL_strcasecmp(content_disposition, "inline")) + ; /* Use the class we've got. */ + else + { + // override messages that have content disposition set to "attachment" + // even though we probably should show them inline. + if ( (clazz != (MimeObjectClass *)&mimeInlineTextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextPlainClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextPlainFlowedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLParsedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextRichtextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextEnrichedClass) && + (clazz != (MimeObjectClass *)&mimeMessageClass) && + (clazz != (MimeObjectClass *)&mimeInlineImageClass) ) { + // not a special inline type, so show as attachment + // 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; + else if (!child->superclass) + return false; + else + return mime_subclass_p(child->superclass, parent); +} + +bool +mime_typep(MimeObject *obj, MimeObjectClass *clazz) +{ + return mime_subclass_p(obj->clazz, clazz); +} + + + +/* URL munging + */ + + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +char * +mime_part_address(MimeObject *obj) +{ + if (!obj->parent) + return strdup("0"); + else + { + /* Find this object in its parent. */ + int32_t i, j = -1; + char buf [20]; + char *higher = 0; + MimeContainer *cont = (MimeContainer *) obj->parent; + NS_ASSERTION(mime_typep(obj->parent, + (MimeObjectClass *)&mimeContainerClass), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + for (i = 0; i < cont->nchildren; i++) + if (cont->children[i] == obj) + { + j = i+1; + break; + } + if (j == -1) + { + NS_ERROR("No children under MeimContainer"); + return 0; + } + + PR_snprintf(buf, sizeof(buf), "%ld", j); + if (obj->parent->parent) + { + higher = mime_part_address(obj->parent); + if (!higher) return 0; /* MIME_OUT_OF_MEMORY */ + } + + if (!higher) + return strdup(buf); + else + { + uint32_t slen = strlen(higher) + strlen(buf) + 3; + char *s = (char *)PR_MALLOC(slen); + if (!s) + { + PR_Free(higher); + return 0; /* MIME_OUT_OF_MEMORY */ + } + PL_strncpyz(s, higher, slen); + PL_strcatn(s, slen, "."); + PL_strcatn(s, slen, buf); + PR_Free(higher); + return s; + } + } +} + + +/* Returns a string describing the location of the *IMAP* part (like "2.5.3"). + This is not a full URL, just a part-number. + This part is explicitly passed in the X-Mozilla-IMAP-Part header. + Return value must be freed by the caller. + */ +char * +mime_imap_part_address(MimeObject *obj) +{ + if (!obj || !obj->headers) + return 0; + else + return MimeHeaders_get(obj->headers, IMAP_EXTERNAL_CONTENT_HEADER, false, false); +} + +/* Returns a full URL if the current mime object has a EXTERNAL_ATTACHMENT_URL_HEADER + header. + Return value must be freed by the caller. +*/ +char * +mime_external_attachment_url(MimeObject *obj) +{ + if (!obj || !obj->headers) + return 0; + else + return MimeHeaders_get(obj->headers, EXTERNAL_ATTACHMENT_URL_HEADER, false, false); +} + +#ifdef ENABLE_SMIME +/* Asks whether the given object is one of the cryptographically signed + or encrypted objects that we know about. (MimeMessageClass uses this + to decide if the headers need to be presented differently.) + */ +bool +mime_crypto_object_p(MimeHeaders *hdrs, bool clearsigned_counts, MimeDisplayOptions *opts) +{ + char *ct; + MimeObjectClass *clazz; + + if (!hdrs) return false; + + ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, true, false); + if (!ct) return false; + + /* Rough cut -- look at the string before doing a more complex comparison. */ + if (PL_strcasecmp(ct, MULTIPART_SIGNED) && + PL_strncasecmp(ct, "application/", 12)) + { + PR_Free(ct); + return false; + } + + /* It's a candidate for being a crypto object. Let's find out for sure... */ + clazz = mime_find_class(ct, hdrs, opts, true); + PR_Free(ct); + + if (clazz == ((MimeObjectClass *)&mimeEncryptedCMSClass)) + return true; + else if (clearsigned_counts && + clazz == ((MimeObjectClass *)&mimeMultipartSignedCMSClass)) + return true; + else + return false; +} + +#endif // ENABLE_SMIME + +/* Puts a part-number into a URL. If append_p is true, then the part number + is appended to any existing part-number already in that URL; otherwise, + it replaces it. + */ +char * +mime_set_url_part(const char *url, const char *part, bool append_p) +{ + const char *part_begin = 0; + const char *part_end = 0; + bool got_q = false; + const char *s; + char *result; + + if (!url || !part) return 0; + + nsAutoCString urlString(url); + int32_t typeIndex = urlString.Find("?type=application/x-message-display"); + if (typeIndex != -1) + { + urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1); + if (urlString.CharAt(typeIndex) == '&') + urlString.Replace(typeIndex, 1, '?'); + url = urlString.get(); + } + + for (s = url; *s; s++) + { + if (*s == '?') + { + got_q = true; + if (!PL_strncasecmp(s, "?part=", 6)) + part_begin = (s += 6); + } + else if (got_q && *s == '&' && !PL_strncasecmp(s, "&part=", 6)) + part_begin = (s += 6); + + if (part_begin) + { + for (; (*s && *s != '?' && *s != '&'); s++) + ; + part_end = s; + break; + } + } + + uint32_t resultlen = strlen(url) + strlen(part) + 10; + result = (char *) PR_MALLOC(resultlen); + if (!result) return 0; + + if (part_begin) + { + if (append_p) + { + memcpy(result, url, part_end - url); + result [part_end - url] = '.'; + result [part_end - url + 1] = 0; + } + else + { + memcpy(result, url, part_begin - url); + result [part_begin - url] = 0; + } + } + else + { + PL_strncpyz(result, url, resultlen); + if (got_q) + PL_strcatn(result, resultlen, "&part="); + else + PL_strcatn(result, resultlen, "?part="); + } + + PL_strcatn(result, resultlen, part); + + if (part_end && *part_end) + PL_strcatn(result, resultlen, part_end); + + /* Semi-broken kludge to omit a trailing "?part=0". */ + { + int L = strlen(result); + if (L > 6 && + (result[L-7] == '?' || result[L-7] == '&') && + !strcmp("part=0", result + L - 6)) + result[L-7] = 0; + } + + return result; +} + + + +/* Puts an *IMAP* part-number into a URL. + Strips off any previous *IMAP* part numbers, since they are absolute, not relative. + */ +char * +mime_set_url_imap_part(const char *url, const char *imappart, const char *libmimepart) +{ + char *result = 0; + char *whereCurrent = PL_strstr(url, "/;section="); + if (whereCurrent) + { + *whereCurrent = 0; + } + + uint32_t resultLen = strlen(url) + strlen(imappart) + strlen(libmimepart) + 17; + result = (char *) PR_MALLOC(resultLen); + if (!result) return 0; + + PL_strncpyz(result, url, resultLen); + PL_strcatn(result, resultLen, "/;section="); + PL_strcatn(result, resultLen, imappart); + PL_strcatn(result, resultLen, "?part="); + PL_strcatn(result, resultLen, libmimepart); + + if (whereCurrent) + *whereCurrent = '/'; + + return result; +} + + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches, and returns the MimeObject (else NULL.) + (part is not a URL -- it's of the form "1.3.5".) + */ +MimeObject * +mime_address_to_part(const char *part, MimeObject *obj) +{ + /* Note: this is an N^2 operation, but the number of parts in a message + shouldn't ever be large enough that this really matters... */ + + bool match; + + if (!part || !*part) + { + match = !obj->parent; + } + else + { + char *part2 = mime_part_address(obj); + if (!part2) return 0; /* MIME_OUT_OF_MEMORY */ + match = !strcmp(part, part2); + PR_Free(part2); + } + + if (match) + { + /* These are the droids we're looking for. */ + return obj; + } + else if (!mime_typep(obj, (MimeObjectClass *) &mimeContainerClass)) + { + /* Not a container, pull up, pull up! */ + return 0; + } + else + { + int32_t i; + MimeContainer *cont = (MimeContainer *) obj; + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *o2 = mime_address_to_part(part, cont->children[i]); + if (o2) return o2; + } + return 0; + } +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +char * +mime_find_content_type_of_part(const char *part, MimeObject *obj) +{ + char *result = 0; + + obj = mime_address_to_part(part, obj); + if (!obj) return 0; + + result = (obj->headers ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, true, false) : 0); + + return result; +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +char * +mime_find_suggested_name_of_part(const char *part, MimeObject *obj) +{ + char *result = 0; + + obj = mime_address_to_part(part, obj); + if (!obj) return 0; + + result = (obj->headers ? MimeHeaders_get_name(obj->headers, obj->options) : 0); + + /* If this part doesn't have a name, but this part is one fork of an + AppleDouble, and the AppleDouble itself has a name, then use that. */ + if (!result && + obj->parent && + obj->parent->headers && + mime_typep(obj->parent, + (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + result = MimeHeaders_get_name(obj->parent->headers, obj->options); + + /* Else, if this part is itself an AppleDouble, and one of its children + has a name, then use that (check data fork first, then resource.) */ + if (!result && + mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + { + MimeContainer *cont = (MimeContainer *) obj; + if (cont->nchildren > 1 && + cont->children[1] && + cont->children[1]->headers) + result = MimeHeaders_get_name(cont->children[1]->headers, obj->options); + + if (!result && + cont->nchildren > 0 && + cont->children[0] && + cont->children[0]->headers) + result = MimeHeaders_get_name(cont->children[0]->headers, obj->options); + } + + /* Ok, now we have the suggested name, if any. + Now we remove any extensions that correspond to the + Content-Transfer-Encoding. For example, if we see the headers + + Content-Type: text/plain + Content-Disposition: inline; filename=foo.text.uue + Content-Transfer-Encoding: x-uuencode + + then we would look up (in mime.types) the file extensions which are + associated with the x-uuencode encoding, find that "uue" is one of + them, and remove that from the end of the file name, thus returning + "foo.text" as the name. This is because, by the time this file ends + up on disk, its content-transfer-encoding will have been removed; + therefore, we should suggest a file name that indicates that. + */ + if (result && obj->encoding && *obj->encoding) + { + int32_t L = strlen(result); + const char **exts = 0; + + /* + I'd like to ask the mime.types file, "what extensions correspond + to obj->encoding (which happens to be "x-uuencode") but doing that + in a non-sphagetti way would require brain surgery. So, since + currently uuencode is the only content-transfer-encoding which we + understand which traditionally has an extension, we just special- + case it here! Icepicks in my forehead! + + Note that it's special-cased in a similar way in libmsg/compose.c. + */ + if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE)) + { + static const char *uue_exts[] = { "uu", "uue", 0 }; + exts = uue_exts; + } + + while (exts && *exts) + { + const char *ext = *exts; + int32_t L2 = strlen(ext); + if (L > L2 + 1 && /* long enough */ + result[L - L2 - 1] == '.' && /* '.' in right place*/ + !PL_strcasecmp(ext, result + (L - L2))) /* ext matches */ + { + result[L - L2 - 1] = 0; /* truncate at '.' and stop. */ + break; + } + exts++; + } + } + + return result; +} + +/* Parse the various "?" options off the URL and into the options struct. + */ +int +mime_parse_url_options(const char *url, MimeDisplayOptions *options) +{ + const char *q; + + if (!url || !*url) return 0; + if (!options) return 0; + + MimeHeadersState default_headers = options->headers; + + q = PL_strrchr (url, '?'); + if (! q) return 0; + q++; + while (*q) + { + const char *end, *value, *name_end; + for (end = q; *end && *end != '&'; end++) + ; + for (value = q; *value != '=' && value < end; value++) + ; + name_end = value; + if (value < end) value++; + if (name_end <= q) + ; + else if (!PL_strncasecmp ("headers", q, name_end - q)) + { + if (end > value && !PL_strncasecmp ("only", value, end-value)) + options->headers = MimeHeadersOnly; + else if (end > value && !PL_strncasecmp ("none", value, end-value)) + options->headers = MimeHeadersNone; + else if (end > value && !PL_strncasecmp ("all", value, end - value)) + options->headers = MimeHeadersAll; + else if (end > value && !PL_strncasecmp ("some", value, end - value)) + options->headers = MimeHeadersSome; + else if (end > value && !PL_strncasecmp ("micro", value, end - value)) + options->headers = MimeHeadersMicro; + else if (end > value && !PL_strncasecmp ("cite", value, end - value)) + options->headers = MimeHeadersCitation; + else if (end > value && !PL_strncasecmp ("citation", value, end-value)) + options->headers = MimeHeadersCitation; + else + options->headers = default_headers; + } + else if (!PL_strncasecmp ("part", q, name_end - q) && + options->format_out != nsMimeOutput::nsMimeMessageBodyQuoting) + { + PR_FREEIF (options->part_to_load); + if (end > value) + { + options->part_to_load = (char *) PR_MALLOC(end - value + 1); + if (!options->part_to_load) + return MIME_OUT_OF_MEMORY; + memcpy(options->part_to_load, value, end-value); + options->part_to_load[end-value] = 0; + } + } + else if (!PL_strncasecmp ("rot13", q, name_end - q)) + { + options->rot13_p = end <= value || !PL_strncasecmp ("true", value, end - value); + } + else if (!PL_strncasecmp ("emitter", q, name_end - q)) + { + if ((end > value) && !PL_strncasecmp ("js", value, end - value)) + { + // the js emitter needs to hear about nested message bodies + // in order to build a proper representation. + options->notify_nested_bodies = true; + // show_attachment_inline_p has the side-effect of letting the + // emitter see all parts of a multipart/alternative, which it + // really appreciates. + options->show_attachment_inline_p = true; + // however, show_attachment_inline_p also results in a few + // subclasses writing junk into the body for display purposes. + // put a stop to these shenanigans by enabling write_pure_bodies. + // current offenders are: + // - MimeInlineImage + options->write_pure_bodies = true; + // we don't actually care about the data in the attachments, just the + // metadata (i.e. size) + options->metadata_only = true; + } + } + + q = end; + if (*q) + q++; + } + + +/* Compatibility with the "?part=" syntax used in the old (Mozilla 2.0) + MIME parser. + + Basically, the problem is that the old part-numbering code was totally + busted: here's a comparison of the old and new numberings with a pair + of hypothetical messages (one with a single part, and one with nested + containers.) + NEW: OLD: OR: + message/rfc822 + image/jpeg 1 0 0 + + message/rfc822 + multipart/mixed 1 0 0 + text/plain 1.1 1 1 + image/jpeg 1.2 2 2 + message/rfc822 1.3 - 3 + text/plain 1.3.1 3 - + message/rfc822 1.4 - 4 + multipart/mixed 1.4.1 4 - + text/plain 1.4.1.1 4.1 - + image/jpeg 1.4.1.2 4.2 - + text/plain 1.5 5 5 + + The "NEW" column is how the current code counts. The "OLD" column is + what "?part=" references would do in 3.0b4 and earlier; you'll see that + you couldn't directly refer to the child message/rfc822 objects at all! + But that's when it got really weird, because if you turned on + "Attachments As Links" (or used a URL like "?inline=false&part=...") + then you got a totally different numbering system (seen in the "OR" + column.) Gag! + + So, the problem is, ClariNet had been using these part numbers in their + HTML news feeds, as a sleazy way of transmitting both complex HTML layouts + and images using NNTP as transport, without invoking HTTP. + + The following clause is to provide some small amount of backward + compatibility. By looking at that table, one can see that in the new + model, "part=0" has no meaning, and neither does "part=2" or "part=3" + and so on. + + "part=1" is ambiguous between the old and new way, as is any part + specification that has a "." in it. + + So, the compatibility hack we do here is: if the part is "0", then map + that to "1". And if the part is >= "2", then prepend "1." to it (so that + we map "2" to "1.2", and "3" to "1.3".) + + This leaves the URLs compatible in the cases of: + + = single part messages + = references to elements of a top-level multipart except the first + + and leaves them incompatible for: + + = the first part of a top-level multipart + = all elements deeper than the outermost part + + Life s#$%s when you don't properly think out things that end up turning + into de-facto standards... + */ + + if (options->part_to_load && + !PL_strchr(options->part_to_load, '.')) /* doesn't contain a dot */ + { + if (!strcmp(options->part_to_load, "0")) /* 0 */ + { + PR_Free(options->part_to_load); + options->part_to_load = strdup("1"); + if (!options->part_to_load) + return MIME_OUT_OF_MEMORY; + } + else if (strcmp(options->part_to_load, "1")) /* not 1 */ + { + const char *prefix = "1."; + uint32_t slen = strlen(options->part_to_load) + strlen(prefix) + 1; + char *s = (char *) PR_MALLOC(slen); + if (!s) return MIME_OUT_OF_MEMORY; + PL_strncpyz(s, prefix, slen); + PL_strcatn(s, slen, options->part_to_load); + PR_Free(options->part_to_load); + options->part_to_load = s; + } + } + + return 0; +} + + +/* Some output-generation utility functions... + */ + +int +MimeOptions_write(MimeHeaders *hdrs, MimeDisplayOptions *opt, const char *data, + int32_t length, bool user_visible_p) +{ + int status = 0; + void* closure = 0; + if (!opt || !opt->output_fn || !opt->state) + return 0; + + closure = opt->output_closure; + if (!closure) closure = opt->stream_closure; + +// PR_ASSERT(opt->state->first_data_written_p); + + if (opt->state->separator_queued_p && user_visible_p) + { + opt->state->separator_queued_p = false; + if (opt->state->separator_suppressed_p) + opt->state->separator_suppressed_p = false; + else { + const char *sep = "<BR><FIELDSET CLASS=\"mimeAttachmentHeader\">"; + int lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString name; + name.Adopt(MimeHeaders_get_name(hdrs, opt)); + MimeHeaders_convert_header_value(opt, name, false); + + if (!name.IsEmpty()) { + sep = "<LEGEND CLASS=\"mimeAttachmentHeaderName\">"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString escapedName; + escapedName.Adopt(MsgEscapeHTML(name.get())); + + lstatus = opt->output_fn(escapedName.get(), + escapedName.Length(), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + sep = "</LEGEND>"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + } + + sep = "</FIELDSET><BR/>"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + } + } + if (user_visible_p) + opt->state->separator_suppressed_p = false; + + if (length > 0) + { + status = opt->output_fn(data, length, closure); + if (status < 0) return status; + } + + return 0; +} + +int +MimeObject_write(MimeObject *obj, const char *output, int32_t length, + bool user_visible_p) +{ + if (!obj->output_p) return 0; + + // if we're stripping attachments, check if any parent is not being ouput + if (obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) + { + // if true, mime generates a separator in html - we don't want that. + user_visible_p = false; + + for (MimeObject *parent = obj->parent; parent; parent = parent->parent) + { + if (!parent->output_p) + return 0; + } + } + if (!obj->options->state->first_data_written_p) + { + int status = MimeObject_output_init(obj, 0); + if (status < 0) return status; + NS_ASSERTION(obj->options->state->first_data_written_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + + return MimeOptions_write(obj->headers, obj->options, output, length, user_visible_p); +} + +int +MimeObject_write_separator(MimeObject *obj) +{ + if (obj->options && obj->options->state && + // we never want separators if we are asking for pure bodies + !obj->options->write_pure_bodies) + obj->options->state->separator_queued_p = true; + return 0; +} + +int +MimeObject_output_init(MimeObject *obj, const char *content_type) +{ + if (obj && + obj->options && + obj->options->state && + !obj->options->state->first_data_written_p) + { + int status; + const char *charset = 0; + char *name = 0, *x_mac_type = 0, *x_mac_creator = 0; + + if (!obj->options->output_init_fn) + { + obj->options->state->first_data_written_p = true; + return 0; + } + + if (obj->headers) + { + char *ct; + name = MimeHeaders_get_name(obj->headers, obj->options); + + ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, + false, false); + if (ct) + { + x_mac_type = MimeHeaders_get_parameter(ct, PARAM_X_MAC_TYPE, NULL, NULL); + x_mac_creator= MimeHeaders_get_parameter(ct, PARAM_X_MAC_CREATOR, NULL, NULL); + /* if don't have a x_mac_type and x_mac_creator, we need to try to get it from its parent */ + if (!x_mac_type && !x_mac_creator && obj->parent && obj->parent->headers) + { + char * ctp = MimeHeaders_get(obj->parent->headers, HEADER_CONTENT_TYPE, false, false); + if (ctp) + { + x_mac_type = MimeHeaders_get_parameter(ctp, PARAM_X_MAC_TYPE, NULL, NULL); + x_mac_creator= MimeHeaders_get_parameter(ctp, PARAM_X_MAC_CREATOR, NULL, NULL); + PR_Free(ctp); + } + } + + if (!(obj->options->override_charset)) { + char *charset = MimeHeaders_get_parameter(ct, "charset", nullptr, nullptr); + if (charset) + { + PR_FREEIF(obj->options->default_charset); + obj->options->default_charset = charset; + } + } + PR_Free(ct); + } + } + + if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextClass)) + charset = ((MimeInlineText *)obj)->charset; + + if (!content_type) + content_type = obj->content_type; + if (!content_type) + content_type = TEXT_PLAIN; + + // + // Set the charset on the channel we are dealing with so people know + // what the charset is set to. Do this for quoting/Printing ONLY! + // + extern void ResetChannelCharset(MimeObject *obj); + if ( (obj->options) && + ( (obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) || + (obj->options->format_out == nsMimeOutput::nsMimeMessagePrintOutput) ) ) + ResetChannelCharset(obj); + + status = obj->options->output_init_fn (content_type, charset, name, + x_mac_type, x_mac_creator, + obj->options->stream_closure); + PR_FREEIF(name); + PR_FREEIF(x_mac_type); + PR_FREEIF(x_mac_creator); + obj->options->state->first_data_written_p = true; + return status; + } + return 0; +} + +char * +mime_get_base_url(const char *url) +{ + if (!url) + return nullptr; + + const char *s = strrchr(url, '?'); + if (s && !strncmp(s, "?type=application/x-message-display", sizeof("?type=application/x-message-display") - 1)) + { + const char *nextTerm = strchr(s, '&'); + s = (nextTerm) ? nextTerm : s + strlen(s) - 1; + } + // we need to keep the ?number part of the url, or we won't know + // which local message the part belongs to. + if (s && *s && *(s+1) && !strncmp(s + 1, "number=", sizeof("number=") - 1)) + { + const char *nextTerm = strchr(++s, '&'); + s = (nextTerm) ? nextTerm : s + strlen(s) - 1; + } + char *result = (char *) PR_MALLOC(strlen(url) + 1); + NS_ASSERTION(result, "out of memory"); + if (!result) + return nullptr; + + memcpy(result, url, s - url); + result[s - url] = 0; + return result; +} diff --git a/mailnews/mime/src/mimei.h b/mailnews/mime/src/mimei.h new file mode 100644 index 000000000..a024c4523 --- /dev/null +++ b/mailnews/mime/src/mimei.h @@ -0,0 +1,410 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEI_H_ +#define _MIMEI_H_ + +/* + This module, libmime, implements a general-purpose MIME parser. + One of the methods provided by this parser is the ability to emit + an HTML representation of it. + + All Mozilla-specific code is (and should remain) isolated in the + file mimemoz.c. Generally, if the code involves images, netlib + streams it should be in mimemoz.c instead of in the main body of + the MIME parser. + + The parser is object-oriented and fully buzzword-compliant. + There is a class for each MIME type, and each class is responsible + for parsing itself, and/or handing the input data off to one of its + child objects. + + The class hierarchy is: + + MimeObject (abstract) + | + +--- MimeContainer (abstract) + | | + | +--- MimeMultipart (abstract) + | | | + | | +--- MimeMultipartMixed + | | | + | | +--- MimeMultipartDigest + | | | + | | +--- MimeMultipartParallel + | | | + | | +--- MimeMultipartAlternative + | | | + | | +--- MimeMultipartRelated + | | | + | | +--- MimeMultipartAppleDouble + | | | + | | +--- MimeSunAttachment + | | | + | | \--- MimeMultipartSigned (abstract) + | | | + | | \--- MimeMultipartSignedCMS + | | + | +--- MimeEncrypted (abstract) + | | | + | | \--- MimeEncryptedPKCS7 + | | + | +--- MimeXlateed (abstract) + | | | + | | \--- MimeXlateed + | | + | +--- MimeMessage + | | + | \--- MimeUntypedText + | + +--- MimeLeaf (abstract) + | | + | +--- MimeInlineText (abstract) + | | | + | | +--- MimeInlineTextPlain + | | | | + | | | \--- MimeInlineTextHTMLAsPlaintext + | | | + | | +--- MimeInlineTextPlainFlowed + | | | + | | +--- MimeInlineTextHTML + | | | | + | | | +--- MimeInlineTextHTMLParsed + | | | | + | | | \--- MimeInlineTextHTMLSanitized + | | | + | | +--- MimeInlineTextRichtext + | | | | + | | | \--- MimeInlineTextEnriched + | | | + | | +--- MimeInlineTextVCard + | | + | +--- MimeInlineImage + | | + | \--- MimeExternalObject + | + \--- MimeExternalBody + + + ========================================================================= + The definition of these classes is somewhat idiosyncratic, since I defined + my own small object system, instead of giving the C++ virus another foothold. + (I would have liked to have written this in Java, but our runtime isn't + quite ready for prime time yet.) + + There is one header file and one source file for each class (for example, + the MimeInlineText class is defined in "mimetext.h" and "mimetext.c".) + Each header file follows the following boiler-plate form: + + TYPEDEFS: these come first to avoid circular dependencies. + + typedef struct FoobarClass FoobarClass; + typedef struct Foobar Foobar; + + CLASS DECLARATION: + Theis structure defines the callback routines and other per-class data + of the class defined in this module. + + struct FoobarClass { + ParentClass superclass; + ...any callbacks or class-variables... + }; + + CLASS DEFINITION: + This variable holds an instance of the one-and-only class record; the + various instances of this class point to this object. (One interrogates + the type of an instance by comparing the value of its class pointer with + the address of this variable.) + + extern FoobarClass foobarClass; + + INSTANCE DECLARATION: + Theis structure defines the per-instance data of an object, and a pointer + to the corresponding class record. + + struct Foobar { + Parent parent; + ...any instance variables... + }; + + Then, in the corresponding .c file, the following structure is used: + + CLASS DEFINITION: + First we pull in the appropriate include file (which includes all necessary + include files for the parent classes) and then we define the class object + using the MimeDefClass macro: + + #include "foobar.h" + #define MIME_SUPERCLASS parentlClass + MimeDefClass(Foobar, FoobarClass, foobarClass, &MIME_SUPERCLASS); + + The definition of MIME_SUPERCLASS is just to move most of the knowlege of the + exact class hierarchy up to the file's header, instead of it being scattered + through the various methods; see below. + + METHOD DECLARATIONS: + We will be putting function pointers into the class object, so we declare + them here. They can generally all be static, since nobody outside of this + file needs to reference them by name; all references to these routines should + be through the class object. + + extern int FoobarMethod(Foobar *); + ...etc... + + CLASS INITIALIZATION FUNCTION: + The MimeDefClass macro expects us to define a function which will finish up + any initialization of the class object that needs to happen before the first + time it is instantiated. Its name must be of the form "<class>Initialize", + and it should initialize the various method slots in the class as + appropriate. Any methods or class variables which this class does not wish + to override will be automatically inherited from the parent class (by virtue + of its class-initialization function having been run first.) Each class + object will only be initialized once. + + static int + FoobarClassInitialize(FoobarClass *class) + { + clazz->method = FoobarMethod. + ...etc... + } + + METHOD DEFINITIONS: + Next come the definitions of the methods we referred to in the class-init + function. The way to access earlier methods (methods defined on the + superclass) is to simply extract them from the superclass's object. + But note that you CANNOT get at methods by indirecting through + object->clazz->superclass: that will only work to one level, and will + go into a loop if some subclass tries to continue on this method. + + The easiest way to do this is to make use of the MIME_SUPERCLASS macro that + was defined at the top of the file, as shown below. The alternative to that + involves typing the literal name of the direct superclass of the class + defined in this file, which will be a maintenance headache if the class + hierarchy changes. If you use the MIME_SUPERCLASS idiom, then a textual + change is required in only one place if this class's superclass changes. + + static void + Foobar_finalize (MimeObject *object) + { + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); // RIGHT + parentClass.whatnot.object.finalize(object); // (works...) + object->clazz->superclass->finalize(object); // WRONG!! + } + + If you write a libmime content type handler, libmime might create several + instances of your class at once and call e.g. the same finalize code for + 3 different objects in a row. + */ + +#include "mimehdrs.h" +#include "nsTArray.h" + +typedef struct MimeObject MimeObject; +typedef struct MimeObjectClass MimeObjectClass; + +#ifdef ENABLE_SMIME +class nsICMSMessage; +#endif // ENABLE_SMIME + +/* (I don't pretend to understand this.) */ +#define cpp_stringify_noop_helper(x)#x +#define cpp_stringify(x) cpp_stringify_noop_helper(x) + +#define MimeObjectClassInitializer(ITYPE,CSUPER) \ + cpp_stringify(ITYPE), \ + sizeof(ITYPE), \ + (MimeObjectClass *) CSUPER, \ + (int (*) (MimeObjectClass *)) ITYPE##ClassInitialize, \ + 0 + +/* Macro used for setting up class definitions. + */ +#define MimeDefClass(ITYPE,CTYPE,CVAR,CSUPER) \ + static int ITYPE##ClassInitialize(ITYPE##Class *); \ + ITYPE##Class CVAR = { ITYPE##ClassInitializer(ITYPE,CSUPER) } + + +/* Creates a new (subclass of) MimeObject of the given class, with the + given headers (which are copied.) + */ +extern MimeObject *mime_new (MimeObjectClass *clazz, MimeHeaders *hdrs, + const char *override_content_type); + + +/* Destroys a MimeObject (or subclass) and all data associated with it. + */ +extern "C" void mime_free (MimeObject *object); + +/* Given a content-type string, finds and returns an appropriate subclass + of MimeObject. A class object is returned. If `exact_match_p' is true, + then only fully-known types will be returned; that is, if it is true, + then "text/x-unknown" will return MimeInlineTextPlainType, but if it is + false, it will return NULL. + */ +extern MimeObjectClass *mime_find_class (const char *content_type, + MimeHeaders *hdrs, + MimeDisplayOptions *opts, + bool exact_match_p); + +/** Given a content-type string, creates and returns an appropriate subclass + * of MimeObject. The headers (from which the content-type was presumably + * extracted) are copied. forceInline is set to true when the caller wants + * the function to ignore opts->show_attachment_inline_p and force inline + * display, e.g., mimemalt wants the body part to be shown inline. + */ +extern MimeObject *mime_create (const char *content_type, MimeHeaders *hdrs, + MimeDisplayOptions *opts, bool forceInline = false); + + +/* Querying the type hierarchy */ +extern bool mime_subclass_p(MimeObjectClass *child, + MimeObjectClass *parent); +extern bool mime_typep(MimeObject *obj, MimeObjectClass *clazz); + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +extern char *mime_part_address(MimeObject *obj); + +/* 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. + */ +extern char *mime_imap_part_address(MimeObject *obj); + +extern char *mime_external_attachment_url(MimeObject *obj); + +/* 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. + */ +extern char *mime_set_url_part(const char *url, const char *part, bool append_p); + +/* + cut the part of url for display a attachment as a email. +*/ +extern char *mime_get_base_url(const char *url); + +/* Puts an *IMAP* part-number into a URL. + */ +extern char *mime_set_url_imap_part(const char *url, const char *part, const char *libmimepart); + + +/* 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".) + */ +extern MimeObject *mime_address_to_part(const char *part, MimeObject *obj); + + +/* 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".) + */ +extern char *mime_find_suggested_name_of_part(const char *part, + MimeObject *obj); + +/* 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".) + */ +extern char *mime_find_content_type_of_part(const char *part, MimeObject *obj); + +/* Parse the various "?" options off the URL and into the options struct. + */ +extern int mime_parse_url_options(const char *url, MimeDisplayOptions *); + +#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.) + */ +extern bool mime_crypto_object_p(MimeHeaders *, bool clearsigned_counts, MimeDisplayOptions *); + +/* Tells whether the given MimeObject is a message which has been encrypted + or signed. (Helper for MIME_GetMessageCryptoState()). + */ +extern void mime_get_crypto_state (MimeObject *obj, + bool *signed_p, bool *encrypted_p, + bool *signed_ok, bool *encrypted_ok); + +/* How the crypto code tells the MimeMessage object what the crypto stamp + on it says. */ +extern void mime_set_crypto_stamp(MimeObject *obj, + bool signed_p, bool encrypted_p); +#endif // ENABLE_SMIME + +class MimeParseStateObject { +public: + + MimeParseStateObject() + {root = 0; separator_queued_p = false; separator_suppressed_p = false; + first_part_written_p = false; post_header_html_run_p = false; first_data_written_p = false; + strippingPart = false; + } + MimeObject *root; /* The outermost parser object. */ + + bool separator_queued_p; /* Whether a separator should be written out + before the next text is written (this lets + us write separators lazily, so that one + doesn't appear at the end, and so that more + than one don't appear in a row.) */ + + bool separator_suppressed_p; /* Whether the currently-queued separator + should not be printed; this is a kludge to + prevent seps from being printed just after + a header block... */ + + bool first_part_written_p; /* State used for the `Show Attachments As + Links' kludge. */ + + bool post_header_html_run_p; /* Whether we've run the + options->generate_post_header_html_fn */ + + bool first_data_written_p; /* State used for Mozilla lazy-stream- + creation evilness. */ + + nsTArray<nsCString> partsToStrip; /* if we're stripping parts, what parts to strip */ + nsTArray<nsCString> detachToFiles; /* if we're detaching parts, where each part was detached to */ + bool strippingPart; + nsCString detachedFilePath; /* if we've detached this part, filepath of detached part */ +}; + + +/* Some output-generation utility functions... + */ +extern int MimeObject_output_init(MimeObject *obj, const char *content_type); + +/* The `user_visible_p' argument says whether the output that has just been + written will cause characters or images to show up on the screen, that + is, it should be false if the stuff being written is merely structural + HTML or whitespace ("<P>", "</TABLE>", etc.) This information is used + when making the decision of whether a separating <HR> is needed. + */ +extern int MimeObject_write(MimeObject *, const char *data, int32_t length, + bool user_visible_p); +extern int MimeOptions_write(MimeHeaders *, + MimeDisplayOptions *, + const char *data, int32_t length, + bool user_visible_p); + +/* Writes out the right kind of HR (or rather, queues it for writing.) */ +extern int MimeObject_write_separator(MimeObject *); + +extern bool MimeObjectIsMessageBody(MimeObject *obj); + +struct MimeDisplayData { /* This struct is what we hang off of + (context)->mime_data, to remember info + about the last MIME object we've + parsed and displayed. See + MimeGuessURLContentName() below. + */ + MimeObject *last_parsed_object; + char *last_parsed_url; +}; + +#endif /* _MIMEI_H_ */ diff --git a/mailnews/mime/src/mimeiimg.cpp b/mailnews/mime/src/mimeiimg.cpp new file mode 100644 index 000000000..1d47db2ab --- /dev/null +++ b/mailnews/mime/src/mimeiimg.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "mimeiimg.h" +#include "mimemoz2.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "nsMimeStringResources.h" +#include "nsINetUtil.h" +#include "nsMsgUtils.h" + +#define MIME_SUPERCLASS mimeLeafClass +MimeDefClass(MimeInlineImage, MimeInlineImageClass, + mimeInlineImageClass, &MIME_SUPERCLASS); + +static int MimeInlineImage_initialize (MimeObject *); +static void MimeInlineImage_finalize (MimeObject *); +static int MimeInlineImage_parse_begin (MimeObject *); +static int MimeInlineImage_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineImage_parse_eof (MimeObject *, bool); +static int MimeInlineImage_parse_decoded_buffer (const char *, int32_t, MimeObject *); + +static int +MimeInlineImageClassInitialize(MimeInlineImageClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeLeafClass *lclass = (MimeLeafClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeInlineImage_initialize; + oclass->finalize = MimeInlineImage_finalize; + oclass->parse_begin = MimeInlineImage_parse_begin; + oclass->parse_line = MimeInlineImage_parse_line; + oclass->parse_eof = MimeInlineImage_parse_eof; + lclass->parse_decoded_buffer = MimeInlineImage_parse_decoded_buffer; + + return 0; +} + + +static int +MimeInlineImage_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeInlineImage_finalize (MimeObject *object) +{ + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeInlineImage_parse_begin (MimeObject *obj) +{ + MimeInlineImage *img = (MimeInlineImage *) obj; + + int status; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + if (!obj->output_p) return 0; + + if (!obj->options || !obj->options->output_fn || + // don't bother processing if the consumer doesn't want us + // gunking the body up. + obj->options->write_pure_bodies) + return 0; + + if (obj->options && + obj->options->image_begin && + obj->options->write_html_p && + obj->options->image_write_buffer) + { + char *html, *part, *image_url; + const char *ct; + + part = mime_part_address(obj); + if (!part) return MIME_OUT_OF_MEMORY; + + char *no_part_url = nullptr; + if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) + no_part_url = mime_get_base_url(obj->options->url); + + if (no_part_url) + { + image_url = mime_set_url_part(no_part_url, part, true); + PR_Free(no_part_url); + } + else + image_url = mime_set_url_part(obj->options->url, part, true); + + if (!image_url) + { + PR_Free(part); + return MIME_OUT_OF_MEMORY; + } + PR_Free(part); + + ct = obj->content_type; + if (!ct) ct = IMAGE_GIF; /* Can't happen? Close enough. */ + + // Fill in content type and attachment name here. + nsAutoCString url_with_filename(image_url); + url_with_filename += "&type="; + url_with_filename += ct; + char * filename = MimeHeaders_get_name ( obj->headers, obj->options ); + if (filename) + { + nsCString escapedName; + MsgEscapeString(nsDependentCString(filename), nsINetUtil::ESCAPE_URL_PATH, + escapedName); + url_with_filename += "&filename="; + url_with_filename += escapedName; + PR_Free(filename); + } + + // We need to separate images with HR's... + MimeObject_write_separator(obj); + + img->image_data = + obj->options->image_begin(url_with_filename.get(), ct, obj->options->stream_closure); + PR_Free(image_url); + + if (!img->image_data) return MIME_OUT_OF_MEMORY; + + html = obj->options->make_image_html(img->image_data); + if (!html) return MIME_OUT_OF_MEMORY; + + status = MimeObject_write(obj, html, strlen(html), true); + PR_Free(html); + if (status < 0) return status; + } + + // + // Now we are going to see if we should set the content type in the + // URI for the url being run... + // + if (obj->options && obj->options->stream_closure && obj->content_type) + { + mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure); + if ( (msd) && (msd->channel) ) + { + msd->channel->SetContentType(nsDependentCString(obj->content_type)); + } + } + + return 0; +} + + +static int +MimeInlineImage_parse_eof (MimeObject *obj, bool abort_p) +{ + MimeInlineImage *img = (MimeInlineImage *) obj; + int status; + if (obj->closed_p) return 0; + + /* Force out any buffered data from the superclass (the base64 decoder.) */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) abort_p = true; + + if (img->image_data) + { + obj->options->image_end(img->image_data, + (status < 0 ? status : (abort_p ? -1 : 0))); + img->image_data = 0; + } + + return status; +} + + +static int +MimeInlineImage_parse_decoded_buffer (const char *buf, int32_t size, MimeObject *obj) +{ + /* This is called (by MimeLeafClass->parse_buffer) with blocks of data + that have already been base64-decoded. Pass this raw image data + along to the backend-specific image display code. + */ + MimeInlineImage *img = (MimeInlineImage *) obj; + int status; + + /* Don't do a roundtrip through XPConnect when we're only interested in + * metadata and size. 0 means ok, the caller just checks for negative return + * value + */ + if (obj->options && obj->options->metadata_only) + return 0; + + if (obj->output_p && + obj->options && + !obj->options->write_html_p) + { + /* in this case, we just want the raw data... + Make the stream, if it's not made, and dump the data out. + */ + + if (!obj->options->state->first_data_written_p) + { + 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 MimeObject_write(obj, buf, size, true); + } + + + if (!obj->options || + !obj->options->image_write_buffer) + return 0; + + /* If we don't have any image data, the image_end method must have already + been called, so don't call image_write_buffer again. */ + if (!img->image_data) return 0; + + /* Hand this data off to the backend-specific image display stream. + */ + status = obj->options->image_write_buffer (buf, size, img->image_data); + + /* If the image display stream fails, then close the stream - but do not + return the failure status, and do not give up on parsing this object. + Just because the image data was corrupt doesn't mean we need to give up + on the whole document; we can continue by just skipping over the rest of + this part, and letting our parent continue. + */ + if (status < 0) + { + obj->options->image_end (img->image_data, status); + img->image_data = 0; + status = 0; + } + + return status; +} + + +static int +MimeInlineImage_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("This method should never be called (inline images do no line buffering)."); + return -1; +} diff --git a/mailnews/mime/src/mimeiimg.h b/mailnews/mime/src/mimeiimg.h new file mode 100644 index 000000000..ea8a9a3d1 --- /dev/null +++ b/mailnews/mime/src/mimeiimg.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEIIMG_H_ +#define _MIMEIIMG_H_ + +#include "mimeleaf.h" + +/* The MimeInlineImage class implements those MIME image types which can be + displayed inline. + */ + +typedef struct MimeInlineImageClass MimeInlineImageClass; +typedef struct MimeInlineImage MimeInlineImage; + +struct MimeInlineImageClass { + MimeLeafClass leaf; +}; + +extern MimeInlineImageClass mimeInlineImageClass; + +struct MimeInlineImage { + MimeLeaf leaf; + + /* Opaque data object for the backend-specific inline-image-display code + (internal-external-reconnect nastiness.) */ + void *image_data; +}; + +#define MimeInlineImageClassInitializer(ITYPE,CSUPER) \ + { MimeLeafClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEIIMG_H_ */ diff --git a/mailnews/mime/src/mimeleaf.cpp b/mailnews/mime/src/mimeleaf.cpp new file mode 100644 index 000000000..b76d0ce3b --- /dev/null +++ b/mailnews/mime/src/mimeleaf.cpp @@ -0,0 +1,220 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "modmimee.h" +#include "mimeleaf.h" +#include "nsMimeTypes.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeStringResources.h" + +#define MIME_SUPERCLASS mimeObjectClass +MimeDefClass(MimeLeaf, MimeLeafClass, mimeLeafClass, &MIME_SUPERCLASS); + +static int MimeLeaf_initialize (MimeObject *); +static void MimeLeaf_finalize (MimeObject *); +static int MimeLeaf_parse_begin (MimeObject *); +static int MimeLeaf_parse_buffer (const char *, int32_t, MimeObject *); +static int MimeLeaf_parse_line (const char *, int32_t, MimeObject *); +static int MimeLeaf_close_decoder (MimeObject *); +static int MimeLeaf_parse_eof (MimeObject *, bool); +static bool MimeLeaf_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +static int +MimeLeafClassInitialize(MimeLeafClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeLeaf_initialize; + oclass->finalize = MimeLeaf_finalize; + oclass->parse_begin = MimeLeaf_parse_begin; + oclass->parse_buffer = MimeLeaf_parse_buffer; + oclass->parse_line = MimeLeaf_parse_line; + oclass->parse_eof = MimeLeaf_parse_eof; + oclass->displayable_inline_p = MimeLeaf_displayable_inline_p; + clazz->close_decoder = MimeLeaf_close_decoder; + + /* Default `parse_buffer' method is one which line-buffers the now-decoded + data and passes it on to `parse_line'. (We snarf the implementation of + this method from our superclass's implementation of `parse_buffer', which + inherited it from MimeObject.) + */ + clazz->parse_decoded_buffer = + ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_buffer; + + return 0; +} + + +static int +MimeLeaf_initialize (MimeObject *obj) +{ + /* This is an abstract class; it shouldn't be directly instantiated. */ + NS_ASSERTION(obj->clazz != (MimeObjectClass *) &mimeLeafClass, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + // Initial size is -1 (meaning "unknown size") - we'll correct it in + // parse_buffer. + MimeLeaf *leaf = (MimeLeaf *) obj; + leaf->sizeSoFar = -1; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + + +static void +MimeLeaf_finalize (MimeObject *object) +{ + MimeLeaf *leaf = (MimeLeaf *)object; + object->clazz->parse_eof (object, false); + + /* Free the decoder data, if it's still around. It was probably freed + in MimeLeaf_parse_eof(), but just in case... */ + if (leaf->decoder_data) + { + MimeDecoderDestroy(leaf->decoder_data, true); + leaf->decoder_data = 0; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (object); +} + + +static int +MimeLeaf_parse_begin (MimeObject *obj) +{ + MimeLeaf *leaf = (MimeLeaf *) obj; + MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0; + + /* Initialize a decoder if necessary. + */ + if (!obj->encoding || + // If we need the object as "raw" for saving or forwarding, + // don't decode text parts of message types. Other output formats, + // like "display" (nsMimeMessageBodyDisplay), need decoding. + (obj->options->format_out == nsMimeOutput::nsMimeMessageRaw && + obj->parent && obj->parent->output_p && + (!PL_strcasecmp(obj->parent->content_type, MESSAGE_NEWS) || + !PL_strcasecmp(obj->parent->content_type, MESSAGE_RFC822)))) + /* no-op */ ; + else if (!PL_strcasecmp(obj->encoding, ENCODING_BASE64)) + fn = &MimeB64DecoderInit; + else if (!PL_strcasecmp(obj->encoding, ENCODING_QUOTED_PRINTABLE)) + leaf->decoder_data = + MimeQPDecoderInit(((MimeConverterOutputCallback) + ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer), + obj, obj); + else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4)) + fn = &MimeUUDecoderInit; + else if (!PL_strcasecmp(obj->encoding, ENCODING_YENCODE)) + fn = &MimeYDecoderInit; + + if (fn) + { + leaf->decoder_data = + fn (/* The MimeConverterOutputCallback cast is to turn the `void' argument + into `MimeObject'. */ + ((MimeConverterOutputCallback) + ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer), + obj); + + if (!leaf->decoder_data) + return MIME_OUT_OF_MEMORY; + } + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); +} + + +static int +MimeLeaf_parse_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + MimeLeaf *leaf = (MimeLeaf *) obj; + + NS_ASSERTION(!obj->closed_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (obj->closed_p) return -1; + + /* If we're not supposed to write this object, bug out now. + */ + if (!obj->output_p || + !obj->options || + !obj->options->output_fn) + return 0; + + int rv; + if (leaf->sizeSoFar == -1) + leaf->sizeSoFar = 0; + + if (leaf->decoder_data && + obj->options && + obj->options->format_out != nsMimeOutput::nsMimeMessageDecrypt + && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach) { + int outSize = 0; + rv = MimeDecoderWrite (leaf->decoder_data, buffer, size, &outSize); + leaf->sizeSoFar += outSize; + } + else { + rv = ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer (buffer, size, + obj); + leaf->sizeSoFar += size; + } + return rv; +} + +static int +MimeLeaf_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("MimeLeaf_parse_line shouldn't ever be called."); + return -1; +} + + +static int +MimeLeaf_close_decoder (MimeObject *obj) +{ + MimeLeaf *leaf = (MimeLeaf *) obj; + + if (leaf->decoder_data) + { + int status = MimeDecoderDestroy(leaf->decoder_data, false); + leaf->decoder_data = 0; + return status; + } + + return 0; +} + + +static int +MimeLeaf_parse_eof (MimeObject *obj, bool abort_p) +{ + MimeLeaf *leaf = (MimeLeaf *) obj; + if (obj->closed_p) return 0; + + /* Close off the decoder, to cause it to give up any buffered data that + it is still holding. + */ + if (leaf->decoder_data) + { + int status = MimeLeaf_close_decoder(obj); + if (status < 0) return status; + } + + /* Now run the superclass's parse_eof, which will force out the line + buffer (which we may have just repopulated, above.) + */ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p); +} + + +static bool +MimeLeaf_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs) +{ + return true; +} diff --git a/mailnews/mime/src/mimeleaf.h b/mailnews/mime/src/mimeleaf.h new file mode 100644 index 000000000..10cfdc59a --- /dev/null +++ b/mailnews/mime/src/mimeleaf.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMELEAF_H_ +#define _MIMELEAF_H_ + +#include "mimeobj.h" +#include "modmimee.h" + +/* MimeLeaf is the class for the objects representing all MIME types which + are not containers for other MIME objects. The implication of this is + that they are MIME types which can have Content-Transfer-Encodings + applied to their data. This class provides that service in its + parse_buffer() method: + + int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj) + + The `parse_buffer' method of MimeLeaf passes each block of data through + the appropriate decoder (if any) and then calls `parse_decoded_buffer' + on each block (not line) of output. + + The default `parse_decoded_buffer' method of MimeLeaf line-buffers the + now-decoded data, handing each line to the `parse_line' method in turn. + If different behavior is desired (for example, if a class wants access + to the decoded data before it is line-buffered) the `parse_decoded_buffer' + method should be overridden. (MimeExternalObject does this.) + */ + +typedef struct MimeLeafClass MimeLeafClass; +typedef struct MimeLeaf MimeLeaf; + +struct MimeLeafClass { + MimeObjectClass object; + /* This is the callback that is handed to the decoder. */ + int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj); + int (*close_decoder) (MimeObject *obj); +}; + +extern MimeLeafClass mimeLeafClass; + +struct MimeLeaf { + MimeObject object; /* superclass variables */ + + /* If we're doing Base64, Quoted-Printable, or UU decoding, this is the + state object for the decoder. */ + MimeDecoderData *decoder_data; + + /* We want to count the size of the MimeObject to offer consumers the + * opportunity to display the sizes of attachments. + */ + int sizeSoFar; +}; + +#define MimeLeafClassInitializer(ITYPE,CSUPER) \ + { MimeObjectClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMELEAF_H_ */ diff --git a/mailnews/mime/src/mimemalt.cpp b/mailnews/mime/src/mimemalt.cpp new file mode 100644 index 000000000..3354b1f9b --- /dev/null +++ b/mailnews/mime/src/mimemalt.cpp @@ -0,0 +1,580 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + BACKGROUND + ---------- + + At the simplest level, multipart/alternative means "pick one of these and + display it." However, it's actually a lot more complicated than that. + + The alternatives are in preference order, and counterintuitively, they go + from *least* to *most* preferred rather than the reverse. Therefore, when + we're parsing, we can't just take the first one we like and throw the rest + away -- we have to parse through the whole thing, discarding the n'th part if + we are capable of displaying the n+1'th. + + Adding a wrinkle to that is the fact that we give the user the option of + demanding the plain-text alternative even though we are perfectly capable of + displaying the HTML, and it is almost always the preferred format, i.e., it + almost always comes after the plain-text alternative. + + Speaking of which, you can't assume that each of the alternatives is just a + basic text/[whatever]. There may be, for example, a text/plain followed by a + multipart/related which contains text/html and associated embedded + images. Yikes! + + You also can't assume that there will be just two parts. There can be an + arbitrary number, and the ones we are capable of displaying and the ones we + aren't could be interspersed in any order by the producer of the MIME. + + We can't just throw away the parts we're not displaying when we're processing + the MIME for display. If we were to do that, then the MIME parts that + remained wouldn't get numbered properly, and that would mean, for example, + that deleting attachments wouldn't work in some messages. Indeed, that very + problem is what prompted a rewrite of this file into its current + architecture. + + ARCHITECTURE + ------------ + + Parts are read and queued until we know whether we're going to display + them. If the first pending part is one we don't know how to display, then we + can add it to the MIME structure immediatelly, with output_p disabled. If the + first pending part is one we know how to display, then we can't add it to the + in-memory MIME structure until either (a) we encounter a later, more + preferred part we know how to display, or (b) we reach the end of the + parts. A display-capable part of the queue may be followed by one or more + display-incapable parts. We can't add them to the in-memory structure until + we figure out what to do with the first, display-capable pending part, + because otherwise the order and numbering will be wrong. All of the logic in + this paragraph is implemented in the flush_children function. + + The display_cached_part function is what actually adds a MIME part to the + in-memory MIME structure. There is one complication there which forces us to + violate abstrations... Even if we set output_p on a child before adding it to + the parent, the parse_begin function resets it. The kluge I came up with to + prevent that was to give the child a separate options object and set + output_fn to nullptr in it, because that causes parse_begin to set output_p to + false. This seemed like the least onerous way to accomplish this, although I + can't say it's a solution I'm particularly fond of. + + Another complication in display_cached_part is that if we were just a normal + multipart type, we could rely on MimeMultipart_parse_line to notify emitters + about content types, character sets, part numbers, etc. as our new children + get created. However, since we defer creation of some children, the + notification doesn't happen there, so we have to handle it + ourselves. Unfortunately, this requires a small abstraction violation in + MimeMultipart_parse_line -- we have to check there if the entity is + multipart/alternative and if so not notify emitters there because + MimeMultipartAlternative_create_child handles it. + + - Jonathan Kamens, 2010-07-23 + + When the option prefer_plaintext is on, the last text/plain part + should be preferred over any other part that can be displayed. But + if no text/plain part is found, then the algorithm should go as + normal and convert any html part found to text. To achive this I + found that the simplest way was to change the function display_part_p + into returning priority as an integer instead of boolean can/can't + display. Then I also changed the function flush_children so it selects + the last part with the highest priority. (Priority 0 means it cannot + be displayed and the part is never choosen.) + + - Terje Bråten, 2013-02-16 +*/ + +#include "mimemalt.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "nsMimeStringResources.h" +#include "nsIPrefBranch.h" +#include "mimemoz2.h" // for prefs + +extern "C" MimeObjectClass mimeMultipartRelatedClass; + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartAlternative, MimeMultipartAlternativeClass, + mimeMultipartAlternativeClass, &MIME_SUPERCLASS); + +static int MimeMultipartAlternative_initialize (MimeObject *); +static void MimeMultipartAlternative_finalize (MimeObject *); +static int MimeMultipartAlternative_parse_eof (MimeObject *, bool); +static int MimeMultipartAlternative_create_child(MimeObject *); +static int MimeMultipartAlternative_parse_child_line (MimeObject *, const char *, + int32_t, bool); +static int MimeMultipartAlternative_close_child(MimeObject *); + +static int MimeMultipartAlternative_flush_children(MimeObject *, bool, priority_t); +static priority_t MimeMultipartAlternative_display_part_p(MimeObject *self, + MimeHeaders *sub_hdrs); +static priority_t MimeMultipartAlternative_prioritize_part(char *content_type, + bool prefer_plaintext); + +static int MimeMultipartAlternative_display_cached_part(MimeObject *, + MimeHeaders *, + MimePartBufferData *, + bool); + +static int +MimeMultipartAlternativeClassInitialize(MimeMultipartAlternativeClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeMultipartAlternative_initialize; + oclass->finalize = MimeMultipartAlternative_finalize; + oclass->parse_eof = MimeMultipartAlternative_parse_eof; + mclass->create_child = MimeMultipartAlternative_create_child; + mclass->parse_child_line = MimeMultipartAlternative_parse_child_line; + mclass->close_child = MimeMultipartAlternative_close_child; + return 0; +} + + +static int +MimeMultipartAlternative_initialize (MimeObject *obj) +{ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + + NS_ASSERTION(!malt->part_buffers, "object initialized multiple times"); + NS_ASSERTION(!malt->buffered_hdrs, "object initialized multiple times"); + malt->pending_parts = 0; + malt->max_parts = 0; + malt->buffered_priority = PRIORITY_UNDISPLAYABLE; + malt->buffered_hdrs = nullptr; + malt->part_buffers = nullptr; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + +static void +MimeMultipartAlternative_cleanup(MimeObject *obj) +{ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + int32_t i; + + for (i = 0; i < malt->pending_parts; i++) { + MimeHeaders_free(malt->buffered_hdrs[i]); + MimePartBufferDestroy(malt->part_buffers[i]); + } + PR_FREEIF(malt->buffered_hdrs); + PR_FREEIF(malt->part_buffers); + malt->pending_parts = 0; + malt->max_parts = 0; +} + + +static void +MimeMultipartAlternative_finalize (MimeObject *obj) +{ + MimeMultipartAlternative_cleanup(obj); + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + + +static int +MimeMultipartAlternative_flush_children(MimeObject *obj, + bool finished, + priority_t next_priority) +{ + /* + The cache should always have at the head the part with highest priority. + + Possible states: + + 1. Cache contains nothing: do nothing. + + 2. Finished, and the cache contains one displayable body followed + by zero or more bodies with lower priority: + + 3. Finished, and the cache contains one non-displayable body: + create it with output off. + + 4. Not finished, and the cache contains one displayable body + followed by zero or more bodies with lower priority, and the new + body we're about to create is higher or equal priority: + create all cached bodies with output off. + + 5. Not finished, and the cache contains one displayable body + followed by zero or more bodies with lower priority, and the new + body we're about to create has lower priority: do nothing. + + 6. Not finished, and the cache contains one non-displayable body: + create it with output off. + */ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + bool have_displayable, do_flush, do_display; + + /* Case 1 */ + if (! malt->pending_parts) + return 0; + + have_displayable = (malt->buffered_priority > next_priority); + + if (finished && have_displayable) { + /* Case 2 */ + do_flush = true; + do_display = true; + } + else if (finished && ! have_displayable) { + /* Case 3 */ + do_flush = true; + do_display = false; + } + else if (! finished && have_displayable) { + /* Case 5 */ + do_flush = false; + do_display = false; + } + else if (! finished && ! have_displayable) { + /* Case 4 */ + /* Case 6 */ + do_flush = true; + do_display = false; + } + else { + NS_ERROR("mimemalt.cpp: logic error in flush_children"); + return -1; + } + + if (do_flush) { + int32_t i; + for (i = 0; i < malt->pending_parts; i++) { + MimeMultipartAlternative_display_cached_part(obj, + malt->buffered_hdrs[i], + malt->part_buffers[i], + do_display && (i == 0)); + MimeHeaders_free(malt->buffered_hdrs[i]); + MimePartBufferDestroy(malt->part_buffers[i]); + } + malt->pending_parts = 0; + } + return 0; +} + +static int +MimeMultipartAlternative_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + + if (obj->closed_p) return 0; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + + status = MimeMultipartAlternative_flush_children(obj, true, + PRIORITY_UNDISPLAYABLE); + if (status < 0) + return status; + + MimeMultipartAlternative_cleanup(obj); + + return status; +} + + +static int +MimeMultipartAlternative_create_child(MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + + priority_t priority = + MimeMultipartAlternative_display_part_p (obj, mult->hdrs); + + MimeMultipartAlternative_flush_children(obj, false, priority); + + mult->state = MimeMultipartPartFirstLine; + int32_t i = malt->pending_parts++; + + if (i==0) { + malt->buffered_priority = priority; + } + + if (malt->pending_parts > malt->max_parts) { + malt->max_parts = malt->pending_parts; + MimeHeaders **newBuf = (MimeHeaders **) + PR_REALLOC(malt->buffered_hdrs, + malt->max_parts * sizeof(*malt->buffered_hdrs)); + NS_ENSURE_TRUE(newBuf, MIME_OUT_OF_MEMORY); + malt->buffered_hdrs = newBuf; + + MimePartBufferData **newBuf2 = (MimePartBufferData **) + PR_REALLOC(malt->part_buffers, + malt->max_parts * sizeof(*malt->part_buffers)); + NS_ENSURE_TRUE(newBuf2, MIME_OUT_OF_MEMORY); + malt->part_buffers = newBuf2; + } + + malt->buffered_hdrs[i] = MimeHeaders_copy(mult->hdrs); + NS_ENSURE_TRUE(malt->buffered_hdrs[i], MIME_OUT_OF_MEMORY); + + malt->part_buffers[i] = MimePartBufferCreate(); + NS_ENSURE_TRUE(malt->part_buffers[i], MIME_OUT_OF_MEMORY); + + return 0; +} + + +static int +MimeMultipartAlternative_parse_child_line (MimeObject *obj, + const char *line, int32_t length, + bool first_line_p) +{ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + + NS_ASSERTION(malt->pending_parts, "should be pending parts, but there aren't"); + if (!malt->pending_parts) + return -1; + int32_t i = malt->pending_parts - 1; + + /* Push this line into the buffer for later retrieval. */ + return MimePartBufferWrite (malt->part_buffers[i], line, length); +} + + +static int +MimeMultipartAlternative_close_child(MimeObject *obj) +{ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + MimeMultipart *mult = (MimeMultipart *) obj; + + /* PR_ASSERT(malt->part_buffer); Some Mac brokenness trips this... + if (!malt->part_buffer) return -1; */ + + if (malt->pending_parts) + MimePartBufferClose(malt->part_buffers[malt->pending_parts-1]); + + /* PR_ASSERT(mult->hdrs); I expect the Mac trips this too */ + + if (mult->hdrs) { + MimeHeaders_free(mult->hdrs); + mult->hdrs = 0; + } + + return 0; +} + + +static priority_t +MimeMultipartAlternative_display_part_p(MimeObject *self, + MimeHeaders *sub_hdrs) +{ + priority_t priority = PRIORITY_UNDISPLAYABLE; + char *ct = MimeHeaders_get (sub_hdrs, HEADER_CONTENT_TYPE, true, false); + if (!ct) + return priority; + + /* RFC 1521 says: + Receiving user agents should pick and display the last format + they are capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains unrecognized + sub-parts, the user agent may choose either to show that alternative, + an earlier alternative, or both. + */ + + // We must pass 'true' as last parameter so that text/calendar is + // only displayable when Lightning is installed. + MimeObjectClass *clazz = mime_find_class(ct, sub_hdrs, self->options, true); + if (clazz && clazz->displayable_inline_p(clazz, sub_hdrs)) { + // prefer_plaintext pref + bool prefer_plaintext = false; + nsIPrefBranch *prefBranch = GetPrefBranch(self->options); + if (prefBranch) { + prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", + &prefer_plaintext); + } + prefer_plaintext = prefer_plaintext && + (self->options->format_out != nsMimeOutput::nsMimeMessageSaveAs) && + (self->options->format_out != nsMimeOutput::nsMimeMessageRaw); + + priority = MimeMultipartAlternative_prioritize_part(ct, prefer_plaintext); + } + + PR_FREEIF(ct); + return priority; +} + +/** +* RFC 1521 says we should display the last format we are capable of displaying. +* But for various reasons (mainly to improve the user experience) we choose +* to ignore that in some cases, and rather pick one that we prioritize. +*/ +static priority_t +MimeMultipartAlternative_prioritize_part(char *content_type, + bool prefer_plaintext) +{ + /* + * PRIORITY_NORMAL is the priority of text/html, multipart/..., etc. that + * we normally display. We should try to have as few exceptions from + * PRIORITY_NORMAL as possible + */ + + /* (with no / in the type) */ + if (!PL_strcasecmp(content_type, "text")) { + if (prefer_plaintext) { + /* When in plain text view, a plain text part is what we want. */ + return PRIORITY_HIGH; + } + /* We normally prefer other parts over the unspecified text type. */ + return PRIORITY_TEXT_UNKNOWN; + } + + if (!PL_strncasecmp(content_type, "text/", 5)) { + char *text_type = content_type + 5; + + if (!PL_strncasecmp(text_type, "plain", 5)) { + if (prefer_plaintext) { + /* When in plain text view, + the text/plain part is exactly what we want */ + return PRIORITY_HIGHEST; + } + /* + * Because the html and the text part may be switched, + * or we have an extra text/plain added by f.ex. a buggy virus checker, + * we prioritize text/plain lower than normal. + */ + return PRIORITY_TEXT_PLAIN; + } + + if (!PL_strncasecmp(text_type, "calendar", 8) && prefer_plaintext) { + /* + * text/calendar receives an equally high priority so an invitation + * shows even in plaintext mode. + */ + return PRIORITY_HIGHEST; + } + + /* Need to white-list all text/... types that are or could be implemented. */ + if (!PL_strncasecmp(text_type, "html", 4) || + !PL_strncasecmp(text_type, "enriched", 8) || + !PL_strncasecmp(text_type, "richtext", 8) || + !PL_strncasecmp(text_type, "calendar", 8) || + !PL_strncasecmp(text_type, "rtf", 3)) { + return PRIORITY_NORMAL; + } + + /* We prefer other parts over unknown text types. */ + return PRIORITY_TEXT_UNKNOWN; + } + + // Guard against rogue messages with incorrect MIME structure and + // don't show images when plain text is requested. + if (!PL_strncasecmp(content_type, "image", 5)) { + if (prefer_plaintext) + return PRIORITY_UNDISPLAYABLE; + else + return PRIORITY_LOW; + } + + return PRIORITY_NORMAL; +} + +static int +MimeMultipartAlternative_display_cached_part(MimeObject *obj, + MimeHeaders *hdrs, + MimePartBufferData *buffer, + bool do_display) +{ + int status; + bool old_options_no_output_p; + + char *ct = (hdrs + ? MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, true, false) + : 0); + const char *dct = (((MimeMultipartClass *) obj->clazz)->default_part_type); + MimeObject *body; + /** Don't pass in NULL as the content-type (this means that the + * auto-uudecode-hack won't ever be done for subparts of a + * multipart, but only for untyped children of message/rfc822. + */ + const char *uct = (ct && *ct) ? ct : (dct ? dct: TEXT_PLAIN); + + // We always want to display the cached part inline. + body = mime_create(uct, hdrs, obj->options, true); + PR_FREEIF(ct); + if (!body) return MIME_OUT_OF_MEMORY; + body->output_p = do_display; + + status = ((MimeContainerClass *) obj->clazz)->add_child(obj, body); + if (status < 0) + { + mime_free(body); + return status; + } + /* add_child assigns body->options from obj->options, but that's + just a pointer so if we muck with it in the child it'll modify + the parent as well, which we definitely don't want. Therefore we + need to make a copy of the old value and restore it later. */ + old_options_no_output_p = obj->options->no_output_p; + if (! do_display) + body->options->no_output_p = true; + +#ifdef MIME_DRAFTS + /* if this object is a child of a multipart/related object, the parent is + taking care of decomposing the whole part, don't need to do it at this level. + However, we still have to call decompose_file_init_fn and decompose_file_close_fn + in order to set the correct content-type. But don't call MimePartBufferRead + */ + bool multipartRelatedChild = mime_typep(obj->parent,(MimeObjectClass*)&mimeMultipartRelatedClass); + bool decomposeFile = do_display && obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_init_fn && + !mime_typep(body, (MimeObjectClass *) &mimeMultipartClass); + + if (decomposeFile) + { + status = obj->options->decompose_file_init_fn ( + obj->options->stream_closure, hdrs); + if (status < 0) return status; + } +#endif /* MIME_DRAFTS */ + + /* Now that we've added this new object to our list of children, + notify emitters and start its parser going. */ + MimeMultipart_notify_emitter(body); + + status = body->clazz->parse_begin(body); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if (decomposeFile && !multipartRelatedChild) + status = MimePartBufferRead (buffer, + obj->options->decompose_file_output_fn, + obj->options->stream_closure); + else +#endif /* MIME_DRAFTS */ + + status = MimePartBufferRead (buffer, + /* The MimeConverterOutputCallback cast is to turn the + `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) body->clazz->parse_buffer), + body); + + if (status < 0) return status; + + /* Done parsing. */ + status = body->clazz->parse_eof(body, false); + if (status < 0) return status; + status = body->clazz->parse_end(body, false); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if (decomposeFile) + { + status = obj->options->decompose_file_close_fn ( obj->options->stream_closure ); + if (status < 0) return status; + } +#endif /* MIME_DRAFTS */ + + /* Restore options to what parent classes expects. */ + obj->options->no_output_p = old_options_no_output_p; + + return 0; +} diff --git a/mailnews/mime/src/mimemalt.h b/mailnews/mime/src/mimemalt.h new file mode 100644 index 000000000..6cd792f54 --- /dev/null +++ b/mailnews/mime/src/mimemalt.h @@ -0,0 +1,48 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMALT_H_ +#define _MIMEMALT_H_ + +#include "mimemult.h" +#include "mimepbuf.h" + +/* The MimeMultipartAlternative class implements the multipart/alternative + MIME container, which displays only one (the `best') of a set of enclosed + documents. + */ + +typedef struct MimeMultipartAlternativeClass MimeMultipartAlternativeClass; +typedef struct MimeMultipartAlternative MimeMultipartAlternative; + +struct MimeMultipartAlternativeClass { + MimeMultipartClass multipart; +}; + +extern "C" MimeMultipartAlternativeClass mimeMultipartAlternativeClass; + +enum priority_t {PRIORITY_UNDISPLAYABLE, + PRIORITY_LOW, + PRIORITY_TEXT_UNKNOWN, + PRIORITY_TEXT_PLAIN, + PRIORITY_NORMAL, + PRIORITY_HIGH, + PRIORITY_HIGHEST}; + +struct MimeMultipartAlternative { + MimeMultipart multipart; /* superclass variables */ + + MimeHeaders **buffered_hdrs; /* The headers of pending parts */ + MimePartBufferData **part_buffers; /* The data of pending parts + (see mimepbuf.h) */ + int32_t pending_parts; + int32_t max_parts; + priority_t buffered_priority; /* Priority of head of pending parts */ +}; + +#define MimeMultipartAlternativeClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMALT_H_ */ diff --git a/mailnews/mime/src/mimemapl.cpp b/mailnews/mime/src/mimemapl.cpp new file mode 100644 index 000000000..449d80ac0 --- /dev/null +++ b/mailnews/mime/src/mimemapl.cpp @@ -0,0 +1,189 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "mimemapl.h" +#include "prmem.h" +#include "plstr.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsMimeTypes.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartAppleDouble, MimeMultipartAppleDoubleClass, + mimeMultipartAppleDoubleClass, &MIME_SUPERCLASS); + +static int MimeMultipartAppleDouble_parse_begin (MimeObject *); +static bool MimeMultipartAppleDouble_output_child_p(MimeObject *, + MimeObject *); + +static int +MimeMultipartAppleDoubleClassInitialize(MimeMultipartAppleDoubleClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "mime class not initialized"); + oclass->parse_begin = MimeMultipartAppleDouble_parse_begin; + mclass->output_child_p = MimeMultipartAppleDouble_output_child_p; + return 0; +} + +static int +MimeMultipartAppleDouble_parse_begin (MimeObject *obj) +{ + /* #### This method is identical to MimeExternalObject_parse_begin + which kinda s#$%s... + */ + int status; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + /* If we're writing this object, and we're doing it in raw form, then + now is the time to inform the backend what the type of this data is. + */ + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + !obj->options->state->first_data_written_p) + { + status = MimeObject_output_init(obj, 0); + if (status < 0) return status; + NS_ASSERTION(obj->options->state->first_data_written_p, "first data not written"); + } + +#ifdef XP_MACOSX + if (obj->options && obj->options->state) + { +// obj->options->state->separator_suppressed_p = true; + goto done; + } + /* + * It would be nice to not showing the resource fork links + * if we are displaying inline. But, there is no way we could + * know ahead of time that we could display the data fork and + * the data fork is always hidden on MAC platform. + */ +#endif + /* If we're writing this object as HTML, then emit a link for the + multipart/appledouble part (both links) that looks just like the + links that MimeExternalObject emits for external leaf parts. + */ + if (obj->options && + obj->output_p && + obj->options->write_html_p && + obj->options->output_fn) + { + char *id = 0; + char *id_url = 0; + char *id_imap = 0; + + id = mime_part_address (obj); + if (! id) return MIME_OUT_OF_MEMORY; + if (obj->options->missing_parts) + id_imap = mime_imap_part_address (obj); + + if (obj->options && obj->options->url) + { + const char *url = obj->options->url; + if (id_imap && id) + { + /* if this is an IMAP part. */ + id_url = mime_set_url_imap_part(url, id_imap, id); + } + else + { + /* This is just a normal MIME part as usual. */ + id_url = mime_set_url_part(url, id, true); + } + if (!id_url) + { + PR_Free(id); + return MIME_OUT_OF_MEMORY; + } + } + +/********************** + if (!strcmp (id, "0")) + { + PR_Free(id); + id = MimeGetStringByID(MIME_MSG_ATTACHMENT); + } + else + { + const char *p = "Part "; + char *s = (char *)PR_MALLOC(strlen(p) + strlen(id) + 1); + if (!s) + { + PR_Free(id); + PR_Free(id_url); + return MIME_OUT_OF_MEMORY; + } + PL_strcpy(s, p); + PL_strcat(s, id); + PR_Free(id); + id = s; + } + + if (all_headers_p && + // Don't bother showing all headers on this part if it's the only + // part in the message: in that case, we've already shown these + // headers. + obj->options->state && + obj->options->state->root == obj->parent) + all_headers_p = false; + + newopt.fancy_headers_p = true; + newopt.headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome); + +// +RICHIE SHERRY +GOTTA STILL DO THIS FOR QUOTING! + status = MimeHeaders_write_attachment_box (obj->headers, &newopt, + obj->content_type, + obj->encoding, + id_name? id_name : id, id_url, 0 +// +*********************************************************************************/ + +// FAIL: + PR_FREEIF(id); + PR_FREEIF(id_url); + PR_FREEIF(id_imap); + if (status < 0) return status; + } + +#ifdef XP_MACOSX +done: +#endif + + return 0; +} + +static bool +MimeMultipartAppleDouble_output_child_p(MimeObject *obj, MimeObject *child) +{ + MimeContainer *cont = (MimeContainer *) obj; + + /* If this is the first child, and it's an application/applefile, then + don't emit a link for it. (There *should* be only two children, and + the first one should always be an application/applefile.) + */ + + if (cont->nchildren >= 1 && cont->children[0] == child && child->content_type && + !PL_strcasecmp(child->content_type, APPLICATION_APPLEFILE)) + { +#ifdef XP_MACOSX + if (obj->output_p && obj->options && obj->options->write_html_p) //output HTML + return false; +#else + /* if we are not on a Macintosh, don't emitte the resources fork at all. */ + return false; +#endif + } + + return true; +} diff --git a/mailnews/mime/src/mimemapl.h b/mailnews/mime/src/mimemapl.h new file mode 100644 index 000000000..2ba48afdc --- /dev/null +++ b/mailnews/mime/src/mimemapl.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMAPL_H_ +#define _MIMEMAPL_H_ + +#include "mimemult.h" + +/* The MimeMultipartAppleDouble class implements the multipart/appledouble + MIME container, which provides a method of encapsulating and reconstructing + a two-forked Macintosh file. + */ + +typedef struct MimeMultipartAppleDoubleClass MimeMultipartAppleDoubleClass; +typedef struct MimeMultipartAppleDouble MimeMultipartAppleDouble; + +struct MimeMultipartAppleDoubleClass { + MimeMultipartClass multipart; +}; + +extern MimeMultipartAppleDoubleClass mimeMultipartAppleDoubleClass; + +struct MimeMultipartAppleDouble { + MimeMultipart multipart; +}; + +#define MimeMultipartAppleDoubleClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMAPL_H_ */ diff --git a/mailnews/mime/src/mimemcms.cpp b/mailnews/mime/src/mimemcms.cpp new file mode 100644 index 000000000..96aa5481f --- /dev/null +++ b/mailnews/mime/src/mimemcms.cpp @@ -0,0 +1,494 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsICMSMessage.h" +#include "nsICMSMessageErrors.h" +#include "nsICMSDecoder.h" +#include "nsICryptoHash.h" +#include "mimemcms.h" +#include "mimecryp.h" +#include "nsMimeTypes.h" +#include "nspr.h" +#include "nsMimeStringResources.h" +#include "mimemsg.h" +#include "mimemoz2.h" +#include "nsIURI.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMsgSMIMEHeaderSink.h" +#include "nsCOMPtr.h" +#include "nsIX509Cert.h" +#include "plstr.h" +#include "nsComponentManagerUtils.h" + +#define MIME_SUPERCLASS mimeMultipartSignedClass +MimeDefClass(MimeMultipartSignedCMS, MimeMultipartSignedCMSClass, + mimeMultipartSignedCMSClass, &MIME_SUPERCLASS); + +static int MimeMultipartSignedCMS_initialize (MimeObject *); + +static void *MimeMultCMS_init (MimeObject *); +static int MimeMultCMS_data_hash (const char *, int32_t, void *); +static int MimeMultCMS_sig_hash (const char *, int32_t, void *); +static int MimeMultCMS_data_eof (void *, bool); +static int MimeMultCMS_sig_eof (void *, bool); +static int MimeMultCMS_sig_init (void *, MimeObject *, MimeHeaders *); +static char * MimeMultCMS_generate (void *); +static void MimeMultCMS_free (void *); +static void MimeMultCMS_suppressed_child(void *crypto_closure); + +extern int SEC_ERROR_CERT_ADDR_MISMATCH; + +static int +MimeMultipartSignedCMSClassInitialize(MimeMultipartSignedCMSClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartSignedClass *sclass = (MimeMultipartSignedClass *) clazz; + + oclass->initialize = MimeMultipartSignedCMS_initialize; + + sclass->crypto_init = MimeMultCMS_init; + sclass->crypto_data_hash = MimeMultCMS_data_hash; + sclass->crypto_data_eof = MimeMultCMS_data_eof; + sclass->crypto_signature_init = MimeMultCMS_sig_init; + sclass->crypto_signature_hash = MimeMultCMS_sig_hash; + sclass->crypto_signature_eof = MimeMultCMS_sig_eof; + sclass->crypto_generate_html = MimeMultCMS_generate; + sclass->crypto_notify_suppressed_child = MimeMultCMS_suppressed_child; + sclass->crypto_free = MimeMultCMS_free; + + PR_ASSERT(!oclass->class_initialized); + return 0; +} + +static int +MimeMultipartSignedCMS_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + + +typedef struct MimeMultCMSdata +{ + int16_t hash_type; + nsCOMPtr<nsICryptoHash> data_hash_context; + nsCOMPtr<nsICMSDecoder> sig_decoder_context; + nsCOMPtr<nsICMSMessage> content_info; + char *sender_addr; + bool decoding_failed; + unsigned char* item_data; + uint32_t item_len; + MimeObject *self; + nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink; + nsCString url; + + MimeMultCMSdata() + :hash_type(0), + sender_addr(nullptr), + decoding_failed(false), + item_data(nullptr), + self(nullptr) {} + + ~MimeMultCMSdata() + { + PR_FREEIF(sender_addr); + + // Do a graceful shutdown of the nsICMSDecoder and release the nsICMSMessage // + if (sig_decoder_context) + { + nsCOMPtr<nsICMSMessage> cinfo; + sig_decoder_context->Finish(getter_AddRefs(cinfo)); + } + + delete [] item_data; + } +} MimeMultCMSdata; + +/* #### MimeEncryptedCMS and MimeMultipartSignedCMS have a sleazy, + incestuous, dysfunctional relationship. */ +extern bool MimeAnyParentCMSSigned(MimeObject *obj); +extern void MimeCMSGetFromSender(MimeObject *obj, + nsCString &from_addr, + nsCString &from_name, + nsCString &sender_addr, + nsCString &sender_name); +extern bool MimeCMSHeadersAndCertsMatch(MimeObject *obj, + nsICMSMessage *, + bool *signing_cert_without_email_address); +extern void MimeCMSRequestAsyncSignatureVerification(nsICMSMessage *aCMSMsg, + const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel, + const nsCString &aMsgNeckoURL, + unsigned char* item_data, uint32_t item_len, + int16_t digest_type); +extern char *MimeCMS_MakeSAURL(MimeObject *obj); +extern char *IMAP_CreateReloadAllPartsUrl(const char *url); +extern int MIMEGetRelativeCryptoNestLevel(MimeObject *obj); + +static void * +MimeMultCMS_init (MimeObject *obj) +{ + MimeHeaders *hdrs = obj->headers; + MimeMultCMSdata *data = 0; + char *ct, *micalg; + int16_t hash_type; + nsresult rv; + + data = new MimeMultCMSdata; + if (!data) + return 0; + + data->self = obj; + + mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure); + if (msd) + { + nsIChannel *channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsCOMPtr<nsIMsgMailNewsUrl> msgurl; + nsCOMPtr<nsISupports> securityInfo; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + rv = uri->GetSpec(data->url); + + // We only want to update the UI if the current mime transaction + // is intended for display. + // If the current transaction is intended for background processing, + // we can learn that by looking at the additional header=filter + // string contained in the URI. + // + // If we find something, we do not set smimeHeaderSink, + // which will prevent us from giving UI feedback. + // + // If we do not find header=filter, we assume the result of the + // processing will be shown in the UI. + + if (!strstr(data->url.get(), "?header=filter") && + !strstr(data->url.get(), "&header=filter") && + !strstr(data->url.get(), "?header=attach") && + !strstr(data->url.get(), "&header=attach")) + { + msgurl = do_QueryInterface(uri); + if (msgurl) + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + headerSink->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (securityInfo) + data->smimeHeaderSink = do_QueryInterface(securityInfo); + } + } + } // if channel + } // if msd + + if (obj->parent && MimeAnyParentCMSSigned(obj)) { + // Parent is signed. We know this part is a signature, too, because + // multipart doesn't allow encryption. + // We don't support "inner sign" with outer sign, because the + // inner encrypted part could have been produced by an attacker who + // stripped away a part containing the signature (S/MIME doesn't + // have integrity protection). + // Also we don't want to support sign-then-sign, that's misleading, + // which part would be shown as having a signature? + // TODO: should we show all contents, without any signature info? + + if (data->smimeHeaderSink) { + data->smimeHeaderSink->SignedStatus( + MIMEGetRelativeCryptoNestLevel(data->self), + nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url); + } + delete data; + PR_SetError(-1, 0); + return 0; + } + + ct = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (!ct) { + delete data; + return 0; /* #### bogus message? out of memory? */ + } + micalg = MimeHeaders_get_parameter(ct, PARAM_MICALG, NULL, NULL); + PR_Free(ct); + ct = 0; + if (!micalg) { + delete data; + return 0; /* #### bogus message? out of memory? */ + } + + if (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2)) + hash_type = nsICryptoHash::MD5; + else if (!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)) + hash_type = nsICryptoHash::SHA1; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA256) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3)) + hash_type = nsICryptoHash::SHA256; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA384) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3)) + hash_type = nsICryptoHash::SHA384; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA512) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3)) + hash_type = nsICryptoHash::SHA512; + else + hash_type = -1; + + PR_Free(micalg); + micalg = 0; + + if (hash_type == -1) { + delete data; + return 0; /* #### bogus message? */ + } + + data->hash_type = hash_type; + + data->data_hash_context = + do_CreateInstance("@mozilla.org/security/hash;1", &rv); + if (NS_FAILED(rv)) { + delete data; + return 0; + } + + rv = data->data_hash_context->Init(data->hash_type); + if (NS_FAILED(rv)) { + delete data; + return 0; + } + + PR_SetError(0, 0); + + return data; +} + +static int +MimeMultCMS_data_hash (const char *buf, int32_t size, void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data || !data->data_hash_context) { + return -1; + } + + PR_SetError(0, 0); + nsresult rv = data->data_hash_context->Update((unsigned char *) buf, size); + data->decoding_failed = NS_FAILED(rv); + + return 0; +} + +static int +MimeMultCMS_data_eof (void *crypto_closure, bool abort_p) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data || !data->data_hash_context) { + return -1; + } + + nsAutoCString hashString; + data->data_hash_context->Finish(false, hashString); + PR_SetError(0, 0); + + data->item_len = hashString.Length(); + data->item_data = new unsigned char[data->item_len]; + if (!data->item_data) return MIME_OUT_OF_MEMORY; + + memcpy(data->item_data, hashString.get(), data->item_len); + + // Release our reference to nsICryptoHash // + data->data_hash_context = nullptr; + + /* At this point, data->item.data contains a digest for the first part. + When we process the signature, the security library will compare this + digest to what's in the signature object. */ + + return 0; +} + + +static int +MimeMultCMS_sig_init (void *crypto_closure, + MimeObject *multipart_object, + MimeHeaders *signature_hdrs) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + char *ct; + int status = 0; + nsresult rv; + + if (!signature_hdrs) { + return -1; + } + + ct = MimeHeaders_get (signature_hdrs, HEADER_CONTENT_TYPE, true, false); + + /* Verify that the signature object is of the right type. */ + if (!ct || /* is not a signature type */ + (PL_strcasecmp(ct, APPLICATION_XPKCS7_SIGNATURE) != 0 + && PL_strcasecmp(ct, APPLICATION_PKCS7_SIGNATURE) != 0)) { + status = -1; /* #### error msg about bogus message */ + } + PR_FREEIF(ct); + if (status < 0) return status; + + data->sig_decoder_context = do_CreateInstance(NS_CMSDECODER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return 0; + + rv = data->sig_decoder_context->Start(nullptr, nullptr); + if (NS_FAILED(rv)) { + status = PR_GetError(); + if (status >= 0) status = -1; + } + return status; +} + + +static int +MimeMultCMS_sig_hash (const char *buf, int32_t size, void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + nsresult rv; + + if (!data || !data->sig_decoder_context) { + return -1; + } + + rv = data->sig_decoder_context->Update(buf, size); + data->decoding_failed = NS_FAILED(rv); + + return 0; +} + +static int +MimeMultCMS_sig_eof (void *crypto_closure, bool abort_p) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + + if (!data) { + return -1; + } + + /* Hand an EOF to the crypto library. + + We save away the value returned and will use it later to emit a + blurb about whether the signature validation was cool. + */ + + if (data->sig_decoder_context) { + data->sig_decoder_context->Finish(getter_AddRefs(data->content_info)); + + // Release our reference to nsICMSDecoder // + data->sig_decoder_context = nullptr; + } + + return 0; +} + +static void +MimeMultCMS_free (void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data) return; + + delete data; +} + +static void MimeMultCMS_suppressed_child(void *crypto_closure) { + // I'm a multipart/signed. If one of my cryptographic child elements + // was suppressed, then I want my signature to be shown as invalid. + MimeMultCMSdata *data = (MimeMultCMSdata *)crypto_closure; + if (data && data->smimeHeaderSink) { + data->smimeHeaderSink->SignedStatus( + MIMEGetRelativeCryptoNestLevel(data->self), + nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url); + } +} + +static char * +MimeMultCMS_generate (void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data) return 0; + nsCOMPtr<nsIX509Cert> signerCert; + + int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self); + + if (aRelativeNestLevel < 0) + return nullptr; + + int32_t maxNestLevel = 0; + if (data->smimeHeaderSink && aRelativeNestLevel >= 0) + { + data->smimeHeaderSink->MaxWantedNesting(&maxNestLevel); + + if (aRelativeNestLevel > maxNestLevel) + return nullptr; + } + + if (data->self->options->missing_parts) + { + // We were not given all parts of the message. + // We are therefore unable to verify correctness of the signature. + + if (data->smimeHeaderSink) { + data->smimeHeaderSink->SignedStatus(aRelativeNestLevel, + nsICMSMessageErrors::VERIFY_NOT_YET_ATTEMPTED, + nullptr, data->url); + } + return nullptr; + } + + if (!data->content_info) + { + /* No content_info at all -- since we're inside a multipart/signed, + that means that we've either gotten a message that was truncated + before the signature part, or we ran out of memory, or something + awful has happened. + */ + return nullptr; + } + + nsCString from_addr; + nsCString from_name; + nsCString sender_addr; + nsCString sender_name; + + MimeCMSGetFromSender(data->self, + from_addr, from_name, + sender_addr, sender_name); + + MimeCMSRequestAsyncSignatureVerification(data->content_info, + from_addr.get(), from_name.get(), + sender_addr.get(), sender_name.get(), + data->smimeHeaderSink, aRelativeNestLevel, + data->url, + data->item_data, data->item_len, + data->hash_type); + + if (data->content_info) + { +#if 0 // XXX Fix this. What do we do here? // + if (SEC_CMSContainsCertsOrCrls(data->content_info)) + { + /* #### call libsec telling it to import the certs */ + } +#endif + } + + return nullptr; +} diff --git a/mailnews/mime/src/mimemcms.h b/mailnews/mime/src/mimemcms.h new file mode 100644 index 000000000..54f41da1b --- /dev/null +++ b/mailnews/mime/src/mimemcms.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef _MIMEMPKC_H_ +#define _MIMEMPKC_H_ + +#include "mimemsig.h" + +class nsICMSMessage; + +/* The MimeMultipartSignedCMS class implements a multipart/signed MIME + container with protocol=application/x-CMS-signature, which passes the + signed object through CMS code to verify the signature. See mimemsig.h + for details of the general mechanism on which this is built. + */ + +typedef struct MimeMultipartSignedCMSClass MimeMultipartSignedCMSClass; +typedef struct MimeMultipartSignedCMS MimeMultipartSignedCMS; + +struct MimeMultipartSignedCMSClass { + MimeMultipartSignedClass msigned; +}; + +extern MimeMultipartSignedCMSClass mimeMultipartSignedCMSClass; + +struct MimeMultipartSignedCMS { + MimeMultipartSigned msigned; +}; + +#define MimeMultipartSignedCMSClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartSignedClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMPKC_H_ */ diff --git a/mailnews/mime/src/mimemdig.cpp b/mailnews/mime/src/mimemdig.cpp new file mode 100644 index 000000000..6b318fb2b --- /dev/null +++ b/mailnews/mime/src/mimemdig.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimemdig.h" +#include "prlog.h" +#include "nsMimeTypes.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartDigest, MimeMultipartDigestClass, + mimeMultipartDigestClass, &MIME_SUPERCLASS); + +static int +MimeMultipartDigestClassInitialize(MimeMultipartDigestClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); +#endif + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + mclass->default_part_type = MESSAGE_RFC822; + return 0; +} diff --git a/mailnews/mime/src/mimemdig.h b/mailnews/mime/src/mimemdig.h new file mode 100644 index 000000000..6844c286f --- /dev/null +++ b/mailnews/mime/src/mimemdig.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMDIG_H_ +#define _MIMEMDIG_H_ + +#include "mimemult.h" + +/* The MimeMultipartDigest class implements the multipart/digest MIME + container, which is just like multipart/mixed, except that the default + type (for parts with no type explicitly specified) is message/rfc822 + instead of text/plain. + */ + +typedef struct MimeMultipartDigestClass MimeMultipartDigestClass; +typedef struct MimeMultipartDigest MimeMultipartDigest; + +struct MimeMultipartDigestClass { + MimeMultipartClass multipart; +}; + +extern MimeMultipartDigestClass mimeMultipartDigestClass; + +struct MimeMultipartDigest { + MimeMultipart multipart; +}; + +#define MimeMultipartDigestClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMDIG_H_ */ diff --git a/mailnews/mime/src/mimemmix.cpp b/mailnews/mime/src/mimemmix.cpp new file mode 100644 index 000000000..f2d7e31cd --- /dev/null +++ b/mailnews/mime/src/mimemmix.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimemmix.h" +#include "prlog.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartMixed, MimeMultipartMixedClass, + mimeMultipartMixedClass, &MIME_SUPERCLASS); + +static int +MimeMultipartMixedClassInitialize(MimeMultipartMixedClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); +#endif + return 0; +} diff --git a/mailnews/mime/src/mimemmix.h b/mailnews/mime/src/mimemmix.h new file mode 100644 index 000000000..9f401fa31 --- /dev/null +++ b/mailnews/mime/src/mimemmix.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMMIX_H_ +#define _MIMEMMIX_H_ + +#include "mimemult.h" + +/* The MimeMultipartMixed class implements the multipart/mixed MIME container, + and is also used for any and all otherwise-unrecognised subparts of + multipart/. + */ + +typedef struct MimeMultipartMixedClass MimeMultipartMixedClass; +typedef struct MimeMultipartMixed MimeMultipartMixed; + +struct MimeMultipartMixedClass { + MimeMultipartClass multipart; +}; + +extern MimeMultipartMixedClass mimeMultipartMixedClass; + +struct MimeMultipartMixed { + MimeMultipart multipart; +}; + +#define MimeMultipartMixedClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMMIX_H_ */ diff --git a/mailnews/mime/src/mimemoz2.cpp b/mailnews/mime/src/mimemoz2.cpp new file mode 100644 index 000000000..c47c5c8a5 --- /dev/null +++ b/mailnews/mime/src/mimemoz2.cpp @@ -0,0 +1,2212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "prlog.h" +#include "nsCOMPtr.h" +#include "modlmime.h" +#include "mimeobj.h" +#include "mimemsg.h" +#include "mimetric.h" /* for MIME_RichtextConverter */ +#include "mimethtm.h" +#include "mimemsig.h" +#include "mimemrel.h" +#include "mimemalt.h" +#include "mimebuf.h" +#include "mimemapl.h" +#include "prprf.h" +#include "mimei.h" /* for moved MimeDisplayData struct */ +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "prmem.h" +#include "mimemoz2.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsStringGlue.h" +#include "nsMimeStringResources.h" +#include "nsStreamConverter.h" +#include "nsIMsgSend.h" +#include "nsIMsgMailNewsUrl.h" +#include "mozITXTToHTMLConv.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsIImapUrl.h" +#include "nsMsgI18N.h" +#include "nsICharsetConverterManager.h" +#include "nsMimeTypes.h" +#include "nsIIOService.h" +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsIMsgWindow.h" +#include "nsIMimeMiscStatus.h" +#include "nsMsgUtils.h" +#include "nsIChannel.h" +#include "nsITransport.h" +#include "mimeebod.h" +#include "mimeeobj.h" +// <for functions="HTML2Plaintext,HTMLSantinize"> +#include "nsXPCOM.h" +#include "nsLayoutCID.h" +#include "nsIComponentManager.h" +#include "nsIParserUtils.h" +// </for> +#include "mozilla/Services.h" +#include "mozilla/Unused.h" + +void ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs); + +static MimeHeadersState MIME_HeaderType; +static bool MIME_WrapLongLines; +static bool MIME_VariableWidthPlaintext; + +mime_stream_data::mime_stream_data() : url_name(nullptr), orig_url_name(nullptr), + pluginObj2(nullptr), istream(nullptr), obj(nullptr), options(nullptr), + headers(nullptr), output_emitter(nullptr), firstCheck(false) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Attachment handling routines +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +MimeObject *mime_get_main_object(MimeObject* obj); + +nsresult MimeGetSize(MimeObject *child, int32_t *size) { + bool isLeaf = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeLeafClass); + bool isContainer = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeContainerClass); + bool isMsg = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeMessageClass); + + if (isLeaf) { + *size += ((MimeLeaf *)child)->sizeSoFar; + } else if (isMsg) { + *size += ((MimeMessage *)child)->sizeSoFar; + } else if (isContainer) { + int i; + MimeContainer *cont = (MimeContainer *)child; + for (i = 0; i < cont->nchildren; ++i) { + MimeGetSize(cont->children[i], size); + } + } + return NS_OK; +} + +nsresult +ProcessBodyAsAttachment(MimeObject *obj, nsMsgAttachmentData **data) +{ + nsMsgAttachmentData *tmp; + char *disp = nullptr; + char *charset = nullptr; + + // Ok, this is the special case when somebody sends an "attachment" as the + // body of an RFC822 message...I really don't think this is the way this + // should be done. I belive this should really be a multipart/mixed message + // with an empty body part, but what can ya do...our friends to the North seem + // to do this. + MimeObject *child = obj; + + *data = new nsMsgAttachmentData[2]; + if (!*data) + return NS_ERROR_OUT_OF_MEMORY; + + tmp = *data; + tmp->m_realType = child->content_type; + tmp->m_realEncoding = child->encoding; + disp = MimeHeaders_get(child->headers, HEADER_CONTENT_DISPOSITION, false, false); + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, NULL)); + if (!tmp->m_realName.IsEmpty()) + { + char *fname = NULL; + fname = mime_decode_filename(tmp->m_realName.get(), charset, obj->options); + free(charset); + if (fname) + tmp->m_realName.Adopt(fname); + } + else + { + tmp->m_realName.Adopt(MimeHeaders_get_name(child->headers, obj->options)); + + if (tmp->m_realName.IsEmpty() && + tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822)) + { + // We haven't actually parsed the message "attachment", so just give it a + // generic name. + tmp->m_realName = "AttachedMessage.eml"; + } + } + + tmp->m_hasFilename = !tmp->m_realName.IsEmpty(); + + if (tmp->m_realName.IsEmpty() && + StringBeginsWith(tmp->m_realType, NS_LITERAL_CSTRING("text"), + nsCaseInsensitiveCStringComparator())) + ValidateRealName(tmp, child->headers); + + tmp->m_displayableInline = obj->clazz->displayable_inline_p(obj->clazz, + obj->headers); + + char *tmpURL = nullptr; + char *id = nullptr; + char *id_imap = nullptr; + + id = mime_part_address (obj); + if (obj->options->missing_parts) + id_imap = mime_imap_part_address (obj); + + tmp->m_isDownloaded = !id_imap; + + if (! id) + { + delete [] *data; + *data = nullptr; + PR_FREEIF(id_imap); + return NS_ERROR_OUT_OF_MEMORY; + } + + if (obj->options && obj->options->url) + { + const char *url = obj->options->url; + nsresult rv; + if (id_imap && id) + { + // if this is an IMAP part. + tmpURL = mime_set_url_imap_part(url, id_imap, id); + rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), tmpURL, nullptr); + } + else + { + // This is just a normal MIME part as usual. + tmpURL = mime_set_url_part(url, id, true); + rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), tmpURL, nullptr); + } + + if (!tmp->m_url || NS_FAILED(rv)) + { + delete [] *data; + *data = nullptr; + PR_FREEIF(id); + PR_FREEIF(id_imap); + return NS_ERROR_OUT_OF_MEMORY; + } + } + PR_FREEIF(id); + PR_FREEIF(id_imap); + PR_FREEIF(tmpURL); + tmp->m_description.Adopt(MimeHeaders_get(child->headers, HEADER_CONTENT_DESCRIPTION, false, false)); + + tmp->m_size = 0; + MimeGetSize(child, &tmp->m_size); + + return NS_OK; +} + +int32_t +CountTotalMimeAttachments(MimeContainer *aObj) +{ + int32_t i; + int32_t rc = 0; + + if ( (!aObj) || (!aObj->children) || (aObj->nchildren <= 0) ) + return 0; + + if (!mime_typep(((MimeObject *) aObj), (MimeObjectClass*) &mimeContainerClass)) + return 0; + + for (i=0; i<aObj->nchildren; i++) + rc += CountTotalMimeAttachments((MimeContainer *)aObj->children[i]) + 1; + + return rc; +} + +void +ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs) +{ + // Sanity. + if (!aAttach) + return; + + // Do we need to validate? + if (!aAttach->m_realName.IsEmpty()) + return; + + // Internal MIME structures need not be named! + if (aAttach->m_realType.IsEmpty() || + StringBeginsWith(aAttach->m_realType, NS_LITERAL_CSTRING("multipart"), + nsCaseInsensitiveCStringComparator())) + return; + + // + // Now validate any other name we have for the attachment! + // + if (aAttach->m_realName.IsEmpty()) + { + aAttach->m_realName = "attachment"; + nsresult rv = NS_OK; + nsAutoCString contentType (aAttach->m_realType); + int32_t pos = contentType.FindChar(';'); + if (pos > 0) + contentType.SetLength(pos); + + nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString fileExtension; + rv = mimeFinder->GetPrimaryExtension(contentType, EmptyCString(), fileExtension); + + if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty()) + { + aAttach->m_realName.Append('.'); + aAttach->m_realName.Append(fileExtension); + } + } + } +} + +static int32_t attIndex = 0; + +nsresult +GenerateAttachmentData(MimeObject *object, const char *aMessageURL, MimeDisplayOptions *options, + bool isAnAppleDoublePart, int32_t attSize, nsMsgAttachmentData *aAttachData) +{ + nsCString imappart; + nsCString part; + bool isExternalAttachment = false; + + /* be sure the object has not be marked as Not to be an attachment */ + if (object->dontShowAsAttachment) + return NS_OK; + + part.Adopt(mime_part_address(object)); + if (part.IsEmpty()) + return NS_ERROR_OUT_OF_MEMORY; + + if (options->missing_parts) + imappart.Adopt(mime_imap_part_address(object)); + + char *urlSpec = nullptr; + if (!imappart.IsEmpty()) + { + urlSpec = mime_set_url_imap_part(aMessageURL, imappart.get(), part.get()); + } + else + { + char *no_part_url = nullptr; + if (options->part_to_load && options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) + no_part_url = mime_get_base_url(aMessageURL); + if (no_part_url) { + urlSpec = mime_set_url_part(no_part_url, part.get(), true); + PR_Free(no_part_url); + } + else + { + // if the mime object contains an external attachment URL, then use it, otherwise + // fall back to creating an attachment url based on the message URI and the + // part number. + urlSpec = mime_external_attachment_url(object); + isExternalAttachment = urlSpec ? true : false; + if (!urlSpec) + urlSpec = mime_set_url_part(aMessageURL, part.get(), true); + } + } + + if (!urlSpec) + return NS_ERROR_OUT_OF_MEMORY; + + if ((options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) && (PL_strncasecmp(aMessageURL, urlSpec, strlen(urlSpec)) == 0)) + return NS_OK; + + nsCString urlString(urlSpec); + + nsMsgAttachmentData *tmp = &(aAttachData[attIndex++]); + + tmp->m_realType = object->content_type; + tmp->m_realEncoding = object->encoding; + tmp->m_isExternalAttachment = isExternalAttachment; + tmp->m_isExternalLinkAttachment = + (isExternalAttachment && + StringBeginsWith(urlString, NS_LITERAL_CSTRING("http"), + nsCaseInsensitiveCStringComparator())); + tmp->m_size = attSize; + tmp->m_sizeExternalStr = "-1"; + tmp->m_disposition.Adopt(MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, true, false)); + tmp->m_displayableInline = object->clazz->displayable_inline_p(object->clazz, object->headers); + + char *part_addr = mime_imap_part_address(object); + tmp->m_isDownloaded = !part_addr; + PR_FREEIF(part_addr); + + int32_t i; + char *charset = nullptr; + char *disp = MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, false, false); + if (disp) + { + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "filename", &charset, nullptr)); + if (isAnAppleDoublePart) + for (i = 0; i < 2 && tmp->m_realName.IsEmpty(); i ++) + { + PR_FREEIF(disp); + free(charset); + disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_DISPOSITION, false, false); + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "filename", &charset, nullptr)); + } + + if (!tmp->m_realName.IsEmpty()) + { + // check encoded type + // + // The parameter of Content-Disposition must use RFC 2231. + // But old Netscape 4.x and Outlook Express etc. use RFC2047. + // So we should parse both types. + + char *fname = nullptr; + fname = mime_decode_filename(tmp->m_realName.get(), charset, options); + free(charset); + + if (fname) + tmp->m_realName.Adopt(fname); + } + + PR_FREEIF(disp); + } + + disp = MimeHeaders_get(object->headers, HEADER_CONTENT_TYPE, false, false); + if (disp) + { + tmp->m_xMacType.Adopt(MimeHeaders_get_parameter(disp, PARAM_X_MAC_TYPE, nullptr, nullptr)); + tmp->m_xMacCreator.Adopt(MimeHeaders_get_parameter(disp, PARAM_X_MAC_CREATOR, nullptr, nullptr)); + + if (tmp->m_realName.IsEmpty()) + { + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, nullptr)); + if (isAnAppleDoublePart) + // the data fork is the 2nd part, and we should ALWAYS look there first for the file name + for (i = 1; i >= 0 && tmp->m_realName.IsEmpty(); i --) + { + PR_FREEIF(disp); + free(charset); + disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_TYPE, false, false); + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, nullptr)); + tmp->m_realType.Adopt( + MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, + HEADER_CONTENT_TYPE, true, false)); + } + + if (!tmp->m_realName.IsEmpty()) + { + // check encoded type + // + // The parameter of Content-Disposition must use RFC 2231. + // But old Netscape 4.x and Outlook Express etc. use RFC2047. + // So we should parse both types. + + char *fname = nullptr; + fname = mime_decode_filename(tmp->m_realName.get(), charset, options); + free(charset); + + if (fname) + tmp->m_realName.Adopt(fname); + } + } + + if (tmp->m_isExternalLinkAttachment) + { + // If an external link attachment part's Content-Type contains a + // |size| parm, store it in m_sizeExternalStr. Let the msgHeaderSink + // addAttachmentField() figure out if it's sane, and don't bother + // strtol'ing it to an int only to emit it as a string. + char* sizeStr = MimeHeaders_get_parameter(disp, "size", nullptr, nullptr); + if (sizeStr) + tmp->m_sizeExternalStr = sizeStr; + } + + PR_FREEIF(disp); + } + + tmp->m_description.Adopt(MimeHeaders_get(object->headers, HEADER_CONTENT_DESCRIPTION, + false, false)); + + // Now, do the right thing with the name! + if (tmp->m_realName.IsEmpty() && !(tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822))) + { + // Keep in mind that the name was provided by us and this is probably not a + // real attachment. + tmp->m_hasFilename = false; + /* If this attachment doesn't have a name, just give it one... */ + tmp->m_realName.Adopt(MimeGetStringByID(MIME_MSG_DEFAULT_ATTACHMENT_NAME)); + if (!tmp->m_realName.IsEmpty()) + { + char *newName = PR_smprintf(tmp->m_realName.get(), part.get()); + if (newName) + tmp->m_realName.Adopt(newName); + } + else + tmp->m_realName.Adopt(mime_part_address(object)); + } else { + tmp->m_hasFilename = true; + } + + if (!tmp->m_realName.IsEmpty() && !tmp->m_isExternalAttachment) + { + urlString.Append("&filename="); + nsAutoCString aResult; + if (NS_SUCCEEDED(MsgEscapeString(tmp->m_realName, + nsINetUtil::ESCAPE_XALPHAS, aResult))) + urlString.Append(aResult); + else + urlString.Append(tmp->m_realName); + if (tmp->m_realType.EqualsLiteral("message/rfc822") && + !StringEndsWith(urlString, NS_LITERAL_CSTRING(".eml"), nsCaseInsensitiveCStringComparator())) + urlString.Append(".eml"); + } else if (tmp->m_isExternalAttachment) { + // Allows the JS mime emitter to figure out the part information. + urlString.Append("?part="); + urlString.Append(part); + } else if (tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822)) { + // Special case...if this is a enclosed RFC822 message, give it a nice + // name. + if (object->headers->munged_subject) + { + nsCString subject; + subject.Assign(object->headers->munged_subject); + MimeHeaders_convert_header_value(options, subject, false); + tmp->m_realName.Assign(subject); + tmp->m_realName.Append(".eml"); + } + else + tmp->m_realName = "ForwardedMessage.eml"; + } + + nsresult rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), urlString.get(), nullptr); + + PR_FREEIF(urlSpec); + + if (NS_FAILED(rv) || !tmp->m_url) + return NS_ERROR_OUT_OF_MEMORY; + + ValidateRealName(tmp, object->headers); + + return NS_OK; +} + +nsresult +BuildAttachmentList(MimeObject *anObject, nsMsgAttachmentData *aAttachData, const char *aMessageURL) +{ + nsresult rv; + int32_t i; + MimeContainer *cobj = (MimeContainer *) anObject; + bool found_output = false; + + if ( (!anObject) || (!cobj->children) || (!cobj->nchildren) || + (mime_typep(anObject, (MimeObjectClass *)&mimeExternalBodyClass))) + return NS_OK; + + for (i = 0; i < cobj->nchildren ; i++) + { + MimeObject *child = cobj->children[i]; + char *ct = child->content_type; + + // We're going to ignore the output_p attribute because we want to output + // any part with a name to work around bug 674473 + + // Skip the first child that's being output if it's in fact a message body. + // Start by assuming that it is, until proven otherwise in the code below. + bool skip = true; + if (found_output) + // not first child being output + skip = false; + else if (! ct) + // no content type so can't be message body + skip = false; + else if (PL_strcasecmp (ct, TEXT_PLAIN) && + PL_strcasecmp (ct, TEXT_HTML) && + PL_strcasecmp (ct, TEXT_MDL)) + // not a type we recognize as a message body + skip = false; + // we're displaying all body parts + if (child->options->html_as_p == 4) + skip = false; + if (skip && child->headers) + { + // If it has a filename, we don't skip it regardless of the + // content disposition which can be "inline" or "attachment". + // Inline parts are not shown when attachments aren't displayed + // inline, so the only chance to see the part is as attachment. + char * name = MimeHeaders_get_name(child->headers, nullptr); + if (name) + skip = false; + PR_FREEIF(name); + } + + found_output = true; + if (skip) + continue; + + // We should generate an attachment for leaf object only but... + bool isALeafObject = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeLeafClass); + + // ...we will generate an attachment for inline message too. + bool isAnInlineMessage = mime_typep(child, (MimeObjectClass *) &mimeMessageClass); + + // AppleDouble part need special care: we need to fetch the part as well its two + // children for the needed info as they could be anywhere, eventually, they won't contain + // a name or file name. In any case we need to build only one attachment data + bool isAnAppleDoublePart = mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass) && + ((MimeContainer *)child)->nchildren == 2; + + // The function below does not necessarily set the size to something (I + // don't think it will work for external objects, for instance, since they + // are neither containers nor leafs). + int32_t attSize = 0; + MimeGetSize(child, &attSize); + + if (isALeafObject || isAnInlineMessage || isAnAppleDoublePart) + { + rv = GenerateAttachmentData(child, aMessageURL, anObject->options, isAnAppleDoublePart, attSize, aAttachData); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Now build the attachment list for the children of our object... + if (!isALeafObject && !isAnAppleDoublePart) + { + rv = BuildAttachmentList((MimeObject *)child, aAttachData, aMessageURL); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; + +} + +extern "C" nsresult +MimeGetAttachmentList(MimeObject *tobj, const char *aMessageURL, nsMsgAttachmentData **data) +{ + MimeObject *obj; + MimeContainer *cobj; + int32_t n; + bool isAnInlineMessage; + + if (!data) + return NS_ERROR_INVALID_ARG; + *data = nullptr; + + obj = mime_get_main_object(tobj); + if (!obj) + return NS_OK; + + if (!mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeContainerClass)) + return ProcessBodyAsAttachment(obj, data); + + isAnInlineMessage = mime_typep(obj, (MimeObjectClass *) &mimeMessageClass); + + cobj = (MimeContainer*) obj; + n = CountTotalMimeAttachments(cobj); + if (n <= 0) + // XXX n is a regular number here, not meaningful as an nsresult + return static_cast<nsresult>(n); + + // in case of an inline message (as body), we need an extra slot for the + // message itself that we will fill later... + if (isAnInlineMessage) + n ++; + + *data = new nsMsgAttachmentData[n + 1]; + if (!*data) + return NS_ERROR_OUT_OF_MEMORY; + + attIndex = 0; + + // Now, build the list! + + nsresult rv; + + if (isAnInlineMessage) + { + int32_t size = 0; + MimeGetSize(obj, &size); + rv = GenerateAttachmentData(obj, aMessageURL, obj->options, false, size, + *data); + if (NS_FAILED(rv)) + { + delete [] *data; // release data in case of error return. + return rv; + } + + } + rv = BuildAttachmentList((MimeObject *) cobj, *data, aMessageURL); + if (NS_FAILED(rv)) + { + delete [] *data; // release data in case of error return. + } + return rv; +} + +extern "C" void +MimeFreeAttachmentList(nsMsgAttachmentData *data) +{ + delete [] data; +} + +extern "C" void +NotifyEmittersOfAttachmentList(MimeDisplayOptions *opt, + nsMsgAttachmentData *data) +{ + int32_t i = 0; + nsMsgAttachmentData *tmp = data; + + if (!tmp) + return; + + while (tmp->m_url) + { + // The code below implements the following logic: + // - Always display the attachment if the Content-Disposition is + // "attachment" or if it can't be displayed inline. + // - If there's no name at all, just skip it (we don't know what to do with + // it then). + // - If the attachment has a "provided name" (i.e. not something like "Part + // 1.2"), display it. + // - If we're asking for all body parts and NOT asking for metadata only, + // display it. + // - Otherwise, skip it. + if (!tmp->m_disposition.Equals("attachment") && tmp->m_displayableInline && + (tmp->m_realName.IsEmpty() || (!tmp->m_hasFilename && + (opt->html_as_p != 4 || opt->metadata_only)))) + { + ++i; + ++tmp; + continue; + } + + nsAutoCString spec; + if (tmp->m_url) { + if (tmp->m_isExternalLinkAttachment) + mozilla::Unused << tmp->m_url->GetAsciiSpec(spec); + else + mozilla::Unused << tmp->m_url->GetSpec(spec); + } + + nsAutoCString sizeStr; + if (tmp->m_isExternalLinkAttachment) + sizeStr.Append(tmp->m_sizeExternalStr); + else + sizeStr.AppendInt(tmp->m_size); + + nsAutoCString downloadedStr; + downloadedStr.AppendInt(tmp->m_isDownloaded); + + mimeEmitterStartAttachment(opt, tmp->m_realName.get(), tmp->m_realType.get(), + spec.get(), tmp->m_isExternalAttachment); + mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_URL, spec.get()); + mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_SIZE, sizeStr.get()); + mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_DOWNLOADED, downloadedStr.get()); + + if ( (opt->format_out == nsMimeOutput::nsMimeMessageQuoting) || + (opt->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) || + (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs) || + (opt->format_out == nsMimeOutput::nsMimeMessagePrintOutput)) + { + mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_DESCRIPTION, tmp->m_description.get()); + mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_TYPE, tmp->m_realType.get()); + mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_ENCODING, tmp->m_realEncoding.get()); + } + + mimeEmitterEndAttachment(opt); + ++i; + ++tmp; + } + mimeEmitterEndAllAttachments(opt); +} + +// Utility to create a nsIURI object... +extern "C" nsresult +nsMimeNewURI(nsIURI** aInstancePtrResult, const char *aSpec, nsIURI *aBase) +{ + if (nullptr == aInstancePtrResult) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsIIOService> pService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(pService, NS_ERROR_FACTORY_NOT_REGISTERED); + + return pService->NewURI(nsDependentCString(aSpec), nullptr, aBase, aInstancePtrResult); +} + +extern "C" nsresult +SetMailCharacterSetToMsgWindow(MimeObject *obj, const char *aCharacterSet) +{ + nsresult rv = NS_OK; + + if (obj && obj->options) + { + mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure); + if (msd) + { + nsIChannel *channel = msd->channel; + if (channel) + { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(uri)); + if (msgurl) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + rv = msgWindow->SetMailCharacterSet(!PL_strcasecmp(aCharacterSet, "us-ascii") ? + static_cast<const nsCString&>(NS_LITERAL_CSTRING("ISO-8859-1")) : + static_cast<const nsCString&>(nsDependentCString(aCharacterSet))); + } + } + } + } + } + + return rv; +} + +static void ResetMsgHeaderSinkProps(nsIURI *uri) +{ + nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(uri)); + if (!msgurl) + return; + + nsCOMPtr<nsIMsgWindow> msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (!msgWindow) + return; + + nsCOMPtr<nsIMsgHeaderSink> msgHeaderSink; + msgWindow->GetMsgHeaderSink(getter_AddRefs(msgHeaderSink)); + if (!msgHeaderSink) + return; + + msgHeaderSink->ResetProperties(); +} + +static char * +mime_file_type (const char *filename, void *stream_closure) +{ + char *retType = nullptr; + char *ext = nullptr; + nsresult rv; + + ext = PL_strrchr(filename, '.'); + if (ext) + { + ext++; + nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + if (mimeFinder) { + nsAutoCString type; + mimeFinder->GetTypeFromExtension(nsDependentCString(ext), type); + retType = ToNewCString(type); + } + } + + return retType; +} + +int ConvertUsingEncoderAndDecoder(const char *stringToUse, int32_t inLength, + nsIUnicodeEncoder *encoder, nsIUnicodeDecoder *decoder, + char **pConvertedString, int32_t *outLength) +{ + // buffer size 144 = + // 72 (default line len for compose) + // times 2 (converted byte len might be larger) + const int klocalbufsize = 144; + // do the conversion + char16_t *unichars; + int32_t unicharLength; + int32_t srcLen = inLength; + int32_t dstLength = 0; + char *dstPtr; + nsresult rv; + + // use this local buffer if possible + char16_t localbuf[klocalbufsize+1]; + if (inLength > klocalbufsize) { + rv = decoder->GetMaxLength(stringToUse, srcLen, &unicharLength); + // allocate temporary buffer to hold unicode string + unichars = new char16_t[unicharLength]; + } + else { + unichars = localbuf; + unicharLength = klocalbufsize+1; + } + if (unichars == nullptr) { + rv = NS_ERROR_OUT_OF_MEMORY; + } + else { + // convert to unicode, replacing failed chars with 0xFFFD as in + // the methode used in nsXMLHttpRequest::ConvertBodyToText and nsScanner::Append + // + // We will need several pass to convert the whole string if it has invalid characters + // 'totalChars' is where the sum of the number of converted characters will be done + // 'dataLen' is the number of character left to convert + // 'outLen' is the number of characters still available in the output buffer as input of decoder->Convert + // and the number of characters written in it as output. + int32_t totalChars = 0, + inBufferIndex = 0, + outBufferIndex = 0; + int32_t dataLen = srcLen, + outLen = unicharLength; + + do { + int32_t inBufferLength = dataLen; + rv = decoder->Convert(&stringToUse[inBufferIndex], + &inBufferLength, + &unichars[outBufferIndex], + &outLen); + totalChars += outLen; + // Done if conversion successful + if (NS_SUCCEEDED(rv)) + break; + + // We consume one byte, replace it with U+FFFD + // and try the conversion again. + outBufferIndex += outLen; + unichars[outBufferIndex++] = char16_t(0xFFFD); + // totalChars is updated here + outLen = unicharLength - (++totalChars); + + inBufferIndex += inBufferLength + 1; + dataLen -= inBufferLength + 1; + + decoder->Reset(); + + // If there is not at least one byte available after the one we + // consumed, we're done + } while ( dataLen > 0 ); + + rv = encoder->GetMaxLength(unichars, totalChars, &dstLength); + // allocale an output buffer + dstPtr = (char *) PR_Malloc(dstLength + 1); + if (dstPtr == nullptr) { + rv = NS_ERROR_OUT_OF_MEMORY; + } + else { + int32_t buffLength = dstLength; + // convert from unicode + rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nullptr, '?'); + if (NS_SUCCEEDED(rv)) { + rv = encoder->Convert(unichars, &totalChars, dstPtr, &dstLength); + if (NS_SUCCEEDED(rv)) { + int32_t finLen = buffLength - dstLength; + rv = encoder->Finish((char *)(dstPtr+dstLength), &finLen); + if (NS_SUCCEEDED(rv)) { + dstLength += finLen; + } + dstPtr[dstLength] = '\0'; + *pConvertedString = dstPtr; // set the result string + *outLength = dstLength; + } + } + } + if (inLength > klocalbufsize) + delete [] unichars; + } + + return NS_SUCCEEDED(rv) ? 0 : -1; +} + + +static int +mime_convert_charset (const char *input_line, int32_t input_length, + const char *input_charset, const char *output_charset, + char **output_ret, int32_t *output_size_ret, + void *stream_closure, nsIUnicodeDecoder *decoder, nsIUnicodeEncoder *encoder) +{ + int32_t res = -1; + char *convertedString = NULL; + int32_t convertedStringLen = 0; + if (encoder && decoder) + { + res = ConvertUsingEncoderAndDecoder(input_line, input_length, encoder, decoder, &convertedString, &convertedStringLen); + } + if (res != 0) + { + *output_ret = 0; + *output_size_ret = 0; + } + else + { + *output_ret = (char *) convertedString; + *output_size_ret = convertedStringLen; + } + + return 0; +} + +static int +mime_output_fn(const char *buf, int32_t size, void *stream_closure) +{ + uint32_t written = 0; + mime_stream_data *msd = (mime_stream_data *) stream_closure; + if ( (!msd->pluginObj2) && (!msd->output_emitter) ) + return -1; + + // Fire pending start request + ((nsStreamConverter*)msd->pluginObj2)->FirePendingStartRequest(); + + + // Now, write to the WriteBody method if this is a message body and not + // a part retrevial + if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) + { + if (msd->output_emitter) + { + msd->output_emitter->WriteBody(Substring(buf, buf+size), + &written); + } + } + else + { + if (msd->output_emitter) + { + msd->output_emitter->Write(Substring(buf, buf+size), &written); + } + } + return written; +} + +extern "C" int +mime_display_stream_write (nsMIMESession *stream, + const char* buf, + int32_t size) +{ + mime_stream_data *msd = (mime_stream_data *) ((nsMIMESession *)stream)->data_object; + + MimeObject *obj = (msd ? msd->obj : 0); + if (!obj) return -1; + + // + // Ok, now check to see if this is a display operation for a MIME Parts on Demand + // enabled call. + // + if (msd->firstCheck) + { + if (msd->channel) + { + nsCOMPtr<nsIURI> aUri; + if (NS_SUCCEEDED(msd->channel->GetURI(getter_AddRefs(aUri)))) + { + nsCOMPtr<nsIImapUrl> imapURL = do_QueryInterface(aUri); + if (imapURL) + { + nsImapContentModifiedType cModified; + if (NS_SUCCEEDED(imapURL->GetContentModified(&cModified))) + { + if ( cModified != nsImapContentModifiedTypes::IMAP_CONTENT_NOT_MODIFIED ) + msd->options->missing_parts = true; + } + } + } + } + + msd->firstCheck = false; + } + + return obj->clazz->parse_buffer((char *) buf, size, obj); +} + +extern "C" void +mime_display_stream_complete (nsMIMESession *stream) +{ + mime_stream_data *msd = (mime_stream_data *) ((nsMIMESession *)stream)->data_object; + MimeObject *obj = (msd ? msd->obj : 0); + if (obj) + { + int status; + bool abortNow = false; + + if ((obj->options) && (obj->options->headers == MimeHeadersOnly)) + abortNow = true; + + status = obj->clazz->parse_eof(obj, abortNow); + obj->clazz->parse_end(obj, (status < 0 ? true : false)); + + // + // Ok, now we are going to process the attachment data by getting all + // of the attachment info and then driving the emitter with this data. + // + if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) + { + nsMsgAttachmentData *attachments; + nsresult rv = MimeGetAttachmentList(obj, msd->url_name, &attachments); + if (NS_SUCCEEDED(rv)) + { + NotifyEmittersOfAttachmentList(msd->options, attachments); + MimeFreeAttachmentList(attachments); + } + } + + // Release the conversion object - this has to be done after + // we finish processing data. + if ( obj->options) + { + NS_IF_RELEASE(obj->options->conv); + } + + // Destroy the object now. + PR_ASSERT(msd->options == obj->options); + mime_free(obj); + obj = NULL; + if (msd->options) + { + delete msd->options; + msd->options = 0; + } + } + + if (msd->headers) + MimeHeaders_free (msd->headers); + + if (msd->url_name) + NS_Free(msd->url_name); + + if (msd->orig_url_name) + NS_Free(msd->orig_url_name); + + delete msd; +} + +extern "C" void +mime_display_stream_abort (nsMIMESession *stream, int status) +{ + mime_stream_data *msd = (mime_stream_data *) ((nsMIMESession *)stream)->data_object; + + MimeObject *obj = (msd ? msd->obj : 0); + if (obj) + { + if (!obj->closed_p) + obj->clazz->parse_eof(obj, true); + if (!obj->parsed_p) + obj->clazz->parse_end(obj, true); + + // Destroy code.... + PR_ASSERT(msd->options == obj->options); + mime_free(obj); + if (msd->options) + { + delete msd->options; + msd->options = 0; + } + } + + if (msd->headers) + MimeHeaders_free (msd->headers); + + if (msd->url_name) + NS_Free(msd->url_name); + + if (msd->orig_url_name) + NS_Free(msd->orig_url_name); + + delete msd; +} + +static int +mime_output_init_fn (const char *type, + const char *charset, + const char *name, + const char *x_mac_type, + const char *x_mac_creator, + void *stream_closure) +{ + mime_stream_data *msd = (mime_stream_data *) stream_closure; + + // Now, all of this stream creation is done outside of libmime, so this + // is just a check of the pluginObj member and returning accordingly. + if (!msd->pluginObj2) + return -1; + else + return 0; +} + +static void *mime_image_begin(const char *image_url, const char *content_type, + void *stream_closure); +static void mime_image_end(void *image_closure, int status); +static char *mime_image_make_image_html(void *image_data); +static int mime_image_write_buffer(const char *buf, int32_t size, void *image_closure); + +/* Interface between libmime and inline display of images: the abomination + that is known as "internal-external-reconnect". + */ +class mime_image_stream_data { +public: + mime_image_stream_data(); + + mime_stream_data *msd; + char *url; + nsMIMESession *istream; +}; + +mime_image_stream_data::mime_image_stream_data() +{ + url = nullptr; + istream = nullptr; + msd = nullptr; +} + +static void * +mime_image_begin(const char *image_url, const char *content_type, + void *stream_closure) +{ + mime_stream_data *msd = (mime_stream_data *) stream_closure; + class mime_image_stream_data *mid; + + mid = new mime_image_stream_data; + if (!mid) return nullptr; + + + mid->msd = msd; + + mid->url = (char *) strdup(image_url); + if (!mid->url) + { + PR_Free(mid); + return nullptr; + } + + mid->istream = (nsMIMESession *) msd->pluginObj2; + return mid; +} + +static void +mime_image_end(void *image_closure, int status) +{ + mime_image_stream_data *mid = + (mime_image_stream_data *) image_closure; + + PR_ASSERT(mid); + if (!mid) + return; + + PR_FREEIF(mid->url); + delete mid; +} + + +static char * +mime_image_make_image_html(void *image_closure) +{ + mime_image_stream_data *mid = + (mime_image_stream_data *) image_closure; + + const char *prefix; + /* Wouldn't it be nice if attributes were case-sensitive? */ + const char *scaledPrefix = "<P><CENTER><IMG CLASS=\"moz-attached-image\" shrinktofit=\"yes\" SRC=\""; + const char *unscaledPrefix = "<P><CENTER><IMG CLASS=\"moz-attached-image\" SRC=\""; + const char *suffix = "\"></CENTER><P>"; + const char *url; + char *buf; + + PR_ASSERT(mid); + if (!mid) return 0; + + /* Internal-external-reconnect only works when going to the screen. */ + if (!mid->istream) + return strdup("<P><CENTER><IMG SRC=\"resource://gre-resources/loading-image.png\" ALT=\"[Image]\"></CENTER><P>"); + + nsCOMPtr<nsIPrefBranch> prefBranch; + nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID)); + bool resize = true; + + if (prefSvc) + prefSvc->GetBranch("", getter_AddRefs(prefBranch)); + if (prefBranch) + prefBranch->GetBoolPref("mail.enable_automatic_image_resizing", &resize); // ignore return value + prefix = resize ? scaledPrefix : unscaledPrefix; + + if ( (!mid->url) || (!(*mid->url)) ) + url = ""; + else + url = mid->url; + + uint32_t buflen = strlen(prefix) + strlen(suffix) + strlen(url) + 20; + buf = (char *) PR_MALLOC (buflen); + if (!buf) + return 0; + *buf = 0; + + PL_strcatn (buf, buflen, prefix); + PL_strcatn (buf, buflen, url); + PL_strcatn (buf, buflen, suffix); + return buf; +} + +static int +mime_image_write_buffer(const char *buf, int32_t size, void *image_closure) +{ + mime_image_stream_data *mid = + (mime_image_stream_data *) image_closure; + mime_stream_data *msd = mid->msd; + + if ( ( (!msd->output_emitter) ) && + ( (!msd->pluginObj2) ) ) + return -1; + + return size; +} + +MimeObject* +mime_get_main_object(MimeObject* obj) +{ + MimeContainer *cobj; + if (!(mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeMessageClass))) + { + return obj; + } + cobj = (MimeContainer*) obj; + if (cobj->nchildren != 1) return obj; + obj = cobj->children[0]; + while (obj) + { + if ( (!mime_subclass_p(obj->clazz, + (MimeObjectClass*) &mimeMultipartSignedClass)) && + (PL_strcasecmp(obj->content_type, MULTIPART_SIGNED) != 0) + ) + { + return obj; + } + else + { + if (mime_subclass_p(obj->clazz, (MimeObjectClass*)&mimeContainerClass)) + { + // We don't care about a signed/smime object; Go inside to the + // thing that we signed or smime'ed + // + cobj = (MimeContainer*) obj; + if (cobj->nchildren > 0) + obj = cobj->children[0]; + else + obj = nullptr; + } + else + { + // we received a message with a child object that looks like a signed + // object, but it is not a subclass of mimeContainer, so let's + // return the given child object. + return obj; + } + } + } + return nullptr; +} + +static +bool MimeObjectIsMessageBodyNoClimb(MimeObject *parent, + MimeObject *looking_for, + bool *stop) +{ + MimeContainer *container = (MimeContainer *)parent; + int32_t i; + char *disp; + + NS_ASSERTION(stop, "NULL stop to MimeObjectIsMessageBodyNoClimb"); + + for (i = 0; i < container->nchildren; i++) { + MimeObject *child = container->children[i]; + bool is_body = true; + + // The body can't be something we're not displaying. + if (! child->output_p) + is_body = false; + else if ((disp = MimeHeaders_get (child->headers, HEADER_CONTENT_DISPOSITION, + true, false))) { + PR_Free(disp); + is_body = false; + } + else if (PL_strcasecmp (child->content_type, TEXT_PLAIN) && + PL_strcasecmp (child->content_type, TEXT_HTML) && + PL_strcasecmp (child->content_type, TEXT_MDL) && + PL_strcasecmp (child->content_type, MESSAGE_NEWS) && + PL_strcasecmp (child->content_type, MESSAGE_RFC822)) + is_body = false; + + if (is_body || child == looking_for) { + *stop = true; + return child == looking_for; + } + + // The body could be down inside a multipart child, so search recursively. + if (mime_subclass_p(child->clazz, (MimeObjectClass*) &mimeContainerClass)) { + is_body = MimeObjectIsMessageBodyNoClimb(child, looking_for, stop); + if (is_body || *stop) + return is_body; + } + } + return false; +} + +/* Should this be static in mimemult.cpp? */ +bool MimeObjectIsMessageBody(MimeObject *looking_for) +{ + bool stop = false; + MimeObject *root = looking_for; + while (root->parent) + root = root->parent; + return MimeObjectIsMessageBodyNoClimb(root, looking_for, &stop); +} + +// +// New Stream Converter Interface +// + +// Get the connnection to prefs service manager +nsIPrefBranch * +GetPrefBranch(MimeDisplayOptions *opt) +{ + if (!opt) + return nullptr; + + return opt->m_prefBranch; +} + +// Get the text converter... +mozITXTToHTMLConv * +GetTextConverter(MimeDisplayOptions *opt) +{ + if (!opt) + return nullptr; + + return opt->conv; +} + +MimeDisplayOptions::MimeDisplayOptions() +{ + conv = nullptr; // For text conversion... + format_out = 0; // The format out type + url = nullptr; + + memset(&headers,0, sizeof(headers)); + fancy_headers_p = false; + + output_vcard_buttons_p = false; + + variable_width_plaintext_p = false; + wrap_long_lines_p = false; + rot13_p = false; + part_to_load = nullptr; + + no_output_p = false; + write_html_p = false; + + decrypt_p = false; + + whattodo = 0 ; + default_charset = nullptr; + override_charset = false; + force_user_charset = false; + stream_closure = nullptr; + + /* For setting up the display stream, so that the MIME parser can inform + the caller of the type of the data it will be getting. */ + output_init_fn = nullptr; + output_fn = nullptr; + + output_closure = nullptr; + + charset_conversion_fn = nullptr; + rfc1522_conversion_p = false; + + file_type_fn = nullptr; + + passwd_prompt_fn = nullptr; + + html_closure = nullptr; + + generate_header_html_fn = nullptr; + generate_post_header_html_fn = nullptr; + generate_footer_html_fn = nullptr; + generate_reference_url_fn = nullptr; + generate_mailto_url_fn = nullptr; + generate_news_url_fn = nullptr; + + image_begin = nullptr; + image_end = nullptr; + image_write_buffer = nullptr; + make_image_html = nullptr; + state = nullptr; + +#ifdef MIME_DRAFTS + decompose_file_p = false; + done_parsing_outer_headers = false; + is_multipart_msg = false; + decompose_init_count = 0; + + signed_p = false; + caller_need_root_headers = false; + decompose_headers_info_fn = nullptr; + decompose_file_init_fn = nullptr; + decompose_file_output_fn = nullptr; + decompose_file_close_fn = nullptr; +#endif /* MIME_DRAFTS */ + + attachment_icon_layer_id = 0; + + missing_parts = false; + show_attachment_inline_p = false; + quote_attachment_inline_p = false; + notify_nested_bodies = false; + write_pure_bodies = false; + metadata_only = false; +} + +MimeDisplayOptions::~MimeDisplayOptions() +{ + PR_FREEIF(part_to_load); + PR_FREEIF(default_charset); +} +//////////////////////////////////////////////////////////////// +// Bridge routines for new stream converter XP-COM interface +//////////////////////////////////////////////////////////////// +extern "C" void * +mime_bridge_create_display_stream( + nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out, + uint32_t whattodo, + nsIChannel *aChannel) +{ + int status = 0; + MimeObject *obj; + mime_stream_data *msd; + nsMIMESession *stream = 0; + + if (!uri) + return nullptr; + + msd = new mime_stream_data; + if (!msd) + return NULL; + + // Assign the new mime emitter - will handle output operations + msd->output_emitter = newEmitter; + msd->firstCheck = true; + + // Store the URL string for this decode operation + nsAutoCString urlString; + nsresult rv; + + // Keep a hold of the channel... + msd->channel = aChannel; + rv = uri->GetSpec(urlString); + if (NS_SUCCEEDED(rv)) + { + if (!urlString.IsEmpty()) + { + msd->url_name = ToNewCString(urlString); + if (!(msd->url_name)) + { + delete msd; + return NULL; + } + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(uri); + if (msgUrl) + msgUrl->GetOriginalSpec(&msd->orig_url_name); + } + } + + msd->format_out = format_out; // output format + msd->pluginObj2 = newPluginObj2; // the plugin object pointer + + msd->options = new MimeDisplayOptions; + if (!msd->options) + { + delete msd; + return 0; + } +// memset(msd->options, 0, sizeof(*msd->options)); + msd->options->format_out = format_out; // output format + + msd->options->m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + { + delete msd; + return nullptr; + } + + // Need the text converter... + rv = CallCreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &(msd->options->conv)); + if (NS_FAILED(rv)) + { + msd->options->m_prefBranch = nullptr; + delete msd; + return nullptr; + } + + // + // Set the defaults, based on the context, and the output-type. + // + MIME_HeaderType = MimeHeadersAll; + msd->options->write_html_p = true; + switch (format_out) + { + case nsMimeOutput::nsMimeMessageSplitDisplay: // the wrapper HTML output to produce the split header/body display + case nsMimeOutput::nsMimeMessageHeaderDisplay: // the split header/body display + case nsMimeOutput::nsMimeMessageBodyDisplay: // the split header/body display + msd->options->fancy_headers_p = true; + msd->options->output_vcard_buttons_p = true; + break; + + case nsMimeOutput::nsMimeMessageSaveAs: // Save As operations + case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted/printed output + case nsMimeOutput::nsMimeMessagePrintOutput: + msd->options->fancy_headers_p = true; + break; + + case nsMimeOutput::nsMimeMessageBodyQuoting: // only HTML body quoted output + MIME_HeaderType = MimeHeadersNone; + break; + + case nsMimeOutput::nsMimeMessageAttach: // handling attachment storage + msd->options->write_html_p = false; + break; + case nsMimeOutput::nsMimeMessageRaw: // the raw RFC822 data (view source) and attachments + case nsMimeOutput::nsMimeMessageDraftOrTemplate: // Loading drafts & templates + case nsMimeOutput::nsMimeMessageEditorTemplate: // Loading templates into editor + case nsMimeOutput::nsMimeMessageFilterSniffer: // generating an output that can be scan by a message filter + break; + + case nsMimeOutput::nsMimeMessageDecrypt: + msd->options->decrypt_p = true; + msd->options->write_html_p = false; + break; + } + + //////////////////////////////////////////////////////////// + // Now, get the libmime prefs... + //////////////////////////////////////////////////////////// + + MIME_WrapLongLines = true; + MIME_VariableWidthPlaintext = true; + msd->options->force_user_charset = false; + + if (msd->options->m_prefBranch) + { + msd->options->m_prefBranch->GetBoolPref("mail.wrap_long_lines", &MIME_WrapLongLines); + msd->options->m_prefBranch->GetBoolPref("mail.fixed_width_messages", &MIME_VariableWidthPlaintext); + // + // Charset overrides takes place here + // + // We have a bool pref (mail.force_user_charset) to deal with attachments. + // 1) If true - libmime does NO conversion and just passes it through to raptor + // 2) If false, then we try to use the charset of the part and if not available, + // the charset of the root message + // + msd->options->m_prefBranch->GetBoolPref("mail.force_user_charset", &(msd->options->force_user_charset)); + msd->options->m_prefBranch->GetBoolPref("mail.inline_attachments", &(msd->options->show_attachment_inline_p)); + msd->options->m_prefBranch->GetBoolPref("mail.reply_quote_inline", &(msd->options->quote_attachment_inline_p)); + msd->options->m_prefBranch->GetIntPref("mailnews.display.html_as", &(msd->options->html_as_p)); + } + /* This pref is written down in with the + opposite sense of what we like to use... */ + MIME_VariableWidthPlaintext = !MIME_VariableWidthPlaintext; + + msd->options->wrap_long_lines_p = MIME_WrapLongLines; + msd->options->headers = MIME_HeaderType; + + // We need to have the URL to be able to support the various + // arguments + status = mime_parse_url_options(msd->url_name, msd->options); + if (status < 0) + { + PR_FREEIF(msd->options->part_to_load); + PR_Free(msd->options); + delete msd; + return 0; + } + + if (msd->options->headers == MimeHeadersMicro && + (msd->url_name == NULL || (strncmp(msd->url_name, "news:", 5) != 0 && + strncmp(msd->url_name, "snews:", 6) != 0)) ) + msd->options->headers = MimeHeadersMicroPlus; + + msd->options->url = msd->url_name; + msd->options->output_init_fn = mime_output_init_fn; + + msd->options->output_fn = mime_output_fn; + + msd->options->whattodo = whattodo; + msd->options->charset_conversion_fn = mime_convert_charset; + msd->options->rfc1522_conversion_p = true; + msd->options->file_type_fn = mime_file_type; + msd->options->stream_closure = msd; + msd->options->passwd_prompt_fn = 0; + + msd->options->image_begin = mime_image_begin; + msd->options->image_end = mime_image_end; + msd->options->make_image_html = mime_image_make_image_html; + msd->options->image_write_buffer = mime_image_write_buffer; + + msd->options->variable_width_plaintext_p = MIME_VariableWidthPlaintext; + + // If this is a part, then we should emit the HTML to render the data + // (i.e. embedded images) + if (msd->options->part_to_load && msd->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay) + msd->options->write_html_p = false; + + obj = mime_new ((MimeObjectClass *)&mimeMessageClass, (MimeHeaders *) NULL, MESSAGE_RFC822); + if (!obj) + { + delete msd->options; + delete msd; + return 0; + } + + obj->options = msd->options; + msd->obj = obj; + + /* Both of these better not be true at the same time. */ + PR_ASSERT(! (obj->options->decrypt_p && obj->options->write_html_p)); + + stream = PR_NEW(nsMIMESession); + if (!stream) + { + delete msd->options; + delete msd; + PR_Free(obj); + return 0; + } + + ResetMsgHeaderSinkProps(uri); + + memset (stream, 0, sizeof (*stream)); + stream->name = "MIME Conversion Stream"; + stream->complete = mime_display_stream_complete; + stream->abort = mime_display_stream_abort; + stream->put_block = mime_display_stream_write; + stream->data_object = msd; + + status = obj->clazz->initialize(obj); + if (status >= 0) + status = obj->clazz->parse_begin(obj); + if (status < 0) + { + PR_Free(stream); + delete msd->options; + delete msd; + PR_Free(obj); + return 0; + } + + return stream; +} + +// +// Emitter Wrapper Routines! +// +nsIMimeEmitter * +GetMimeEmitter(MimeDisplayOptions *opt) +{ + mime_stream_data *msd = (mime_stream_data *)opt->stream_closure; + if (!msd) + return NULL; + + nsIMimeEmitter *ptr = (nsIMimeEmitter *)(msd->output_emitter); + return ptr; +} + +mime_stream_data * +GetMSD(MimeDisplayOptions *opt) +{ + if (!opt) + return nullptr; + mime_stream_data *msd = (mime_stream_data *)opt->stream_closure; + return msd; +} + +bool +NoEmitterProcessing(nsMimeOutputType format_out) +{ + if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (format_out == nsMimeOutput::nsMimeMessageEditorTemplate)) + return true; + else + return false; +} + +extern "C" nsresult +mimeEmitterAddAttachmentField(MimeDisplayOptions *opt, const char *field, const char *value) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + return emitter->AddAttachmentField(field, value); + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterAddHeaderField(MimeDisplayOptions *opt, const char *field, const char *value) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + return emitter->AddHeaderField(field, value); + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterAddAllHeaders(MimeDisplayOptions *opt, const char *allheaders, const int32_t allheadersize) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + return emitter->AddAllHeaders(Substring(allheaders, + allheaders + allheadersize)); + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterStartAttachment(MimeDisplayOptions *opt, const char *name, const char *contentType, const char *url, + bool aIsExternalAttachment) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + return emitter->StartAttachment(nsDependentCString(name), contentType, url, + aIsExternalAttachment); + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterEndAttachment(MimeDisplayOptions *opt) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + if (emitter) + return emitter->EndAttachment(); + else + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterEndAllAttachments(MimeDisplayOptions *opt) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + if (emitter) + return emitter->EndAllAttachments(); + else + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterStartBody(MimeDisplayOptions *opt, bool bodyOnly, const char *msgID, const char *outCharset) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + return emitter->StartBody(bodyOnly, msgID, outCharset); + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterEndBody(MimeDisplayOptions *opt) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + return emitter->EndBody(); + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterEndHeader(MimeDisplayOptions *opt, MimeObject *obj) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + + nsCString name; + if (msd->format_out == nsMimeOutput::nsMimeMessageSplitDisplay || + msd->format_out == nsMimeOutput::nsMimeMessageHeaderDisplay || + msd->format_out == nsMimeOutput::nsMimeMessageBodyDisplay || + msd->format_out == nsMimeOutput::nsMimeMessageSaveAs || + msd->format_out == nsMimeOutput::nsMimeMessagePrintOutput) { + if (obj->headers) { + nsMsgAttachmentData attachment; + attIndex = 0; + nsresult rv = GenerateAttachmentData(obj, msd->url_name, opt, false, + 0, &attachment); + + if (NS_SUCCEEDED(rv)) + name.Assign(attachment.m_realName); + } + } + + MimeHeaders_convert_header_value(opt, name, false); + return emitter->EndHeader(name); + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterUpdateCharacterSet(MimeDisplayOptions *opt, const char *aCharset) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + return emitter->UpdateCharacterSet(aCharset); + } + + return NS_ERROR_FAILURE; +} + +extern "C" nsresult +mimeEmitterStartHeader(MimeDisplayOptions *opt, bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) +{ + // Check for draft processing... + if (NoEmitterProcessing(opt->format_out)) + return NS_OK; + + mime_stream_data *msd = GetMSD(opt); + if (!msd) + return NS_ERROR_FAILURE; + + if (msd->output_emitter) + { + nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter; + return emitter->StartHeader(rootMailHeader, headerOnly, msgID, outCharset); + } + + return NS_ERROR_FAILURE; +} + + +extern "C" nsresult +mimeSetNewURL(nsMIMESession *stream, char *url) +{ + if ( (!stream) || (!url) || (!*url) ) + return NS_ERROR_FAILURE; + + mime_stream_data *msd = (mime_stream_data *)stream->data_object; + if (!msd) + return NS_ERROR_FAILURE; + + char *tmpPtr = strdup(url); + if (!tmpPtr) + return NS_ERROR_OUT_OF_MEMORY; + + PR_FREEIF(msd->url_name); + msd->url_name = tmpPtr; + return NS_OK; +} + +#define MIME_URL "chrome://messenger/locale/mime.properties" + +extern "C" +char * +MimeGetStringByID(int32_t stringID) +{ + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + + nsCOMPtr<nsIStringBundle> stringBundle; + stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle)); + if (stringBundle) + { + nsString v; + if (NS_SUCCEEDED(stringBundle->GetStringFromID(stringID, getter_Copies(v)))) + return ToNewUTF8String(v); + } + + return strdup("???"); +} + +extern "C" +char * +MimeGetStringByName(const char16_t *stringName) +{ + nsCOMPtr<nsIStringBundleService> stringBundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + + nsCOMPtr<nsIStringBundle> stringBundle; + stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle)); + if (stringBundle) + { + nsString v; + if (NS_SUCCEEDED(stringBundle->GetStringFromName(stringName, getter_Copies(v)))) + return ToNewUTF8String(v); + } + + return strdup("???"); +} + +void +ResetChannelCharset(MimeObject *obj) +{ + if (obj->options && obj->options->stream_closure && + obj->options->default_charset && obj->headers ) + { + mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure); + char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false); + if ( (ct) && (msd) && (msd->channel) ) + { + char *ptr = strstr(ct, "charset="); + if (ptr) + { + // First, setup the channel! + msd->channel->SetContentType(nsDependentCString(ct)); + + // Second, if this is a Save As operation, then we need to convert + // to override the output charset! + mime_stream_data *msd = GetMSD(obj->options); + if ( (msd) && (msd->format_out == nsMimeOutput::nsMimeMessageSaveAs) ) + { + // Extract the charset alone + char *cSet = nullptr; + if (*(ptr+8) == '"') + cSet = strdup(ptr+9); + else + cSet = strdup(ptr+8); + if (cSet) + { + char *ptr2 = cSet; + while ( (*cSet) && (*cSet != ' ') && (*cSet != ';') && + (*cSet != '\r') && (*cSet != '\n') && (*cSet != '"') ) + ptr2++; + + if (*cSet) { + PR_FREEIF(obj->options->default_charset); + obj->options->default_charset = strdup(cSet); + obj->options->override_charset = true; + } + + PR_FREEIF(cSet); + } + } + } + PR_FREEIF(ct); + } + } +} + + //////////////////////////////////////////////////////////// + // Function to get up mail/news fontlang + //////////////////////////////////////////////////////////// + + +nsresult GetMailNewsFont(MimeObject *obj, bool styleFixed, int32_t *fontPixelSize, + int32_t *fontSizePercentage, nsCString& fontLang) +{ + nsresult rv = NS_OK; + + nsIPrefBranch *prefBranch = GetPrefBranch(obj->options); + if (prefBranch) { + MimeInlineText *text = (MimeInlineText *) obj; + nsAutoCString charset; + + // get a charset + if (!text->initializeCharset) + ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj); + + if (!text->charset || !(*text->charset)) + charset.Assign("us-ascii"); + else + charset.Assign(text->charset); + + nsCOMPtr<nsICharsetConverterManager> charSetConverterManager2; + nsCOMPtr<nsIAtom> langGroupAtom; + nsAutoCString prefStr; + + ToLowerCase(charset); + + charSetConverterManager2 = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + if ( NS_FAILED(rv)) + return rv; + + // get a language, e.g. x-western, ja + rv = charSetConverterManager2->GetCharsetLangGroup(charset.get(), getter_AddRefs(langGroupAtom)); + if (NS_FAILED(rv)) + return rv; + rv = langGroupAtom->ToUTF8String(fontLang); + if (NS_FAILED(rv)) + return rv; + + // get a font size from pref + prefStr.Assign(!styleFixed ? "font.size.variable." : "font.size.fixed."); + prefStr.Append(fontLang); + rv = prefBranch->GetIntPref(prefStr.get(), fontPixelSize); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIPrefBranch> prefDefBranch; + nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if(prefSvc) + rv = prefSvc->GetDefaultBranch("", getter_AddRefs(prefDefBranch)); + + if(!prefDefBranch) + return rv; + + // get original font size + int32_t originalSize; + rv = prefDefBranch->GetIntPref(prefStr.get(), &originalSize); + if (NS_FAILED(rv)) + return rv; + + // calculate percentage + *fontSizePercentage = originalSize ? + (int32_t)((float)*fontPixelSize / (float)originalSize * 100) : 0; + + } + + return NS_OK; +} + + + +/** + * This function synchronously converts an HTML document (as string) + * to plaintext (as string) using the Gecko converter. + * + * @param flags see nsIDocumentEncoder.h + */ +nsresult +HTML2Plaintext(const nsString& inString, nsString& outString, + uint32_t flags, uint32_t wrapCol) +{ + nsCOMPtr<nsIParserUtils> utils = + do_GetService(NS_PARSERUTILS_CONTRACTID); + return utils->ConvertToPlainText(inString, flags, wrapCol, outString); +} + + +/** + * This function synchronously sanitizes an HTML document (string->string) + * using the Gecko nsTreeSanitizer. + */ +nsresult +HTMLSanitize(const nsString& inString, nsString& outString) +{ + // If you want to add alternative sanitization, you can insert a conditional + // call to another sanitizer and an early return here. + + uint32_t flags = nsIParserUtils::SanitizerCidEmbedsOnly | + nsIParserUtils::SanitizerDropForms; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + bool dropPresentational = true; + bool dropMedia = false; + prefs->GetBoolPref( + "mailnews.display.html_sanitizer.drop_non_css_presentation", + &dropPresentational); + prefs->GetBoolPref( + "mailnews.display.html_sanitizer.drop_media", + &dropMedia); + if (dropPresentational) + flags |= nsIParserUtils::SanitizerDropNonCSSPresentation; + if (dropMedia) + flags |= nsIParserUtils::SanitizerDropMedia; + + nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID); + return utils->Sanitize(inString, flags, outString); +} diff --git a/mailnews/mime/src/mimemoz2.h b/mailnews/mime/src/mimemoz2.h new file mode 100644 index 000000000..962a42ae0 --- /dev/null +++ b/mailnews/mime/src/mimemoz2.h @@ -0,0 +1,196 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMOZ_H_ +#define _MIMEMOZ_H_ + +#include "nsStreamConverter.h" +#include "nsIMimeEmitter.h" +#include "nsIURI.h" +#include "mozITXTToHTMLConv.h" +#include "nsIMsgSend.h" +#include "modmimee.h" +#include "nsMsgAttachmentData.h" + +// SHERRY - Need to get these out of here eventually + +#ifdef XP_UNIX +#undef Bool +#endif + + + +#include "mimei.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "nsIPrefBranch.h" + +typedef struct _nsMIMESession nsMIMESession; + +/* stream functions */ +typedef unsigned int +(*MKSessionWriteReadyFunc) (nsMIMESession *stream); + +#define MAX_WRITE_READY (((unsigned) (~0) << 1) >> 1) /* must be <= than MAXINT!!!!! */ + +typedef int +(*MKSessionWriteFunc) (nsMIMESession *stream, const char *str, int32_t len); + +typedef void +(*MKSessionCompleteFunc) (nsMIMESession *stream); + +typedef void +(*MKSessionAbortFunc) (nsMIMESession *stream, int status); + +/* streamclass function */ +struct _nsMIMESession { + + const char * name; /* Just for diagnostics */ + + void * window_id; /* used for progress messages, etc. */ + + void * data_object; /* a pointer to whatever + * structure you wish to have + * passed to the routines below + * during writes, etc... + * + * this data object should hold + * the document, document + * structure or a pointer to the + * document. + */ + + MKSessionWriteReadyFunc is_write_ready; /* checks to see if the stream is ready + * for writing. Returns 0 if not ready + * or the number of bytes that it can + * accept for write + */ + MKSessionWriteFunc put_block; /* writes a block of data to the stream */ + MKSessionCompleteFunc complete; /* normal end */ + MKSessionAbortFunc abort; /* abnormal end */ + + bool is_multipart; /* is the stream part of a multipart sequence */ +}; + +/* + * This is for the reworked mime parser. + */ +class mime_stream_data { /* This object is the state we pass around + amongst the various stream functions + used by MIME_MessageConverter(). */ +public: + mime_stream_data(); + + char *url_name; + char *orig_url_name; /* original url name */ + nsCOMPtr<nsIChannel> channel; + nsMimeOutputType format_out; + void *pluginObj2; /* The new XP-COM stream converter object */ + nsMIMESession *istream; /* Holdover - new stream we're writing out image data-if any. */ + MimeObject *obj; /* The root parser object */ + MimeDisplayOptions *options; /* Data for communicating with libmime.a */ + MimeHeaders *headers; /* Copy of outer most mime header */ + + nsIMimeEmitter *output_emitter; /* Output emitter engine for libmime */ + bool firstCheck; /* Is this the first look at the stream data */ +}; + +// +// This object is the state we use for loading drafts and templates... +// +class mime_draft_data +{ +public: + mime_draft_data(); + char *url_name; // original url name */ + nsMimeOutputType format_out; // intended output format; should be FO_OPEN_DRAFT */ + nsMIMESession *stream; // not used for now + MimeObject *obj; // The root + MimeDisplayOptions *options; // data for communicating with libmime + MimeHeaders *headers; // Copy of outer most mime header + nsTArray<nsMsgAttachedFile*> attachments;// attachments + nsMsgAttachedFile *messageBody; // message body + nsMsgAttachedFile *curAttachment; // temp + + nsCOMPtr <nsIFile> tmpFile; + nsCOMPtr <nsIOutputStream> tmpFileStream; // output file handle + + MimeDecoderData *decoder_data; + char *mailcharset; // get it from CHARSET of Content-Type + bool forwardInline; + bool forwardInlineFilter; + bool overrideComposeFormat; // Override compose format (for forward inline). + nsString forwardToAddress; + nsCOMPtr<nsIMsgIdentity> identity; + char *originalMsgURI; // the original URI of the message we are currently processing + nsCOMPtr<nsIMsgDBHdr> origMsgHdr; +}; + +//////////////////////////////////////////////////////////////// +// Bridge routines for legacy mime code +//////////////////////////////////////////////////////////////// + +// Create bridge stream for libmime +extern "C" +void *mime_bridge_create_display_stream(nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out, + uint32_t whattodo, + nsIChannel *aChannel); + +// To get the mime emitter... +extern "C" nsIMimeEmitter *GetMimeEmitter(MimeDisplayOptions *opt); + +// To support 2 types of emitters...we need these routines :-( +extern "C" nsresult mimeSetNewURL(nsMIMESession *stream, char *url); +extern "C" nsresult mimeEmitterAddAttachmentField(MimeDisplayOptions *opt, const char *field, const char *value); +extern "C" nsresult mimeEmitterAddHeaderField(MimeDisplayOptions *opt, const char *field, const char *value); +extern "C" nsresult mimeEmitterAddAllHeaders(MimeDisplayOptions *opt, const char *allheaders, const int32_t allheadersize); +extern "C" nsresult mimeEmitterStartAttachment(MimeDisplayOptions *opt, const char *name, const char *contentType, const char *url, + bool aIsExternalAttachment); +extern "C" nsresult mimeEmitterEndAttachment(MimeDisplayOptions *opt); +extern "C" nsresult mimeEmitterEndAllAttachments(MimeDisplayOptions *opt); +extern "C" nsresult mimeEmitterStartBody(MimeDisplayOptions *opt, bool bodyOnly, const char *msgID, const char *outCharset); +extern "C" nsresult mimeEmitterEndBody(MimeDisplayOptions *opt); +extern "C" nsresult mimeEmitterEndHeader(MimeDisplayOptions *opt, MimeObject *obj); +extern "C" nsresult mimeEmitterStartHeader(MimeDisplayOptions *opt, bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset); +extern "C" nsresult mimeEmitterUpdateCharacterSet(MimeDisplayOptions *opt, const char *aCharset); + +extern "C" nsresult MimeGetAttachmentList(MimeObject *tobj, const char *aMessageURL, nsMsgAttachmentData **data); + +/* To Get the connnection to prefs service manager */ +extern "C" nsIPrefBranch *GetPrefBranch(MimeDisplayOptions *opt); + +// Get the text converter... +mozITXTToHTMLConv *GetTextConverter(MimeDisplayOptions *opt); + +nsresult +HTML2Plaintext(const nsString& inString, nsString& outString, + uint32_t flags, uint32_t wrapCol); +nsresult +HTMLSanitize(const nsString& inString, nsString& outString); + +extern "C" char *MimeGetStringByID(int32_t stringID); +extern "C" char *MimeGetStringByName(const char16_t *stringName); + +// Utility to create a nsIURI object... +extern "C" nsresult nsMimeNewURI(nsIURI** aInstancePtrResult, const char *aSpec, nsIURI *aBase); + +extern "C" nsresult SetMailCharacterSetToMsgWindow(MimeObject *obj, const char *aCharacterSet); + +extern "C" nsresult GetMailNewsFont(MimeObject *obj, bool styleFixed, int32_t *fontPixelSize, int32_t *fontSizePercentage, nsCString& fontLang); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _MIMEMOZ_H_ */ + diff --git a/mailnews/mime/src/mimempar.cpp b/mailnews/mime/src/mimempar.cpp new file mode 100644 index 000000000..efcd06445 --- /dev/null +++ b/mailnews/mime/src/mimempar.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimempar.h" +#include "prlog.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartParallel, MimeMultipartParallelClass, + mimeMultipartParallelClass, &MIME_SUPERCLASS); + +static int +MimeMultipartParallelClassInitialize(MimeMultipartParallelClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); +#endif + return 0; +} diff --git a/mailnews/mime/src/mimempar.h b/mailnews/mime/src/mimempar.h new file mode 100644 index 000000000..1ac39f1fc --- /dev/null +++ b/mailnews/mime/src/mimempar.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMPAR_H_ +#define _MIMEMPAR_H_ + +#include "mimemult.h" + +/* The MimeMultipartParallel class implements the multipart/parallel MIME + container, which is currently no different from multipart/mixed, since + it's not clear that there's anything useful it could do differently. + */ + +typedef struct MimeMultipartParallelClass MimeMultipartParallelClass; +typedef struct MimeMultipartParallel MimeMultipartParallel; + +struct MimeMultipartParallelClass { + MimeMultipartClass multipart; +}; + +extern MimeMultipartParallelClass mimeMultipartParallelClass; + +struct MimeMultipartParallel { + MimeMultipart multipart; +}; + +#define MimeMultipartParallelClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMPAR_H_ */ diff --git a/mailnews/mime/src/mimemrel.cpp b/mailnews/mime/src/mimemrel.cpp new file mode 100644 index 000000000..bbcb990b5 --- /dev/null +++ b/mailnews/mime/src/mimemrel.cpp @@ -0,0 +1,1199 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +/* Thoughts on how to implement this: + + = if the type of this multipart/related is not text/html, then treat + it the same as multipart/mixed. + + = For each part in this multipart/related + = if this part is not the "top" part + = then save this part to a tmp file or a memory object, + kind-of like what we do for multipart/alternative sub-parts. + If this is an object we're blocked on (see below) send its data along. + = else + = emit this part (remember, it's of type text/html) + = at some point, layout may load a URL for <IMG SRC="cid:xxxx">. + we intercept that. + = if one of our cached parts has that cid, return the data for it. + = else, "block", the same way the image library blocks layout when it + doesn't yet have the size of the image. + = at some point, layout may load a URL for <IMG SRC="relative/yyy">. + we need to intercept that too. + = expand the URL, and compare it to our cached objects. + if it matches, return it. + = else block on it. + + = once we get to the end, if we have any sub-part references that we're + still blocked on, map over them: + = if they're cid: references, close them ("broken image" results.) + = if they're URLs, then load them in the normal way. + + -------------------------------------------------- + + Ok, that's fairly complicated. How about an approach where we go through + all the parts first, and don't emit until the end? + + = if the type of this multipart/related is not text/html, then treat + it the same as multipart/mixed. + + = For each part in this multipart/related + = save this part to a tmp file or a memory object, + like what we do for multipart/alternative sub-parts. + + = Emit the "top" part (the text/html one) + = intercept all calls to NET_GetURL, to allow us to rewrite the URL. + (hook into netlib, or only into imglib's calls to GetURL?) + (make sure we're behaving in a context-local way.) + + = when a URL is loaded, look through our cached parts for a match. + = if we find one, map that URL to a "cid:" URL + = else, let it load normally + + = at some point, layout may load a URL for <IMG SRC="cid:xxxx">. + it will do this either because that's what was in the HTML, or because + that's how we "rewrote" the URLs when we intercepted NET_GetURL. + + = if one of our cached parts has the requested cid, return the data + for it. + = else, generate a "broken image" + + = free all the cached data + + -------------------------------------------------- + + How hard would be an approach where we rewrite the HTML? + (Looks like it's not much easier, and might be more error-prone.) + + = if the type of this multipart/related is not text/html, then treat + it the same as multipart/mixed. + + = For each part in this multipart/related + = save this part to a tmp file or a memory object, + like what we do for multipart/alternative sub-parts. + + = Parse the "top" part, and emit slightly different HTML: + = for each <IMG SRC>, <IMG LOWSRC>, <A HREF>? Any others? + = look through our cached parts for a matching URL + = if we find one, map that URL to a "cid:" URL + = else, let it load normally + + = at some point, layout may load a URL for <IMG SRC="cid:xxxx">. + = if one of our cached parts has the requested cid, return the data + for it. + = else, generate a "broken image" + + = free all the cached data + */ +#include "nsCOMPtr.h" +#include "mimemrel.h" +#include "mimemapl.h" +#include "prmem.h" +#include "prprf.h" +#include "prlog.h" +#include "plstr.h" +#include "mimemoz2.h" +#include "nsStringGlue.h" +#include "nsIURL.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include "mimebuf.h" +#include "nsMsgUtils.h" +#include <ctype.h> + +// +// External Defines... +// + +extern nsresult +nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile); + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartRelated, MimeMultipartRelatedClass, + mimeMultipartRelatedClass, &MIME_SUPERCLASS); + + +class MimeHashValue +{ +public: + MimeHashValue(MimeObject *obj, char *url) { + m_obj = obj; + m_url = strdup(url); + } + virtual ~MimeHashValue() { + if (m_url) + PR_Free((void *)m_url); + } + + MimeObject *m_obj; + char *m_url; +}; + +static int +MimeMultipartRelated_initialize(MimeObject* obj) +{ + MimeMultipartRelated* relobj = (MimeMultipartRelated*) obj; + relobj->base_url = MimeHeaders_get(obj->headers, HEADER_CONTENT_BASE, + false, false); + /* rhp: need this for supporting Content-Location */ + if (!relobj->base_url) + { + relobj->base_url = MimeHeaders_get(obj->headers, HEADER_CONTENT_LOCATION, + false, false); + } + /* rhp: need this for supporting Content-Location */ + + /* I used to have code here to test if the type was text/html. Then I + added multipart/alternative as being OK, too. Then I found that the + VCard spec seems to talk about having the first part of a + multipart/related be an application/directory. At that point, I decided + to punt. We handle anything as the first part, and stomp on the HTML it + generates to adjust tags to point into the other parts. This probably + works out to something reasonable in most cases. */ + + relobj->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, + (PLHashAllocOps *)NULL, NULL); + + if (!relobj->hash) return MIME_OUT_OF_MEMORY; + + relobj->input_file_stream = nullptr; + relobj->output_file_stream = nullptr; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + +static int +mime_multipart_related_nukehash(PLHashEntry *table, + int indx, void *arg) +{ + if (table->key) + PR_Free((char*) table->key); + + if (table->value) + delete (MimeHashValue *)table->value; + + return HT_ENUMERATE_NEXT; /* XP_Maphash will continue traversing the hash */ +} + +static void +MimeMultipartRelated_finalize (MimeObject *obj) +{ + MimeMultipartRelated* relobj = (MimeMultipartRelated*) obj; + PR_FREEIF(relobj->base_url); + PR_FREEIF(relobj->curtag); + if (relobj->buffered_hdrs) { + PR_FREEIF(relobj->buffered_hdrs->all_headers); + PR_FREEIF(relobj->buffered_hdrs->heads); + PR_FREEIF(relobj->buffered_hdrs); + } + PR_FREEIF(relobj->head_buffer); + relobj->head_buffer_fp = 0; + relobj->head_buffer_size = 0; + if (relobj->hash) { + PL_HashTableEnumerateEntries(relobj->hash, mime_multipart_related_nukehash, NULL); + PL_HashTableDestroy(relobj->hash); + relobj->hash = NULL; + } + + if (relobj->input_file_stream) + { + relobj->input_file_stream->Close(); + relobj->input_file_stream = nullptr; + } + + if (relobj->output_file_stream) + { + relobj->output_file_stream->Close(); + relobj->output_file_stream = nullptr; + } + + if (relobj->file_buffer) + { + relobj->file_buffer->Remove(false); + relobj->file_buffer = nullptr; + } + + if (relobj->headobj) { + // In some error conditions when MimeMultipartRelated_parse_eof() isn't run + // (for example, no temp disk space available to extract message parts), + // the head object is also referenced as a child. + // If we free it, we remove the child reference first ... or crash later :-( + MimeContainer *cont = (MimeContainer *)relobj; + for (int i = 0; i < cont->nchildren; i++) { + if (cont->children[i] == relobj->headobj) { + // Shift remaining children down. + for (int j = i+1; j < cont->nchildren; j++) { + cont->children[j-1] = cont->children[j]; + } + cont->children[--cont->nchildren] = nullptr; + break; + } + } + + mime_free(relobj->headobj); + relobj->headobj = nullptr; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + +#define ISHEX(c) ( ((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F') ) +#define NONHEX(c) (!ISHEX(c)) + +extern "C" char * +escape_unescaped_percents(const char *incomingURL) +{ + const char *inC; + char *outC; + char *result = (char *) PR_Malloc(strlen(incomingURL)*3+1); + + if (result) + { + for(inC = incomingURL, outC = result; *inC != '\0'; inC++) + { + if (*inC == '%') + { + /* Check if either of the next two characters are non-hex. */ + if ( !*(inC+1) || NONHEX(*(inC+1)) || !*(inC+2) || NONHEX(*(inC+2)) ) + { + /* Hex characters don't follow, escape the + percent char */ + *outC++ = '%'; *outC++ = '2'; *outC++ = '5'; + } + else + { + /* Hex characters follow, so assume the percent + is escaping something else */ + *outC++ = *inC; + } + } + else + *outC++ = *inC; + } + *outC = '\0'; + } + + return result; +} + +/* This routine is only necessary because the mailbox URL fed to us + by the winfe can contain spaces and '>'s in it. It's a hack. */ +static char * +escape_for_mrel_subst(char *inURL) +{ + char *output, *inC, *outC, *temp; + + int size = strlen(inURL) + 1; + + for(inC = inURL; *inC; inC++) + if ((*inC == ' ') || (*inC == '>')) + size += 2; /* space -> '%20', '>' -> '%3E', etc. */ + + output = (char *)PR_MALLOC(size); + if (output) + { + /* Walk through the source string, copying all chars + except for spaces, which get escaped. */ + inC = inURL; + outC = output; + while(*inC) + { + if (*inC == ' ') + { + *outC++ = '%'; *outC++ = '2'; *outC++ = '0'; + } + else if (*inC == '>') + { + *outC++ = '%'; *outC++ = '3'; *outC++ = 'E'; + } + else + *outC++ = *inC; + + inC++; + } + *outC = '\0'; + + temp = escape_unescaped_percents(output); + if (temp) + { + PR_FREEIF(output); + output = temp; + } + } + return output; +} + +static bool +MimeStartParamExists(MimeObject *obj, MimeObject* child) +{ + char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false); + char *st = (ct + ? MimeHeaders_get_parameter(ct, HEADER_PARM_START, NULL, NULL) + : 0); + + PR_FREEIF(ct); + if (!st) + return false; + + PR_FREEIF(st); + return true; +} + +static bool +MimeThisIsStartPart(MimeObject *obj, MimeObject* child) +{ + bool rval = false; + char *ct, *st, *cst; + + ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false); + st = (ct + ? MimeHeaders_get_parameter(ct, HEADER_PARM_START, NULL, NULL) + : 0); + + PR_FREEIF(ct); + if (!st) + return false; + + cst = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, false, false); + if (!cst) + rval = false; + else + { + char *tmp = cst; + if (*tmp == '<') + { + int length; + tmp++; + length = strlen(tmp); + if (length > 0 && tmp[length - 1] == '>') + { + tmp[length - 1] = '\0'; + } + } + + rval = (!strcmp(st, tmp)); + } + + PR_FREEIF(st); + PR_FREEIF(cst); + return rval; +} +/* rhp - gotta support the "start" parameter */ + +char * +MakeAbsoluteURL(char *base_url, char *relative_url) +{ + char *retString = nullptr; + nsIURI *base = nullptr; + + // if either is NULL, just return the relative if safe... + if (!base_url || !relative_url) + { + if (!relative_url) + return nullptr; + + NS_MsgSACopy(&retString, relative_url); + return retString; + } + + nsresult err = nsMimeNewURI(&base, base_url, nullptr); + if (NS_FAILED(err)) + return nullptr; + + nsAutoCString spec; + + nsIURI *url = nullptr; + err = nsMimeNewURI(&url, relative_url, base); + if (NS_FAILED(err)) + goto done; + + err = url->GetSpec(spec); + if (NS_FAILED(err)) + { + retString = nullptr; + goto done; + } + retString = ToNewCString(spec); + +done: + NS_IF_RELEASE(url); + NS_IF_RELEASE(base); + return retString; +} + +static bool +MimeMultipartRelated_output_child_p(MimeObject *obj, MimeObject* child) +{ + MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj; + + /* rhp - Changed from "if (relobj->head_loaded)" alone to support the + start parameter + */ + if ( + (relobj->head_loaded) || + (MimeStartParamExists(obj, child) && !MimeThisIsStartPart(obj, child)) + ) + { + /* This is a child part. Just remember the mapping between the URL + it represents and the part-URL to get it back. */ + + char* location = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, + false, false); + if (!location) { + char* tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, + false, false); + if (tmp) { + char* tmp2 = tmp; + if (*tmp2 == '<') { + int length; + tmp2++; + length = strlen(tmp2); + if (length > 0 && tmp2[length - 1] == '>') { + tmp2[length - 1] = '\0'; + } + } + location = PR_smprintf("cid:%s", tmp2); + PR_Free(tmp); + } + } + + if (location) { + char *absolute; + char *base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE, + false, false); + absolute = MakeAbsoluteURL(base_url ? base_url : relobj->base_url, location); + + PR_FREEIF(base_url); + PR_Free(location); + if (absolute) { + nsAutoCString partnum; + nsAutoCString imappartnum; + partnum.Adopt(mime_part_address(child)); + if (!partnum.IsEmpty()) { + if (obj->options->missing_parts) + { + char * imappart = mime_imap_part_address(child); + if (imappart) + imappartnum.Adopt(imappart); + } + + /* + AppleDouble part need special care: we need to output only the data fork part of it. + The problem at this point is that we haven't yet decoded the children of the AppleDouble + part therfore we will have to hope the datafork is the second one! + */ + if (mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + partnum.Append(".2"); + + char* part; + if (!imappartnum.IsEmpty()) + part = mime_set_url_imap_part(obj->options->url, imappartnum.get(), partnum.get()); + else + { + char *no_part_url = nullptr; + if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) + no_part_url = mime_get_base_url(obj->options->url); + if (no_part_url) + { + part = mime_set_url_part(no_part_url, partnum.get(), false); + PR_Free(no_part_url); + } + else + part = mime_set_url_part(obj->options->url, partnum.get(), false); + } + if (part) + { + char *name = MimeHeaders_get_name(child->headers, child->options); + // let's stick the filename in the part so save as will work. + if (name) + { + char *savePart = part; + part = PR_smprintf("%s&filename=%s", savePart, name); + PR_Free(savePart); + PR_Free(name); + } + char *temp = part; + /* If there's a space in the url, escape the url. + (This happens primarily on Windows and Unix.) */ + if (PL_strchr(part, ' ') || PL_strchr(part, '>') || PL_strchr(part, '%')) + temp = escape_for_mrel_subst(part); + MimeHashValue * value = new MimeHashValue(child, temp); + PL_HashTableAdd(relobj->hash, absolute, value); + + /* rhp - If this part ALSO has a Content-ID we need to put that into + the hash table and this is what this code does + */ + { + char *tloc; + char *tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, false, false); + if (tmp) + { + char* tmp2 = tmp; + if (*tmp2 == '<') + { + int length; + tmp2++; + length = strlen(tmp2); + if (length > 0 && tmp2[length - 1] == '>') + { + tmp2[length - 1] = '\0'; + } + } + + tloc = PR_smprintf("cid:%s", tmp2); + PR_Free(tmp); + if (tloc) + { + MimeHashValue *value; + value = (MimeHashValue*)PL_HashTableLookup(relobj->hash, tloc); + + if (!value) + { + value = new MimeHashValue(child, temp); + PL_HashTableAdd(relobj->hash, tloc, value); + } + else + PR_smprintf_free(tloc); + } + } + } + /* rhp - End of putting more stuff into the hash table */ + + /* it's possible that temp pointer is the same than the part pointer, + therefore be carefull to not freeing twice the same pointer */ + if (temp && temp != part) + PR_Free(temp); + PR_Free(part); + } + } + } + } + } else { + /* Ah-hah! We're the head object. */ + char* base_url; + relobj->head_loaded = true; + relobj->headobj = child; + relobj->buffered_hdrs = MimeHeaders_copy(child->headers); + base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE, + false, false); + /* rhp: need this for supporting Content-Location */ + if (!base_url) + { + base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, false, false); + } + /* rhp: need this for supporting Content-Location */ + + if (base_url) { + /* If the head object has a base_url associated with it, use + that instead of any base_url that may have been associated + with the multipart/related. */ + PR_FREEIF(relobj->base_url); + relobj->base_url = base_url; + } + } + if (obj->options && !obj->options->write_html_p +#ifdef MIME_DRAFTS + && !obj->options->decompose_file_p +#endif /* MIME_DRAFTS */ + ) + { + return true; + } + + return false; /* Don't actually parse this child; we'll handle + all that at eof time. */ +} + +static int +MimeMultipartRelated_parse_child_line (MimeObject *obj, + const char *line, int32_t length, + bool first_line_p) +{ + MimeContainer *cont = (MimeContainer *) obj; + MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj; + MimeObject *kid; + + if (obj->options && !obj->options->write_html_p +#ifdef MIME_DRAFTS + && !obj->options->decompose_file_p +#endif /* MIME_DRAFTS */ + ) + { + /* Oh, just go do the normal thing... */ + return ((MimeMultipartClass*)&MIME_SUPERCLASS)-> + parse_child_line(obj, line, length, first_line_p); + } + + /* Throw it away if this isn't the head object. (Someday, maybe we'll + cache it instead.) */ + PR_ASSERT(cont->nchildren > 0); + if (cont->nchildren <= 0) + return -1; + kid = cont->children[cont->nchildren-1]; + PR_ASSERT(kid); + if (!kid) return -1; + if (kid != relobj->headobj) return 0; + + /* Buffer this up (###tw much code duplication from mimemalt.c) */ + /* If we don't yet have a buffer (either memory or file) try and make a + memory buffer. */ + if (!relobj->head_buffer && !relobj->file_buffer) { + int target_size = 1024 * 50; /* try for 50k */ + while (target_size > 0) { + relobj->head_buffer = (char *) PR_MALLOC(target_size); + if (relobj->head_buffer) break; /* got it! */ + target_size -= (1024 * 5); /* decrease it and try again */ + } + + if (relobj->head_buffer) { + relobj->head_buffer_size = target_size; + } else { + relobj->head_buffer_size = 0; + } + + relobj->head_buffer_fp = 0; + } + + nsresult rv; + /* Ok, if at this point we still don't have either kind of buffer, try and + make a file buffer. */ + if (!relobj->head_buffer && !relobj->file_buffer) + { + nsCOMPtr <nsIFile> file; + rv = nsMsgCreateTempFile("nsma", getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, -1); + relobj->file_buffer = do_QueryInterface(file); + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(relobj->output_file_stream), relobj->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600); + NS_ENSURE_SUCCESS(rv, -1); + } + + PR_ASSERT(relobj->head_buffer || relobj->output_file_stream); + + + /* If this line will fit in the memory buffer, put it there. + */ + if (relobj->head_buffer && + relobj->head_buffer_fp + length < relobj->head_buffer_size) { + memcpy(relobj->head_buffer + relobj->head_buffer_fp, line, length); + relobj->head_buffer_fp += length; + } else { + /* Otherwise it won't fit; write it to the file instead. */ + + /* If the file isn't open yet, open it, and dump the memory buffer + to it. */ + if (!relobj->output_file_stream) + { + if (!relobj->file_buffer) + { + nsCOMPtr <nsIFile> file; + rv = nsMsgCreateTempFile("nsma", getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, -1); + relobj->file_buffer = do_QueryInterface(file); + } + + nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(relobj->output_file_stream), relobj->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600); + NS_ENSURE_SUCCESS(rv, -1); + + if (relobj->head_buffer && relobj->head_buffer_fp) + { + uint32_t bytesWritten; + rv = relobj->output_file_stream->Write(relobj->head_buffer, relobj->head_buffer_fp, &bytesWritten); + if (NS_FAILED(rv) || (bytesWritten < relobj->head_buffer_fp)) + return MIME_UNABLE_TO_OPEN_TMP_FILE; + } + + PR_FREEIF(relobj->head_buffer); + relobj->head_buffer_fp = 0; + relobj->head_buffer_size = 0; + } + + /* Dump this line to the file. */ + uint32_t bytesWritten; + rv = relobj->output_file_stream->Write(line, length, &bytesWritten); + if ((int32_t) bytesWritten < length || NS_FAILED(rv)) + return MIME_UNABLE_TO_OPEN_TMP_FILE; + } + + return 0; +} + + + + +static int +real_write(MimeMultipartRelated* relobj, const char* buf, int32_t size) +{ + MimeObject* obj = (MimeObject*) relobj; + void* closure = relobj->real_output_closure; + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_output_fn ) + { + + // the buf here has already been decoded, but we want to use general output + // functions here that permit decoded or encoded input, using the closure + // to tell the difference. We'll temporarily disable the closure's decoder, + // then restore it when we are done. Not sure if we shouldn't just turn it off + // permanently though. + + mime_draft_data *mdd = (mime_draft_data *) obj->options->stream_closure; + MimeDecoderData* old_decoder_data = mdd->decoder_data; + mdd->decoder_data = nullptr; + int status = obj->options->decompose_file_output_fn + (buf, size, (void *)mdd); + mdd->decoder_data = old_decoder_data; + return status; + } + else +#endif /* MIME_DRAFTS */ + { + if (!closure) { + MimeObject* lobj = (MimeObject*) relobj; + closure = lobj->options->stream_closure; + } + return relobj->real_output_fn(buf, size, closure); + } +} + + +static int +push_tag(MimeMultipartRelated* relobj, const char* buf, int32_t size) +{ + if (size + relobj->curtag_length > relobj->curtag_max) { + relobj->curtag_max += 2 * size; + if (relobj->curtag_max < 1024) + relobj->curtag_max = 1024; + + char* newBuf = (char*) PR_Realloc(relobj->curtag, relobj->curtag_max); + NS_ENSURE_TRUE(newBuf, MIME_OUT_OF_MEMORY); + relobj->curtag = newBuf; + } + memcpy(relobj->curtag + relobj->curtag_length, buf, size); + relobj->curtag_length += size; + return 0; +} + +static bool accept_related_part(MimeMultipartRelated* relobj, MimeObject* part_obj) +{ + if (!relobj || !part_obj) + return false; + + /* before accepting it as a valid related part, make sure we + are able to display it inline as an embedded object. Else just ignore + it, that will prevent any bad surprise... */ + MimeObjectClass *clazz = mime_find_class (part_obj->content_type, part_obj->headers, part_obj->options, false); + if (clazz ? clazz->displayable_inline_p(clazz, part_obj->headers) : false) + return true; + + /* ...but we always accept it if it's referenced by an anchor */ + return (relobj->curtag && relobj->curtag_length >= 3 && + (relobj->curtag[1] == 'A' || relobj->curtag[1] == 'a') && IS_SPACE(relobj->curtag[2])); +} + +static int +flush_tag(MimeMultipartRelated* relobj) +{ + int length = relobj->curtag_length; + char* buf; + int status; + + if (relobj->curtag == NULL || length == 0) return 0; + + status = push_tag(relobj, "", 1); /* Push on a trailing NULL. */ + if (status < 0) return status; + buf = relobj->curtag; + PR_ASSERT(*buf == '<' && buf[length - 1] == '>'); + while (*buf) { + char c; + char* absolute; + char* part_url; + char* ptr = buf; + char *ptr2; + char quoteDelimiter = '\0'; + while (*ptr && *ptr != '=') ptr++; + if (*ptr == '=') { + /* Ignore = and leading space. */ + /* Safe, because there's a '>' at the end! */ + do {ptr++;} while (IS_SPACE(*ptr)); + if (*ptr == '"' || *ptr == '\'') { + quoteDelimiter = *ptr; + /* Take up the quote and leading space here as well. */ + /* Safe because there's a '>' at the end */ + do {ptr++;} while (IS_SPACE(*ptr)); + } + } + status = real_write(relobj, buf, ptr - buf); + if (status < 0) return status; + buf = ptr; + if (!*buf) break; + if (quoteDelimiter) + { + ptr = PL_strnchr(buf, quoteDelimiter, length - (buf - relobj->curtag)); + } else { + for (ptr = buf; *ptr ; ptr++) { + if (*ptr == '>' || IS_SPACE(*ptr)) break; + } + PR_ASSERT(*ptr); + } + if (!ptr || !*ptr) break; + + while(buf < ptr) + { + /* ### mwelch For each word in the value string, see if + the word is a cid: URL. If so, attempt to + substitute the appropriate mailbox part URL in + its place. */ + ptr2=buf; /* walk from the left end rightward */ + while((ptr2<ptr) && (!IS_SPACE(*ptr2))) + ptr2++; + /* Compare the beginning of the word with "cid:". Yuck. */ + if (((ptr2 - buf) > 4) && + ((buf[0]=='c' || buf[0]=='C') && + (buf[1]=='i' || buf[1]=='I') && + (buf[2]=='d' || buf[2]=='D') && + buf[3]==':')) + { + // Make sure it's lowercase, otherwise it won't be found in the hash table + buf[0] = 'c'; buf[1] = 'i'; buf[2] = 'd'; + + /* Null terminate the word so we can... */ + c = *ptr2; + *ptr2 = '\0'; + + /* Construct a URL out of the word. */ + absolute = MakeAbsoluteURL(relobj->base_url, buf); + + /* See if we have a mailbox part URL + corresponding to this cid. */ + part_url = nullptr; + MimeHashValue * value = nullptr; + if (absolute) + { + value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, buf); + part_url = value ? value->m_url : nullptr; + PR_FREEIF(absolute); + } + + /*If we found a mailbox part URL, write that out instead.*/ + if (part_url && accept_related_part(relobj, value->m_obj)) + { + status = real_write(relobj, part_url, strlen(part_url)); + if (status < 0) return status; + buf = ptr2; /* skip over the cid: URL we substituted */ + + /* don't show that object as attachment */ + if (value->m_obj) + value->m_obj->dontShowAsAttachment = true; + } + + /* Restore the character that we nulled. */ + *ptr2 = c; + } + /* rhp - if we get here, we should still check against the hash table! */ + else + { + char holder = *ptr2; + char *realout; + + *ptr2 = '\0'; + + /* Construct a URL out of the word. */ + absolute = MakeAbsoluteURL(relobj->base_url, buf); + + /* See if we have a mailbox part URL + corresponding to this cid. */ + MimeHashValue * value; + if (absolute) + value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, absolute); + else + value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, buf); + realout = value ? value->m_url : nullptr; + + *ptr2 = holder; + PR_FREEIF(absolute); + + if (realout && accept_related_part(relobj, value->m_obj)) + { + status = real_write(relobj, realout, strlen(realout)); + if (status < 0) return status; + buf = ptr2; /* skip over the cid: URL we substituted */ + + /* don't show that object as attachment */ + if (value->m_obj) + value->m_obj->dontShowAsAttachment = true; + } + } + /* rhp - if we get here, we should still check against the hash table! */ + + /* Advance to the beginning of the next word, or to + the end of the value string. */ + while((ptr2<ptr) && (IS_SPACE(*ptr2))) + ptr2++; + + /* Write whatever original text remains after + cid: URL substitution. */ + status = real_write(relobj, buf, ptr2-buf); + if (status < 0) return status; + buf = ptr2; + } + } + if (buf && *buf) { + status = real_write(relobj, buf, strlen(buf)); + if (status < 0) return status; + } + relobj->curtag_length = 0; + return 0; +} + + + +static int +mime_multipart_related_output_fn(const char* buf, int32_t size, void *stream_closure) +{ + MimeMultipartRelated *relobj = (MimeMultipartRelated *) stream_closure; + char* ptr; + int32_t delta; + int status; + while (size > 0) { + if (relobj->curtag_length > 0) { + ptr = PL_strnchr(buf, '>', size); + if (!ptr) { + return push_tag(relobj, buf, size); + } + delta = ptr - buf + 1; + status = push_tag(relobj, buf, delta); + if (status < 0) return status; + status = flush_tag(relobj); + if (status < 0) return status; + buf += delta; + size -= delta; + } + ptr = PL_strnchr(buf, '<', size); + if (ptr && ptr - buf >= size) ptr = 0; + if (!ptr) { + return real_write(relobj, buf, size); + } + delta = ptr - buf; + status = real_write(relobj, buf, delta); + if (status < 0) return status; + buf += delta; + size -= delta; + PR_ASSERT(relobj->curtag_length == 0); + status = push_tag(relobj, buf, 1); + if (status < 0) return status; + PR_ASSERT(relobj->curtag_length == 1); + buf++; + size--; + } + return 0; +} + + +static int +MimeMultipartRelated_parse_eof (MimeObject *obj, bool abort_p) +{ + /* OK, all the necessary data has been collected. We now have to spew out + the HTML. We let it go through all the normal mechanisms (which + includes content-encoding handling), and intercept the output data to do + translation of the tags. Whee. */ + MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj; + MimeContainer *cont = (MimeContainer *)obj; + int status = 0; + MimeObject *body; + char* ct; + const char* dct; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) goto FAIL; + + if (!relobj->headobj) return 0; + + ct = (relobj->buffered_hdrs + ? MimeHeaders_get (relobj->buffered_hdrs, HEADER_CONTENT_TYPE, + true, false) + : 0); + dct = (((MimeMultipartClass *) obj->clazz)->default_part_type); + + relobj->real_output_fn = obj->options->output_fn; + relobj->real_output_closure = obj->options->output_closure; + + obj->options->output_fn = mime_multipart_related_output_fn; + obj->options->output_closure = obj; + + body = mime_create(((ct && *ct) ? ct : (dct ? dct : TEXT_HTML)), + relobj->buffered_hdrs, obj->options); + + PR_FREEIF(ct); + if (!body) { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + // replace the existing head object with the new object + for (int iChild = 0; iChild < cont->nchildren; iChild++) { + if (cont->children[iChild] == relobj->headobj) { + // cleanup of the headobj is performed explicitly in our finalizer now + // that it does not get cleaned up as a child. + cont->children[iChild] = body; + body->parent = obj; + body->options = obj->options; + } + } + + if (!body->parent) { + NS_WARNING("unexpected mime multipart related structure"); + goto FAIL; + } + + body->dontShowAsAttachment = body->clazz->displayable_inline_p(body->clazz, body->headers); + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_init_fn && + (relobj->file_buffer || relobj->head_buffer)) + { + status = obj->options->decompose_file_init_fn ( obj->options->stream_closure, + relobj->buffered_hdrs ); + if (status < 0) return status; + } +#endif /* MIME_DRAFTS */ + + /* if the emitter wants to know about nested bodies, then it needs + to know that we jumped back to this body part. */ + if (obj->options->notify_nested_bodies) + { + char *part_path = mime_part_address(body); + if (part_path) + { + mimeEmitterAddHeaderField(obj->options, + "x-jsemitter-part-path", + part_path); + PR_Free(part_path); + } + } + + /* Now that we've added this new object to our list of children, + start its parser going. */ + status = body->clazz->parse_begin(body); + if (status < 0) goto FAIL; + + if (relobj->head_buffer) + { + /* Read it out of memory. */ + PR_ASSERT(!relobj->file_buffer && !relobj->input_file_stream); + + status = body->clazz->parse_buffer(relobj->head_buffer, + relobj->head_buffer_fp, + body); + } + else if (relobj->file_buffer) + { + /* Read it off disk. */ + char *buf; + + PR_ASSERT(relobj->head_buffer_size == 0 && + relobj->head_buffer_fp == 0); + PR_ASSERT(relobj->file_buffer); + if (!relobj->file_buffer) + { + status = -1; + goto FAIL; + } + + buf = (char *) PR_MALLOC(FILE_IO_BUFFER_SIZE); + if (!buf) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + + // First, close the output file to open the input file! + if (relobj->output_file_stream) + relobj->output_file_stream->Close(); + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(relobj->input_file_stream), relobj->file_buffer); + if (NS_FAILED(rv)) + { + PR_Free(buf); + status = MIME_UNABLE_TO_OPEN_TMP_FILE; + goto FAIL; + } + + while(1) + { + uint32_t bytesRead = 0; + rv = relobj->input_file_stream->Read(buf, FILE_IO_BUFFER_SIZE - 1, &bytesRead); + if (NS_FAILED(rv) || !bytesRead) + { + status = NS_FAILED(rv) ? -1 : 0; + break; + } + else + { + /* It would be really nice to be able to yield here, and let + some user events and other input sources get processed. + Oh well. */ + + status = body->clazz->parse_buffer(buf, bytesRead, body); + if (status < 0) break; + } + } + PR_Free(buf); + } + + if (status < 0) goto FAIL; + + /* Done parsing. */ + status = body->clazz->parse_eof(body, false); + if (status < 0) goto FAIL; + status = body->clazz->parse_end(body, false); + if (status < 0) goto FAIL; + +FAIL: + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_close_fn && + (relobj->file_buffer || relobj->head_buffer)) { + status = obj->options->decompose_file_close_fn ( obj->options->stream_closure ); + if (status < 0) return status; + } +#endif /* MIME_DRAFTS */ + + obj->options->output_fn = relobj->real_output_fn; + obj->options->output_closure = relobj->real_output_closure; + + return status; +} + + + + +static int +MimeMultipartRelatedClassInitialize(MimeMultipartRelatedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeMultipartRelated_initialize; + oclass->finalize = MimeMultipartRelated_finalize; + oclass->parse_eof = MimeMultipartRelated_parse_eof; + mclass->output_child_p = MimeMultipartRelated_output_child_p; + mclass->parse_child_line = MimeMultipartRelated_parse_child_line; + return 0; +} diff --git a/mailnews/mime/src/mimemrel.h b/mailnews/mime/src/mimemrel.h new file mode 100644 index 000000000..0c56873cf --- /dev/null +++ b/mailnews/mime/src/mimemrel.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMREL_H_ +#define _MIMEMREL_H_ + +#include "mimemult.h" +#include "plhash.h" +#include "prio.h" +#include "nsNetUtil.h" +#include "nsIMimeConverter.h" // for MimeConverterOutputCallback + +/* The MimeMultipartRelated class implements the multipart/related MIME + container, which allows `sibling' sub-parts to refer to each other. + */ + +typedef struct MimeMultipartRelatedClass MimeMultipartRelatedClass; +typedef struct MimeMultipartRelated MimeMultipartRelated; + +struct MimeMultipartRelatedClass { + MimeMultipartClass multipart; +}; + +extern "C" MimeMultipartRelatedClass mimeMultipartRelatedClass; + +struct MimeMultipartRelated { + MimeMultipart multipart; /* superclass variables */ + + char* base_url; /* Base URL (if any) for the whole + multipart/related. */ + + char* head_buffer; /* Buffer used to remember the text/html 'head' + part. */ + uint32_t head_buffer_fp; /* Active length. */ + uint32_t head_buffer_size; /* How big it is. */ + + nsCOMPtr <nsIFile> file_buffer; /* The nsIFile of a temp file used when we + run out of room in the head_buffer. */ + nsCOMPtr <nsIInputStream> input_file_stream; /* A stream to it. */ + nsCOMPtr <nsIOutputStream> output_file_stream; /* A stream to it. */ + + MimeHeaders* buffered_hdrs; /* The headers of the 'head' part. */ + + bool head_loaded; /* Whether we've already passed the 'head' + part. */ + MimeObject* headobj; /* The actual text/html head object. */ + + PLHashTable *hash; + + MimeConverterOutputCallback real_output_fn; + void* real_output_closure; + + char* curtag; + int32_t curtag_max; + int32_t curtag_length; + + + +}; + +#define MimeMultipartRelatedClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMREL_H_ */ diff --git a/mailnews/mime/src/mimemsg.cpp b/mailnews/mime/src/mimemsg.cpp new file mode 100644 index 000000000..d024a7725 --- /dev/null +++ b/mailnews/mime/src/mimemsg.cpp @@ -0,0 +1,965 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsIMimeEmitter.h" +#include "mimemsg.h" +#include "mimemoz2.h" +#include "prmem.h" +#include "prio.h" +#include "plstr.h" +#include "msgCore.h" +#include "prlog.h" +#include "prprf.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include "nsMsgMessageFlags.h" +#include "nsStringGlue.h" +#include "mimetext.h" +#include "mimecryp.h" +#include "mimetpfl.h" +#include "nsINetUtil.h" +#include "nsMsgUtils.h" +#include "nsMsgI18N.h" + +#define MIME_SUPERCLASS mimeContainerClass +MimeDefClass(MimeMessage, MimeMessageClass, mimeMessageClass, + &MIME_SUPERCLASS); + +static int MimeMessage_initialize (MimeObject *); +static void MimeMessage_finalize (MimeObject *); +static int MimeMessage_add_child (MimeObject *, MimeObject *); +static int MimeMessage_parse_begin (MimeObject *); +static int MimeMessage_parse_line (const char *, int32_t, MimeObject *); +static int MimeMessage_parse_eof (MimeObject *, bool); +static int MimeMessage_close_headers (MimeObject *obj); +static int MimeMessage_write_headers_html (MimeObject *); +static char *MimeMessage_partial_message_html(const char *data, + void *closure, + MimeHeaders *headers); + +#ifdef XP_UNIX +extern void MimeHeaders_do_unix_display_hook_hack(MimeHeaders *); +#endif /* XP_UNIX */ + +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeMessage_debug_print (MimeObject *, PRFileDesc *, int32_t depth); +#endif + +extern MimeObjectClass mimeMultipartClass; + +static int +MimeMessageClassInitialize(MimeMessageClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeContainerClass *cclass = (MimeContainerClass *) clazz; + + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeMessage_initialize; + oclass->finalize = MimeMessage_finalize; + oclass->parse_begin = MimeMessage_parse_begin; + oclass->parse_line = MimeMessage_parse_line; + oclass->parse_eof = MimeMessage_parse_eof; + cclass->add_child = MimeMessage_add_child; + +#if defined(DEBUG) && defined(XP_UNIX) + oclass->debug_print = MimeMessage_debug_print; +#endif + return 0; +} + + +static int +MimeMessage_initialize (MimeObject *object) +{ + MimeMessage *msg = (MimeMessage *)object; + msg->grabSubject = false; + msg->bodyLength = 0; + msg->sizeSoFar = 0; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeMessage_finalize (MimeObject *object) +{ + MimeMessage *msg = (MimeMessage *)object; + if (msg->hdrs) + MimeHeaders_free(msg->hdrs); + msg->hdrs = 0; + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeMessage_parse_begin (MimeObject *obj) +{ + MimeMessage *msg = (MimeMessage *)obj; + + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + if (obj->parent) + { + msg->grabSubject = true; + } + + /* Messages have separators before the headers, except for the outermost + message. */ + return MimeObject_write_separator(obj); +} + + +static int +MimeMessage_parse_line (const char *aLine, int32_t aLength, MimeObject *obj) +{ + const char * line = aLine; + int32_t length = aLength; + + MimeMessage *msg = (MimeMessage *) obj; + int status = 0; + + NS_ASSERTION(line && *line, "empty line in mime msg parse_line"); + if (!line || !*line) return -1; + + msg->sizeSoFar += length; + + if (msg->grabSubject) + { + if ( (!PL_strncasecmp(line, "Subject: ", 9)) && (obj->parent) ) + { + if ( (obj->headers) && (!obj->headers->munged_subject) ) + { + obj->headers->munged_subject = (char *) PL_strndup(line + 9, length - 9); + char *tPtr = obj->headers->munged_subject; + while (*tPtr) + { + if ( (*tPtr == '\r') || (*tPtr == '\n') ) + { + *tPtr = '\0'; + break; + } + tPtr++; + } + } + } + } + + /* If we already have a child object, then we're done parsing headers, + and all subsequent lines get passed to the inferior object without + further processing by us. (Our parent will stop feeding us lines + when this MimeMessage part is out of data.) + */ + if (msg->container.nchildren) + { + MimeObject *kid = msg->container.children[0]; + bool nl; + PR_ASSERT(kid); + if (!kid) return -1; + + msg->bodyLength += length; + + /* Don't allow MimeMessage objects to not end in a newline, since it + would be inappropriate for any following part to appear on the same + line as the last line of the message. + + #### This assumes that the only time the `parse_line' method is + called with a line that doesn't end in a newline is when that line + is the last line. + */ + nl = (length > 0 && (line[length-1] == '\r' || line[length-1] == '\n')); + +#ifdef MIME_DRAFTS + if (!mime_typep (kid, (MimeObjectClass*) &mimeMessageClass) && + obj->options && + obj->options->decompose_file_p && + !obj->options->is_multipart_msg && + obj->options->decompose_file_output_fn && + !obj->options->decrypt_p) + { + // If we are processing a flowed plain text line, we need to parse the + // line in mimeInlineTextPlainFlowedClass. + if (mime_typep(kid, (MimeObjectClass *)&mimeInlineTextPlainFlowedClass)) + { + // Remove any stuffed space. + if (length > 0 && ' ' == *line) + { + line++; + length--; + } + return kid->clazz->parse_line (line, length, kid); + } + else + { + status = obj->options->decompose_file_output_fn (line, + length, + obj->options->stream_closure); + if (status < 0) return status; + if (!nl) { + status = obj->options->decompose_file_output_fn (MSG_LINEBREAK, + MSG_LINEBREAK_LEN, + obj->options->stream_closure); + if (status < 0) return status; + } + return status; + } + } +#endif /* MIME_DRAFTS */ + + + if (nl) + return kid->clazz->parse_buffer (line, length, kid); + else + { + /* Hack a newline onto the end. */ + char *s = (char *)PR_MALLOC(length + MSG_LINEBREAK_LEN + 1); + if (!s) return MIME_OUT_OF_MEMORY; + memcpy(s, line, length); + PL_strncpyz(s + length, MSG_LINEBREAK, MSG_LINEBREAK_LEN + 1); + status = kid->clazz->parse_buffer (s, length + MSG_LINEBREAK_LEN, kid); + PR_Free(s); + return status; + } + } + + /* Otherwise we don't yet have a child object, which means we're not + done parsing our headers yet. + */ + if (!msg->hdrs) + { + msg->hdrs = MimeHeaders_new(); + if (!msg->hdrs) return MIME_OUT_OF_MEMORY; + } + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + ! obj->options->is_multipart_msg && + obj->options->done_parsing_outer_headers && + obj->options->decompose_file_output_fn ) + { + status = obj->options->decompose_file_output_fn( line, length, + obj->options->stream_closure ); + if (status < 0) + return status; + } +#endif /* MIME_DRAFTS */ + + status = MimeHeaders_parse_line(line, length, msg->hdrs); + if (status < 0) return status; + + /* If this line is blank, we're now done parsing headers, and should + examine our content-type to create our "body" part. + */ + if (*line == '\r' || *line == '\n') + { + status = MimeMessage_close_headers(obj); + if (status < 0) return status; + } + + return 0; +} + +static int +MimeMessage_close_headers (MimeObject *obj) +{ + MimeMessage *msg = (MimeMessage *) obj; + int status = 0; + char *ct = 0; /* Content-Type header */ + MimeObject *body; + + // Do a proper decoding of the munged subject. + if (obj->headers && msg->hdrs && msg->grabSubject && obj->headers->munged_subject) { + // nsMsgI18NConvertToUnicode wants nsAStrings... + nsDependentCString orig(obj->headers->munged_subject); + nsAutoString dest; + // First, get the Content-Type, then extract the charset="whatever" part of + // it. + nsCString charset; + nsCString contentType; + contentType.Adopt(MimeHeaders_get(msg->hdrs, HEADER_CONTENT_TYPE, false, false)); + if (!contentType.IsEmpty()) + charset.Adopt(MimeHeaders_get_parameter(contentType.get(), "charset", nullptr, nullptr)); + + // If we've got a charset, use nsMsgI18NConvertToUnicode to magically decode + // the munged subject. + if (!charset.IsEmpty()) { + nsresult rv = nsMsgI18NConvertToUnicode(charset.get(), orig, dest); + // If we managed to convert the string, replace munged_subject with the + // UTF8 version of it, otherwise, just forget about it (maybe there was an + // improperly encoded string in there). + PR_Free(obj->headers->munged_subject); + if (NS_SUCCEEDED(rv)) + obj->headers->munged_subject = ToNewUTF8String(dest); + else + obj->headers->munged_subject = nullptr; + } else { + PR_Free(obj->headers->munged_subject); + obj->headers->munged_subject = nullptr; + } + } + + if (msg->hdrs) + { + bool outer_p = !obj->headers; /* is this the outermost message? */ + + +#ifdef MIME_DRAFTS + if (outer_p && + obj->options && + (obj->options->decompose_file_p || obj->options->caller_need_root_headers) && + obj->options->decompose_headers_info_fn) + { +#ifdef ENABLE_SMIME + if (obj->options->decrypt_p && !mime_crypto_object_p(msg->hdrs, false, obj->options)) + obj->options->decrypt_p = false; +#endif /* ENABLE_SMIME */ + if (!obj->options->caller_need_root_headers || (obj == obj->options->state->root)) + status = obj->options->decompose_headers_info_fn ( + obj->options->stream_closure, + msg->hdrs ); + } +#endif /* MIME_DRAFTS */ + + + /* If this is the outermost message, we need to run the + `generate_header' callback. This happens here instead of + in `parse_begin', because it's only now that we've parsed + our headers. However, since this is the outermost message, + we have yet to write any HTML, so that's fine. + */ + if (outer_p && + obj->output_p && + obj->options && + obj->options->write_html_p && + obj->options->generate_header_html_fn) + { + int lstatus = 0; + char *html = 0; + + /* The generate_header_html_fn might return HTML, so it's important + that the output stream be set up with the proper type before we + make the MimeObject_write() call below. */ + if (!obj->options->state->first_data_written_p) + { + lstatus = MimeObject_output_init (obj, TEXT_HTML); + if (lstatus < 0) return lstatus; + PR_ASSERT(obj->options->state->first_data_written_p); + } + + html = obj->options->generate_header_html_fn(NULL, + obj->options->html_closure, + msg->hdrs); + if (html) + { + lstatus = MimeObject_write(obj, html, strlen(html), false); + PR_Free(html); + if (lstatus < 0) return lstatus; + } + } + + + /* Find the content-type of the body of this message. + */ + { + bool ok = true; + char *mv = MimeHeaders_get (msg->hdrs, HEADER_MIME_VERSION, + true, false); + +#ifdef REQUIRE_MIME_VERSION_HEADER + /* If this is the outermost message, it must have a MIME-Version + header with the value 1.0 for us to believe what might be in + the Content-Type header. If the MIME-Version header is not + present, we must treat this message as untyped. + */ + ok = (mv && !strcmp(mv, "1.0")); +#else + /* #### actually, we didn't check this in Mozilla 2.0, and checking + it now could cause some compatibility nonsense, so for now, let's + just believe any Content-Type header we see. + */ + ok = true; +#endif + + if (ok) + { + ct = MimeHeaders_get (msg->hdrs, HEADER_CONTENT_TYPE, true, false); + + /* If there is no Content-Type header, but there is a MIME-Version + header, then assume that this *is* in fact a MIME message. + (I've seen messages with + + MIME-Version: 1.0 + Content-Transfer-Encoding: quoted-printable + + and no Content-Type, and we should treat those as being of type + MimeInlineTextPlain rather than MimeUntypedText.) + */ + if (mv && !ct) + ct = strdup(TEXT_PLAIN); + } + + PR_FREEIF(mv); /* done with this now. */ + } + + /* If this message has a body which is encrypted and we're going to + decrypt it (whithout converting it to HTML, since decrypt_p and + write_html_p are never true at the same time) + */ + if (obj->output_p && + obj->options && + obj->options->decrypt_p +#ifdef ENABLE_SMIME + && !mime_crypto_object_p(msg->hdrs, false, obj->options) +#endif /* ENABLE_SMIME */ + ) + { + /* The body of this message is not an encrypted object, so we need + to turn off the decrypt_p flag (to prevent us from s#$%ing the + body of the internal object up into one.) In this case, + our output will end up being identical to our input. + */ + obj->options->decrypt_p = false; + } + + /* Emit the HTML for this message's headers. Do this before + creating the object representing the body. + */ + if (obj->output_p && + obj->options && + obj->options->write_html_p) + { + /* If citation headers are on, and this is not the outermost message, + turn them off. */ + if (obj->options->headers == MimeHeadersCitation && !outer_p) + obj->options->headers = MimeHeadersSome; + + /* Emit a normal header block. */ + status = MimeMessage_write_headers_html(obj); + if (status < 0) + { + PR_FREEIF(ct); + return status; + } + } + else if (obj->output_p) + { + /* Dump the headers, raw. */ + status = MimeObject_write(obj, "", 0, false); /* initialize */ + if (status < 0) + { + PR_FREEIF(ct); + return status; + } + status = MimeHeaders_write_raw_headers(msg->hdrs, obj->options, + obj->options->decrypt_p); + if (status < 0) + { + PR_FREEIF(ct); + return status; + } + } + +#ifdef XP_UNIX + if (outer_p && obj->output_p) + /* Kludge from mimehdrs.c */ + MimeHeaders_do_unix_display_hook_hack(msg->hdrs); +#endif /* XP_UNIX */ + } + + /* Never put out a separator after a message header block. */ + if (obj->options && obj->options->state) + obj->options->state->separator_suppressed_p = true; + +#ifdef MIME_DRAFTS + if ( !obj->headers && /* outer most message header */ + obj->options && + obj->options->decompose_file_p && + ct ) + obj->options->is_multipart_msg = PL_strcasestr(ct, "multipart/") != NULL; +#endif /* MIME_DRAFTS */ + + + body = mime_create(ct, msg->hdrs, obj->options); + + PR_FREEIF(ct); + if (!body) return MIME_OUT_OF_MEMORY; + status = ((MimeContainerClass *) obj->clazz)->add_child (obj, body); + if (status < 0) + { + mime_free(body); + return status; + } + + // Only do this if this is a Text Object! + if ( mime_typep(body, (MimeObjectClass *) &mimeInlineTextClass) ) + { + ((MimeInlineText *) body)->needUpdateMsgWinCharset = true; + } + + /* Now that we've added this new object to our list of children, + start its parser going. */ + status = body->clazz->parse_begin(body); + if (status < 0) return status; + + // Now notify the emitter if this is the outer most message, unless + // it is a part that is not the head of the message. If it's a part, + // we need to figure out the content type/charset of the part + // + bool outer_p = !obj->headers; /* is this the outermost message? */ + + if ( (outer_p || obj->options->notify_nested_bodies) && + (!obj->options->part_to_load || obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)) + { + // call SetMailCharacterSetToMsgWindow() to set a menu charset + if (mime_typep(body, (MimeObjectClass *) &mimeInlineTextClass)) + { + MimeInlineText *text = (MimeInlineText *) body; + if (text && text->charset && *text->charset) + SetMailCharacterSetToMsgWindow(body, text->charset); + } + + char *msgID = MimeHeaders_get (msg->hdrs, HEADER_MESSAGE_ID, + false, false); + + const char *outCharset = NULL; + if (!obj->options->force_user_charset) /* Only convert if the user prefs is false */ + outCharset = "UTF-8"; + + mimeEmitterStartBody(obj->options, (obj->options->headers == MimeHeadersNone), msgID, outCharset); + PR_FREEIF(msgID); + + // setting up truncated message html fotter function + char *xmoz = MimeHeaders_get(msg->hdrs, HEADER_X_MOZILLA_STATUS, false, + false); + if (xmoz) + { + uint32_t flags = 0; + char dummy = 0; + if (sscanf(xmoz, " %x %c", &flags, &dummy) == 1 && + flags & nsMsgMessageFlags::Partial) + { + obj->options->html_closure = obj; + obj->options->generate_footer_html_fn = + MimeMessage_partial_message_html; + } + PR_FREEIF(xmoz); + } + } + + return 0; +} + + + +static int +MimeMessage_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + bool outer_p; + MimeMessage *msg = (MimeMessage *)obj; + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + outer_p = !obj->headers; /* is this the outermost message? */ + + // Hack for messages with truncated headers (bug 244722) + // If there is no empty line in a message, the parser can't figure out where + // the headers end, causing parsing to hang. So we insert an extra newline + // to keep it happy. This is OK, since a message without any empty lines is + // broken anyway... + if(outer_p && msg->hdrs && ! msg->hdrs->done_p) { + MimeMessage_parse_line("\n", 1, obj); + } + + // Once we get to the end of parsing the message, we will notify + // the emitter that we are done the the body. + + // Mark the end of the mail body if we are actually emitting the + // body of the message (i.e. not Header ONLY) + if ((outer_p || obj->options->notify_nested_bodies) && obj->options && + obj->options->write_html_p) + { + if (obj->options->generate_footer_html_fn) + { + mime_stream_data *msd = + (mime_stream_data *) obj->options->stream_closure; + if (msd) + { + char *html = obj->options->generate_footer_html_fn + (msd->orig_url_name, obj->options->html_closure, msg->hdrs); + if (html) + { + int lstatus = MimeObject_write(obj, html, + strlen(html), + false); + PR_Free(html); + if (lstatus < 0) return lstatus; + } + } + } + if ((!obj->options->part_to_load || obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) && + obj->options->headers != MimeHeadersOnly) + mimeEmitterEndBody(obj->options); + } + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->done_parsing_outer_headers && + ! obj->options->is_multipart_msg && + ! mime_typep(obj, (MimeObjectClass*) &mimeEncryptedClass) && + obj->options->decompose_file_close_fn ) { + status = obj->options->decompose_file_close_fn ( + obj->options->stream_closure ); + + if ( status < 0 ) return status; + } +#endif /* MIME_DRAFTS */ + + + /* Put out a separator after every message/rfc822 object. */ + if (!abort_p && !outer_p) + { + status = MimeObject_write_separator(obj); + if (status < 0) return status; + } + + return 0; +} + + +static int +MimeMessage_add_child (MimeObject *parent, MimeObject *child) +{ + MimeContainer *cont = (MimeContainer *) parent; + PR_ASSERT(parent && child); + if (!parent || !child) return -1; + + /* message/rfc822 containers can only have one child. */ + PR_ASSERT(cont->nchildren == 0); + if (cont->nchildren != 0) return -1; + +#ifdef MIME_DRAFTS + if ( parent->options && + parent->options->decompose_file_p && + ! parent->options->is_multipart_msg && + ! mime_typep(child, (MimeObjectClass*) &mimeEncryptedClass) && + parent->options->decompose_file_init_fn ) { + int status = 0; + status = parent->options->decompose_file_init_fn ( + parent->options->stream_closure, + ((MimeMessage*)parent)->hdrs ); + if ( status < 0 ) return status; + } +#endif /* MIME_DRAFTS */ + + return ((MimeContainerClass*)&MIME_SUPERCLASS)->add_child (parent, child); +} + +// This is necessary to determine which charset to use for a reply/forward +char * +DetermineMailCharset(MimeMessage *msg) +{ + char *retCharset = nullptr; + + if ( (msg) && (msg->hdrs) ) + { + char *ct = MimeHeaders_get (msg->hdrs, HEADER_CONTENT_TYPE, + false, false); + if (ct) + { + retCharset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL); + PR_Free(ct); + } + + if (!retCharset) + { + // If we didn't find "Content-Type: ...; charset=XX" then look + // for "X-Sun-Charset: XX" instead. (Maybe this should be done + // in MimeSunAttachmentClass, but it's harder there than here.) + retCharset = MimeHeaders_get (msg->hdrs, HEADER_X_SUN_CHARSET, + false, false); + } + } + + if (!retCharset) + return strdup("ISO-8859-1"); + else + return retCharset; +} + +static int +MimeMessage_write_headers_html (MimeObject *obj) +{ + MimeMessage *msg = (MimeMessage *) obj; + int status; + + if (!obj->options || !obj->options->output_fn) + return 0; + + PR_ASSERT(obj->output_p && obj->options->write_html_p); + + // To support the no header option! Make sure we are not + // suppressing headers on included email messages... + if ( (obj->options->headers == MimeHeadersNone) && + (obj == obj->options->state->root) ) + { + // Ok, we are going to kick the Emitter for a StartHeader + // operation ONLY WHEN THE CHARSET OF THE ORIGINAL MESSAGE IS + // NOT US-ASCII ("ISO-8859-1") + // + // This is only to notify the emitter of the charset of the + // original message + char *mailCharset = DetermineMailCharset(msg); + + if ( (mailCharset) && (PL_strcasecmp(mailCharset, "US-ASCII")) && + (PL_strcasecmp(mailCharset, "ISO-8859-1")) ) + mimeEmitterUpdateCharacterSet(obj->options, mailCharset); + PR_FREEIF(mailCharset); + return 0; + } + + if (!obj->options->state->first_data_written_p) + { + status = MimeObject_output_init (obj, TEXT_HTML); + if (status < 0) + { + mimeEmitterEndHeader(obj->options, obj); + return status; + } + PR_ASSERT(obj->options->state->first_data_written_p); + } + + // Start the header parsing by the emitter + char *msgID = MimeHeaders_get (msg->hdrs, HEADER_MESSAGE_ID, + false, false); + bool outer_p = !obj->headers; /* is this the outermost message? */ + if (!outer_p && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay && + obj->options->part_to_load) + { + //Maybe we are displaying a embedded message as outer part! + char *id = mime_part_address(obj); + if (id) + { + outer_p = !strcmp(id, obj->options->part_to_load); + PR_Free(id); + } + } + + // Ok, we should really find out the charset of this part. We always + // output UTF-8 for display, but the original charset is necessary for + // reply and forward operations. + // + char *mailCharset = DetermineMailCharset(msg); + mimeEmitterStartHeader(obj->options, + outer_p, + (obj->options->headers == MimeHeadersOnly), + msgID, + mailCharset); + + // Change the default_charset by the charset of the original message + // ONLY WHEN THE CHARSET OF THE ORIGINAL MESSAGE IS NOT US-ASCII + // ("ISO-8859-1") and default_charset and mailCharset are different, + // or when there is no default_charset (this can happen with saved messages). + if ( (!obj->options->default_charset || + ((mailCharset) && (PL_strcasecmp(mailCharset, "US-ASCII")) && + (PL_strcasecmp(mailCharset, "ISO-8859-1")) && + (PL_strcasecmp(obj->options->default_charset, mailCharset)))) && + !obj->options->override_charset ) + { + PR_FREEIF(obj->options->default_charset); + obj->options->default_charset = strdup(mailCharset); + } + + PR_FREEIF(msgID); + PR_FREEIF(mailCharset); + + status = MimeHeaders_write_all_headers (msg->hdrs, obj->options, false); + if (status < 0) + { + mimeEmitterEndHeader(obj->options, obj); + return status; + } + + // If this is the outermost message, then now is the time to run the + // post_header_html_fn. + if (obj->options && obj->options->state && + obj->options->generate_post_header_html_fn && + !obj->options->state->post_header_html_run_p) { + char *html = 0; + PR_ASSERT(obj->options->state->first_data_written_p); + html = obj->options->generate_post_header_html_fn( + NULL, obj->options->html_closure, msg->hdrs); + obj->options->state->post_header_html_run_p = true; + if (html) { + status = MimeObject_write(obj, html, strlen(html), false); + PR_Free(html); + if (status < 0) { + mimeEmitterEndHeader(obj->options, obj); + return status; + } + } + } + + mimeEmitterEndHeader(obj->options, obj); + + // rhp: + // For now, we are going to parse the entire message, even if we are + // only interested in headers...why? Well, because this is the only + // way to build the attachment list. Now we will have the attachment + // list in the output being created by the XML emitter. If we ever + // want to go back to where we were before, just uncomment the conditional + // and it will stop at header parsing. + // + // if (obj->options->headers == MimeHeadersOnly) + // return -1; + // else + + return 0; +} + +static char * +MimeMessage_partial_message_html(const char *data, void *closure, + MimeHeaders *headers) +{ + MimeMessage *msg = (MimeMessage *)closure; + nsAutoCString orig_url(data); + char *uidl = MimeHeaders_get(headers, HEADER_X_UIDL, false, false); + char *msgId = MimeHeaders_get(headers, HEADER_MESSAGE_ID, false, + false); + char *msgIdPtr = PL_strchr(msgId, '<'); + + int32_t pos = orig_url.Find("mailbox-message"); + if (pos != -1) + orig_url.Cut(pos + 7, 8); + + pos = orig_url.FindChar('#'); + if (pos != -1) + orig_url.Replace(pos, 1, "?number=", 8); + + if (msgIdPtr) + msgIdPtr++; + else + msgIdPtr = msgId; + char *gtPtr = PL_strchr(msgIdPtr, '>'); + if (gtPtr) + *gtPtr = 0; + + bool msgBaseTruncated = (msg->bodyLength > MSG_LINEBREAK_LEN); + + nsCString partialMsgHtml; + nsCString item; + + partialMsgHtml.AppendLiteral("<div style=\"margin: 1em auto; border: 1px solid black; width: 80%\">"); + partialMsgHtml.AppendLiteral("<div style=\"margin: 5px; padding: 10px; border: 1px solid gray; font-weight: bold; text-align: center;\">"); + + partialMsgHtml.AppendLiteral("<span style=\"font-size: 120%;\">"); + if (msgBaseTruncated) + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_TRUNCATED")); + else + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_NOT_DOWNLOADED")); + partialMsgHtml += item; + partialMsgHtml.AppendLiteral("</span><hr>"); + + if (msgBaseTruncated) + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_TRUNCATED_EXPLANATION")); + else + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_NOT_DOWNLOADED_EXPLANATION")); + partialMsgHtml += item; + partialMsgHtml.AppendLiteral("<br><br>"); + + partialMsgHtml.AppendLiteral("<a href=\""); + partialMsgHtml.Append(orig_url); + + if (msgIdPtr) { + partialMsgHtml.AppendLiteral("&messageid="); + + MsgEscapeString(nsDependentCString(msgIdPtr), nsINetUtil::ESCAPE_URL_PATH, + item); + + partialMsgHtml.Append(item); + } + + if (uidl) { + partialMsgHtml.AppendLiteral("&uidl="); + + MsgEscapeString(nsDependentCString(uidl), nsINetUtil::ESCAPE_XALPHAS, + item); + + partialMsgHtml.Append(item); + } + + partialMsgHtml.AppendLiteral("\">"); + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_CLICK_FOR_REST")); + partialMsgHtml += item; + partialMsgHtml.AppendLiteral("</a>"); + + partialMsgHtml.AppendLiteral("</div></div>"); + + return ToNewCString(partialMsgHtml); +} + +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeMessage_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + MimeMessage *msg = (MimeMessage *) obj; + char *addr = mime_part_address(obj); + int i; + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); +/* + fprintf(stream, "<%s %s%s 0x%08X>\n", + obj->clazz->class_name, + addr ? addr : "???", + (msg->container.nchildren == 0 ? " (no body)" : ""), + (uint32_t) msg); +*/ + PR_FREEIF(addr); + +#if 0 + if (msg->hdrs) + { + char *s; + + depth++; + +# define DUMP(HEADER) \ + for (i=0; i < depth; i++) \ + PR_Write(stream, " ", 2); \ + s = MimeHeaders_get (msg->hdrs, HEADER, false, true); +/** + \ + PR_Write(stream, HEADER ": %s\n", s ? s : ""); \ +**/ + + PR_FREEIF(s) + + DUMP(HEADER_SUBJECT); + DUMP(HEADER_DATE); + DUMP(HEADER_FROM); + DUMP(HEADER_TO); + /* DUMP(HEADER_CC); */ + DUMP(HEADER_NEWSGROUPS); + DUMP(HEADER_MESSAGE_ID); +# undef DUMP + + PR_Write(stream, "\n", 1); + } +#endif + + PR_ASSERT(msg->container.nchildren <= 1); + if (msg->container.nchildren == 1) + { + MimeObject *kid = msg->container.children[0]; + int status = kid->clazz->debug_print (kid, stream, depth+1); + if (status < 0) return status; + } + return 0; +} +#endif diff --git a/mailnews/mime/src/mimemsg.h b/mailnews/mime/src/mimemsg.h new file mode 100644 index 000000000..9a38a6e71 --- /dev/null +++ b/mailnews/mime/src/mimemsg.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMSG_H_ +#define _MIMEMSG_H_ + +#include "mimecont.h" + +/* The MimeMessage class implements the message/rfc822 and message/news + MIME containers, which is to say, mail and news messages. + */ + +typedef struct MimeMessageClass MimeMessageClass; +typedef struct MimeMessage MimeMessage; + +struct MimeMessageClass { + MimeContainerClass container; +}; + +extern MimeMessageClass mimeMessageClass; + +struct MimeMessage { + MimeContainer container; /* superclass variables */ + MimeHeaders *hdrs; /* headers of this message */ + bool newline_p; /* whether the last line ended in a newline */ + bool grabSubject; /* Should we try to grab the subject of this message */ + int32_t bodyLength; /* Used for determining if the body has been truncated */ + int32_t sizeSoFar; /* The total size of the MIME message, once parsing is + finished. */ +}; + +#define MimeMessageClassInitializer(ITYPE,CSUPER) \ + { MimeContainerClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMSG_H_ */ diff --git a/mailnews/mime/src/mimemsig.cpp b/mailnews/mime/src/mimemsig.cpp new file mode 100644 index 000000000..5b4f8e556 --- /dev/null +++ b/mailnews/mime/src/mimemsig.cpp @@ -0,0 +1,783 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "modmimee.h" +#include "mimemsig.h" +#include "nspr.h" + +#include "prmem.h" +#include "plstr.h" +#include "prerror.h" +#include "nsMimeTypes.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "mimeeobj.h" +#include "nsIMimeConverter.h" // for MimeConverterOutputCallback +#include "mozilla/Attributes.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartSigned, MimeMultipartSignedClass, + mimeMultipartSignedClass, &MIME_SUPERCLASS); + +static int MimeMultipartSigned_initialize (MimeObject *); +static int MimeMultipartSigned_create_child (MimeObject *); +static int MimeMultipartSigned_close_child(MimeObject *); +static int MimeMultipartSigned_parse_line (const char *, int32_t, MimeObject *); +static int MimeMultipartSigned_parse_child_line (MimeObject *, const char *, int32_t, + bool); +static int MimeMultipartSigned_parse_eof (MimeObject *, bool); +static void MimeMultipartSigned_finalize (MimeObject *); + +static int MimeMultipartSigned_emit_child (MimeObject *obj); + +extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass; + +static int +MimeMultipartSignedClassInitialize(MimeMultipartSignedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + + oclass->initialize = MimeMultipartSigned_initialize; + oclass->parse_line = MimeMultipartSigned_parse_line; + oclass->parse_eof = MimeMultipartSigned_parse_eof; + oclass->finalize = MimeMultipartSigned_finalize; + mclass->create_child = MimeMultipartSigned_create_child; + mclass->parse_child_line = MimeMultipartSigned_parse_child_line; + mclass->close_child = MimeMultipartSigned_close_child; + + PR_ASSERT(!oclass->class_initialized); + return 0; +} + +static int +MimeMultipartSigned_initialize (MimeObject *object) +{ + MimeMultipartSigned *sig = (MimeMultipartSigned *) object; + + /* This is an abstract class; it shouldn't be directly instantiated. */ + PR_ASSERT(object->clazz != (MimeObjectClass *) &mimeMultipartSignedClass); + + sig->state = MimeMultipartSignedPreamble; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeMultipartSigned_cleanup (MimeObject *obj, bool finalizing_p) +{ + MimeMultipart *mult = (MimeMultipart *) obj; /* #58075. Fix suggested by jwz */ + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + if (sig->part_buffer) + { + MimePartBufferDestroy(sig->part_buffer); + sig->part_buffer = 0; + } + if (sig->body_hdrs) + { + MimeHeaders_free (sig->body_hdrs); + sig->body_hdrs = 0; + } + if (sig->sig_hdrs) + { + MimeHeaders_free (sig->sig_hdrs); + sig->sig_hdrs = 0; + } + + mult->state = MimeMultipartEpilogue; /* #58075. Fix suggested by jwz */ + sig->state = MimeMultipartSignedEpilogue; + + if (finalizing_p && sig->crypto_closure) { + /* Don't free these until this object is really going away -- keep them + around for the lifetime of the MIME object, so that we can get at the + security info of sub-parts of the currently-displayed message. */ + ((MimeMultipartSignedClass *) obj->clazz)->crypto_free (sig->crypto_closure); + sig->crypto_closure = 0; + } + + if (sig->sig_decoder_data) + { + MimeDecoderDestroy(sig->sig_decoder_data, true); + sig->sig_decoder_data = 0; + } +} + +static int +MimeMultipartSigned_parse_eof (MimeObject *obj, bool abort_p) +{ + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + int status = 0; + + if (obj->closed_p) return 0; + + /* Close off the signature, if we've gotten that far. + */ + if (sig->state == MimeMultipartSignedSignatureHeaders || + sig->state == MimeMultipartSignedSignatureFirstLine || + sig->state == MimeMultipartSignedSignatureLine || + sig->state == MimeMultipartSignedEpilogue) + { + status = (((MimeMultipartSignedClass *) obj->clazz)->crypto_signature_eof) (sig->crypto_closure, abort_p); + if (status < 0) return status; + } + + if (!abort_p) + { + /* Now that we've read both the signed object and the signature (and + have presumably verified the signature) write out a blurb, and then + the signed object. + */ + status = MimeMultipartSigned_emit_child(obj); + if (status < 0) return status; + } + + MimeMultipartSigned_cleanup(obj, false); + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); +} + + +static void +MimeMultipartSigned_finalize (MimeObject *obj) +{ + MimeMultipartSigned_cleanup(obj, true); + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + + +static int +MimeMultipartSigned_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + MimeMultipartParseState old_state = mult->state; + bool hash_line_p = true; + bool no_headers_p = false; + int status = 0; + + /* First do the parsing for normal multipart/ objects by handing it off to + the superclass method. This includes calling the create_child and + close_child methods. + */ + status = (((MimeObjectClass *)(&MIME_SUPERCLASS)) + ->parse_line (line, length, obj)); + if (status < 0) return status; + + /* The instance variable MimeMultipartClass->state tracks motion through + the various stages of multipart/ parsing. The instance variable + MimeMultipartSigned->state tracks the difference between the first + part (the body) and the second part (the signature.) This second, + more specific state variable is updated by noticing the transitions + of the first, more general state variable. + */ + if (old_state != mult->state) /* there has been a state change */ + { + switch (mult->state) + { + case MimeMultipartPreamble: + PR_ASSERT(0); /* can't move *in* to preamble state. */ + sig->state = MimeMultipartSignedPreamble; + break; + + case MimeMultipartHeaders: + /* If we're moving in to the Headers state, then that means + that this line is the preceeding boundary string (and we + should ignore it.) + */ + hash_line_p = false; + + if (sig->state == MimeMultipartSignedPreamble) + sig->state = MimeMultipartSignedBodyFirstHeader; + else if (sig->state == MimeMultipartSignedBodyFirstLine || + sig->state == MimeMultipartSignedBodyLine) + sig->state = MimeMultipartSignedSignatureHeaders; + else if (sig->state == MimeMultipartSignedSignatureFirstLine || + sig->state == MimeMultipartSignedSignatureLine) + sig->state = MimeMultipartSignedEpilogue; + break; + + case MimeMultipartPartFirstLine: + if (sig->state == MimeMultipartSignedBodyFirstHeader) + { + sig->state = MimeMultipartSignedBodyFirstLine; + no_headers_p = true; + } + else if (sig->state == MimeMultipartSignedBodyHeaders) + sig->state = MimeMultipartSignedBodyFirstLine; + else if (sig->state == MimeMultipartSignedSignatureHeaders) + sig->state = MimeMultipartSignedSignatureFirstLine; + else + sig->state = MimeMultipartSignedEpilogue; + break; + + case MimeMultipartPartLine: + + PR_ASSERT(sig->state == MimeMultipartSignedBodyFirstLine || + sig->state == MimeMultipartSignedBodyLine || + sig->state == MimeMultipartSignedSignatureFirstLine || + sig->state == MimeMultipartSignedSignatureLine); + + if (sig->state == MimeMultipartSignedBodyFirstLine) + sig->state = MimeMultipartSignedBodyLine; + else if (sig->state == MimeMultipartSignedSignatureFirstLine) + sig->state = MimeMultipartSignedSignatureLine; + break; + + case MimeMultipartEpilogue: + sig->state = MimeMultipartSignedEpilogue; + break; + + default: /* bad state */ + NS_ERROR("bad state in MultipartSigned parse line"); + return -1; + break; + } + } + + + /* Perform multipart/signed-related actions on this line based on the state + of the parser. + */ + switch (sig->state) + { + case MimeMultipartSignedPreamble: + /* Do nothing. */ + break; + + case MimeMultipartSignedBodyFirstLine: + /* We have just moved out of the MimeMultipartSignedBodyHeaders + state, so cache away the headers that apply only to the body part. + */ + NS_ASSERTION(mult->hdrs, "null multipart hdrs"); + NS_ASSERTION(!sig->body_hdrs, "signed part shouldn't have already have body_hdrs"); + sig->body_hdrs = mult->hdrs; + mult->hdrs = 0; + + /* fall through. */ + MOZ_FALLTHROUGH; + case MimeMultipartSignedBodyFirstHeader: + case MimeMultipartSignedBodyHeaders: + case MimeMultipartSignedBodyLine: + + if (!sig->crypto_closure) + { + /* Set error change */ + PR_SetError(0, 0); + /* Initialize the signature verification library. */ + sig->crypto_closure = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_init) (obj); + if (!sig->crypto_closure) + { + status = PR_GetError(); + NS_ASSERTION(status < 0, "got non-negative status"); + if (status >= 0) + status = -1; + return status; + } + } + + if (hash_line_p) + { + /* this is the first hashed line if this is the first header + (that is, if it's the first line in the header state after + a state change.) + */ + bool first_line_p + = (no_headers_p || + sig->state == MimeMultipartSignedBodyFirstHeader); + + if (sig->state == MimeMultipartSignedBodyFirstHeader) + sig->state = MimeMultipartSignedBodyHeaders; + + /* The newline issues here are tricky, since both the newlines + before and after the boundary string are to be considered part + of the boundary: this is so that a part can be specified such + that it does not end in a trailing newline. + + To implement this, we send a newline *before* each line instead + of after, except for the first line, which is not preceeded by a + newline. + + For purposes of cryptographic hashing, we always hash line + breaks as CRLF -- the canonical, on-the-wire linebreaks, since + we have no idea of knowing what line breaks were used on the + originating system (SMTP rightly destroys that information.) + */ + + /* Remove the trailing newline... */ + if (length > 0 && line[length-1] == '\n') length--; + if (length > 0 && line[length-1] == '\r') length--; + + PR_ASSERT(sig->crypto_closure); + + if (!first_line_p) + { + /* Push out a preceeding newline... */ + char nl[] = CRLF; + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_data_hash (nl, 2, sig->crypto_closure)); + if (status < 0) return status; + } + + /* Now push out the line sans trailing newline. */ + if (length > 0) + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_data_hash (line,length, sig->crypto_closure)); + if (status < 0) return status; + } + break; + + case MimeMultipartSignedSignatureHeaders: + + if (sig->crypto_closure && + old_state != mult->state) + { + /* We have just moved out of the MimeMultipartSignedBodyLine + state, so tell the signature verification library that we've + reached the end of the signed data. + */ + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_data_eof) (sig->crypto_closure, false); + if (status < 0) return status; + } + break; + + case MimeMultipartSignedSignatureFirstLine: + /* We have just moved out of the MimeMultipartSignedSignatureHeaders + state, so cache away the headers that apply only to the sig part. + */ + PR_ASSERT(mult->hdrs); + PR_ASSERT(!sig->sig_hdrs); + sig->sig_hdrs = mult->hdrs; + mult->hdrs = 0; + + + /* If the signature block has an encoding, set up a decoder for it. + (Similar logic is in MimeLeafClass->parse_begin.) + */ + { + MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0; + nsCString encoding; + encoding.Adopt(MimeHeaders_get (sig->sig_hdrs, + HEADER_CONTENT_TRANSFER_ENCODING, + true, false)); + if (encoding.IsEmpty()) + ; + else if (!PL_strcasecmp(encoding.get(), ENCODING_BASE64)) + fn = &MimeB64DecoderInit; + else if (!PL_strcasecmp(encoding.get(), ENCODING_QUOTED_PRINTABLE)) + { + sig->sig_decoder_data = + MimeQPDecoderInit (((MimeConverterOutputCallback) + (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_signature_hash)), + sig->crypto_closure); + if (!sig->sig_decoder_data) + return MIME_OUT_OF_MEMORY; + } + else if (!PL_strcasecmp(encoding.get(), ENCODING_UUENCODE) || + !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE2) || + !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE3) || + !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE4)) + fn = &MimeUUDecoderInit; + else if (!PL_strcasecmp(encoding.get(), ENCODING_YENCODE)) + fn = &MimeYDecoderInit; + if (fn) + { + sig->sig_decoder_data = + fn (((MimeConverterOutputCallback) + (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_signature_hash)), + sig->crypto_closure); + if (!sig->sig_decoder_data) + return MIME_OUT_OF_MEMORY; + } + } + + /* Show these headers to the crypto module. */ + if (hash_line_p) + { + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_signature_init) (sig->crypto_closure, + obj, sig->sig_hdrs); + if (status < 0) return status; + } + + /* fall through. */ + MOZ_FALLTHROUGH; + case MimeMultipartSignedSignatureLine: + if (hash_line_p) + { + /* Feed this line into the signature verification routines. */ + + if (sig->sig_decoder_data) + status = MimeDecoderWrite (sig->sig_decoder_data, line, length, nullptr); + else + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_signature_hash (line, length, + sig->crypto_closure)); + if (status < 0) return status; + } + break; + + case MimeMultipartSignedEpilogue: + /* Nothing special to do here. */ + break; + + default: /* bad state */ + PR_ASSERT(0); + return -1; + } + + return status; +} + + +static int +MimeMultipartSigned_create_child (MimeObject *parent) +{ + /* Don't actually create a child -- we call the superclass create_child + method later, after we've fully parsed everything. (And we only call + it once, for part #1, and never for part #2 (the signature.)) + */ + MimeMultipart *mult = (MimeMultipart *) parent; + mult->state = MimeMultipartPartFirstLine; + return 0; +} + + +static int +MimeMultipartSigned_close_child (MimeObject *obj) +{ + /* The close_child method on MimeMultipartSigned doesn't actually do + anything to the children list, since the create_child method also + doesn't do anything. + */ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeContainer *cont = (MimeContainer *) obj; + MimeMultipartSigned *msig = (MimeMultipartSigned *) obj; + + if (msig->part_buffer) + /* Closes the tmp file, if there is one: doesn't free the part_buffer. */ + MimePartBufferClose(msig->part_buffer); + + if (mult->hdrs) /* duplicated from MimeMultipart_close_child, ugh. */ + { + MimeHeaders_free(mult->hdrs); + mult->hdrs = 0; + } + + /* Should be no kids yet. */ + PR_ASSERT(cont->nchildren == 0); + if (cont->nchildren != 0) return -1; + + return 0; +} + + +static int +MimeMultipartSigned_parse_child_line (MimeObject *obj, + const char *line, int32_t length, + bool first_line_p) +{ + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + MimeContainer *cont = (MimeContainer *) obj; + int status = 0; + + /* Shouldn't have made any sub-parts yet. */ + PR_ASSERT(cont->nchildren == 0); + if (cont->nchildren != 0) return -1; + + switch (sig->state) + { + case MimeMultipartSignedPreamble: + case MimeMultipartSignedBodyFirstHeader: + case MimeMultipartSignedBodyHeaders: + // How'd we get here? Oh well, fall through. + NS_ERROR("wrong state in parse child line"); + MOZ_FALLTHROUGH; + case MimeMultipartSignedBodyFirstLine: + PR_ASSERT(first_line_p); + if (!sig->part_buffer) + { + sig->part_buffer = MimePartBufferCreate(); + if (!sig->part_buffer) + return MIME_OUT_OF_MEMORY; + } + /* fall through */ + MOZ_FALLTHROUGH; + case MimeMultipartSignedBodyLine: + { + /* This is the first part; we are buffering it, and will emit it all + at the end (so that we know whether the signature matches before + showing anything to the user.) + */ + + /* The newline issues here are tricky, since both the newlines + before and after the boundary string are to be considered part + of the boundary: this is so that a part can be specified such + that it does not end in a trailing newline. + + To implement this, we send a newline *before* each line instead + of after, except for the first line, which is not preceeded by a + newline. + */ + + /* Remove the trailing newline... */ + if (length > 0 && line[length-1] == '\n') length--; + if (length > 0 && line[length-1] == '\r') length--; + + PR_ASSERT(sig->part_buffer); + PR_ASSERT(first_line_p == + (sig->state == MimeMultipartSignedBodyFirstLine)); + + if (!first_line_p) + { + /* Push out a preceeding newline... */ + char nl[] = MSG_LINEBREAK; + status = MimePartBufferWrite (sig->part_buffer, nl, MSG_LINEBREAK_LEN); + if (status < 0) return status; + } + + /* Now push out the line sans trailing newline. */ + if (length > 0) + status = MimePartBufferWrite (sig->part_buffer, line, length); + if (status < 0) return status; + } + break; + + case MimeMultipartSignedSignatureHeaders: + // How'd we get here? Oh well, fall through. + NS_ERROR("should have already parse sig hdrs"); + MOZ_FALLTHROUGH; + case MimeMultipartSignedSignatureFirstLine: + case MimeMultipartSignedSignatureLine: + /* Nothing to do here -- hashing of the signature part is handled up + in MimeMultipartSigned_parse_line(). + */ + break; + + case MimeMultipartSignedEpilogue: + /* Too many kids? MimeMultipartSigned_create_child() should have + prevented us from getting here. */ + NS_ERROR("too many kids?"); + return -1; + break; + + default: /* bad state */ + NS_ERROR("bad state in multipart signed parse line"); + return -1; + break; + } + + return status; +} + + +static int +MimeMultipartSigned_emit_child (MimeObject *obj) +{ + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + MimeMultipart *mult = (MimeMultipart *) obj; + MimeContainer *cont = (MimeContainer *) obj; + int status = 0; + MimeObject *body; + + if (!sig->crypto_closure) { + // We might have decided to skip processing this part. + return 0; + } + + NS_ASSERTION(sig->crypto_closure, "no crypto closure"); + + /* Emit some HTML saying whether the signature was cool. + But don't emit anything if in FO_QUOTE_MESSAGE mode. + */ + if (obj->options && + obj->options->headers != MimeHeadersCitation && + obj->options->write_html_p && + obj->options->output_fn && + sig->crypto_closure) + { + // Calling crypto_generate_html may trigger wanted side effects, + // but we're no longer using its results. + char *html = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_generate_html (sig->crypto_closure)); + PR_FREEIF(html); + + /* Now that we have written out the crypto stamp, the outermost header + block is well and truly closed. If this is in fact the outermost + message, then run the post_header_html_fn now. + */ + if (obj->options && + obj->options->state && + obj->options->generate_post_header_html_fn && + !obj->options->state->post_header_html_run_p) + { + MimeHeaders *outer_headers=nullptr; + MimeObject *p; + for (p = obj; p->parent; p = p->parent) + outer_headers = p->headers; + NS_ASSERTION(obj->options->state->first_data_written_p, + "should have already written some data"); + html = obj->options->generate_post_header_html_fn(NULL, + obj->options->html_closure, + outer_headers); + obj->options->state->post_header_html_run_p = true; + if (html) + { + status = MimeObject_write(obj, html, strlen(html), false); + PR_Free(html); + if (status < 0) return status; + } + } + } + + + /* Oh, this is fairly nasty. We're skipping over our "create child" method + and using the one our superclass defines. Perhaps instead we should add + a new method on this class, and initialize that method to be the + create_child method of the superclass. Whatever. + */ + + + /* The superclass method expects to find the headers for the part that it's + to create in mult->hdrs, so ensure that they're there. */ + NS_ASSERTION(!mult->hdrs, "shouldn't already have hdrs for multipart"); + if (mult->hdrs) MimeHeaders_free(mult->hdrs); + mult->hdrs = sig->body_hdrs; + sig->body_hdrs = 0; + + /* Run the superclass create_child method. + */ + status = (((MimeMultipartClass *)(&MIME_SUPERCLASS))->create_child(obj)); + if (status < 0) return status; + + // Notify the charset of the first part. + if (obj->options && !(obj->options->override_charset)) { + MimeObject *firstChild = ((MimeContainer*) obj)->children[0]; + char *disposition = MimeHeaders_get (firstChild->headers, + HEADER_CONTENT_DISPOSITION, + true, + false); + // check if need to show as inline + if (!disposition) + { + const char *content_type = firstChild->content_type; + if (!PL_strcasecmp (content_type, TEXT_PLAIN) || + !PL_strcasecmp (content_type, TEXT_HTML) || + !PL_strcasecmp (content_type, TEXT_MDL) || + !PL_strcasecmp (content_type, MULTIPART_ALTERNATIVE) || + !PL_strcasecmp (content_type, MULTIPART_RELATED) || + !PL_strcasecmp (content_type, MESSAGE_NEWS) || + !PL_strcasecmp (content_type, MESSAGE_RFC822)) { + char *ct = MimeHeaders_get(mult->hdrs, HEADER_CONTENT_TYPE, false, false); + if (ct) { + char *cset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL); + if (cset) { + mimeEmitterUpdateCharacterSet(obj->options, cset); + SetMailCharacterSetToMsgWindow(obj, cset); + PR_Free(cset); + } + PR_Free(ct); + } + } + } + } + + // The js emitter wants to know about the newly created child. Because + // MimeMultipartSigned dummies out its create_child operation, the logic + // in MimeMultipart_parse_line that would normally provide this notification + // does not get to fire. + if (obj->options && obj->options->notify_nested_bodies) { + MimeObject *kid = ((MimeContainer*) obj)->children[0]; + // The emitter is expecting the content type with parameters; not the fully + // parsed thing, so get it from raw. (We do not do it in the charset + // notification block that just happened because it already has complex + // if-checks that do not jive with us. + char *ct = MimeHeaders_get(mult->hdrs, HEADER_CONTENT_TYPE, false, + false); + mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE, + ct ? ct : "text/plain"); + PR_Free(ct); + + char *part_path = mime_part_address(kid); + if (part_path) { + mimeEmitterAddHeaderField(obj->options, + "x-jsemitter-part-path", + part_path); + PR_Free(part_path); + } + } + + /* Retrieve the child that it created. + */ + NS_ASSERTION(cont->nchildren == 1, "should only have one child"); + if (cont->nchildren != 1) + return -1; + body = cont->children[0]; + NS_ASSERTION(body, "missing body"); + if (!body) + return -1; + + if (mime_typep(body, (MimeObjectClass *)&mimeSuppressedCryptoClass)) { + ((MimeMultipartSignedClass *)obj->clazz) + ->crypto_notify_suppressed_child(sig->crypto_closure); + } + +#ifdef MIME_DRAFTS + if (body->options->decompose_file_p) { + body->options->signed_p = true; + if (!mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) && + body->options->decompose_file_init_fn) + body->options->decompose_file_init_fn ( body->options->stream_closure, body->headers ); + } +#endif /* MIME_DRAFTS */ + + /* If there's no part_buffer, this is a zero-length signed message? */ + if (sig->part_buffer) + { +#ifdef MIME_DRAFTS + if (body->options->decompose_file_p && + !mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) && + body->options->decompose_file_output_fn) + status = MimePartBufferRead (sig->part_buffer, + /* The (MimeConverterOutputCallback) cast is to turn the + `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) + body->options->decompose_file_output_fn), + body->options->stream_closure); + else +#endif /* MIME_DRAFTS */ + + status = MimePartBufferRead (sig->part_buffer, + /* The (MimeConverterOutputCallback) cast is to turn the + `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) body->clazz->parse_buffer), + body); + if (status < 0) return status; + } + + MimeMultipartSigned_cleanup(obj, false); + + /* Done parsing. */ + status = body->clazz->parse_eof(body, false); + if (status < 0) return status; + status = body->clazz->parse_end(body, false); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if (body->options->decompose_file_p && + !mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) && + body->options->decompose_file_close_fn) + body->options->decompose_file_close_fn(body->options->stream_closure); +#endif /* MIME_DRAFTS */ + + /* Put out a separator after every multipart/signed object. */ + status = MimeObject_write_separator(obj); + if (status < 0) return status; + + return 0; +} diff --git a/mailnews/mime/src/mimemsig.h b/mailnews/mime/src/mimemsig.h new file mode 100644 index 000000000..2ec9d314d --- /dev/null +++ b/mailnews/mime/src/mimemsig.h @@ -0,0 +1,136 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMSIG_H_ +#define _MIMEMSIG_H_ + +#include "mimemult.h" +#include "mimepbuf.h" +#include "modmimee.h" + +/* The MimeMultipartSigned class implements the multipart/signed MIME + container, which provides a general method of associating a cryptographic + signature to an arbitrary MIME object. + + The MimeMultipartSigned class provides the following methods: + + void *crypto_init (MimeObject *multipart_object) + + This is called with the object, the object->headers of which should be + used to initialize the dexlateion engine. NULL indicates failure; + otherwise, an opaque closure object should be returned. + + int crypto_data_hash (const char *data, int32_t data_size, + void *crypto_closure) + + This is called with the raw data, for which a signature has been computed. + The crypto module should examine this, and compute a signature for it. + + int crypto_data_eof (void *crypto_closure, bool abort_p) + + This is called when no more data remains. If `abort_p' is true, then the + crypto module may choose to discard any data rather than processing it, + as we're terminating abnormally. + + int crypto_signature_init (void *crypto_closure, + MimeObject *multipart_object, + MimeHeaders *signature_hdrs) + + This is called after crypto_data_eof() and just before the first call to + crypto_signature_hash(). The crypto module may wish to do some + initialization here, or may wish to examine the actual headers of the + signature object itself. + + int crypto_signature_hash (const char *data, int32_t data_size, + void *crypto_closure) + + This is called with the raw data of the detached signature block. It will + be called after crypto_data_eof() has been called to signify the end of + the data which is signed. This data is the data of the signature itself. + + int crypto_signature_eof (void *crypto_closure, bool abort_p) + + This is called when no more signature data remains. If `abort_p' is true, + then the crypto module may choose to discard any data rather than + processing it, as we're terminating abnormally. + + char * crypto_generate_html (void *crypto_closure) + + This is called after `crypto_signature_eof' but before `crypto_free'. + The crypto module should return a newly-allocated string of HTML code + which explains the status of the dexlateion to the user (whether the + signature checks out, etc.) + + void crypto_free (void *crypto_closure) + + This will be called when we're all done, after `crypto_signature_eof' and + `crypto_emit_html'. It is intended to free any data represented by the + crypto_closure. + */ + +typedef struct MimeMultipartSignedClass MimeMultipartSignedClass; +typedef struct MimeMultipartSigned MimeMultipartSigned; + +typedef enum { + MimeMultipartSignedPreamble, + MimeMultipartSignedBodyFirstHeader, + MimeMultipartSignedBodyHeaders, + MimeMultipartSignedBodyFirstLine, + MimeMultipartSignedBodyLine, + MimeMultipartSignedSignatureHeaders, + MimeMultipartSignedSignatureFirstLine, + MimeMultipartSignedSignatureLine, + MimeMultipartSignedEpilogue +} MimeMultipartSignedParseState; + +struct MimeMultipartSignedClass { + MimeMultipartClass multipart; + + /* Callbacks used by dexlateion (really, signature verification) module. */ + void * (*crypto_init) (MimeObject *multipart_object); + + int (*crypto_data_hash) (const char *data, int32_t data_size, + void *crypto_closure); + int (*crypto_signature_hash) (const char *data, int32_t data_size, + void *crypto_closure); + + int (*crypto_data_eof) (void *crypto_closure, bool abort_p); + int (*crypto_signature_eof) (void *crypto_closure, bool abort_p); + + int (*crypto_signature_init) (void *crypto_closure, + MimeObject *multipart_object, + MimeHeaders *signature_hdrs); + + char * (*crypto_generate_html) (void *crypto_closure); + + void (*crypto_notify_suppressed_child)(void *crypto_closure); + + void (*crypto_free) (void *crypto_closure); +}; + +extern "C" MimeMultipartSignedClass mimeMultipartSignedClass; + +struct MimeMultipartSigned { + MimeMultipart multipart; + MimeMultipartSignedParseState state; /* State of parser */ + + void *crypto_closure; /* Opaque data used by signature + verification module. */ + + MimeHeaders *body_hdrs; /* The headers of the signed object. */ + MimeHeaders *sig_hdrs; /* The headers of the signature. */ + + MimePartBufferData *part_buffer; /* The buffered body of the signed + object (see mimepbuf.h) */ + + MimeDecoderData *sig_decoder_data; /* The signature is probably base64 + encoded; this is the decoder used + to get raw bits out of it. */ +}; + +#define MimeMultipartSignedClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMSIG_H_ */ diff --git a/mailnews/mime/src/mimemult.cpp b/mailnews/mime/src/mimemult.cpp new file mode 100644 index 000000000..4695ba991 --- /dev/null +++ b/mailnews/mime/src/mimemult.cpp @@ -0,0 +1,753 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "mimemult.h" +#include "mimemoz2.h" +#include "mimeeobj.h" + +#include "prlog.h" +#include "prmem.h" +#include "plstr.h" +#include "prio.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include <ctype.h> + +#ifdef XP_MACOSX + extern MimeObjectClass mimeMultipartAppleDoubleClass; +#endif + +#define MIME_SUPERCLASS mimeContainerClass +MimeDefClass(MimeMultipart, MimeMultipartClass, + mimeMultipartClass, &MIME_SUPERCLASS); + +static int MimeMultipart_initialize (MimeObject *); +static void MimeMultipart_finalize (MimeObject *); +static int MimeMultipart_parse_line (const char *line, int32_t length, MimeObject *); +static int MimeMultipart_parse_eof (MimeObject *object, bool abort_p); + +static MimeMultipartBoundaryType MimeMultipart_check_boundary(MimeObject *, + const char *, + int32_t); +static int MimeMultipart_create_child(MimeObject *); +static bool MimeMultipart_output_child_p(MimeObject *, MimeObject *); +static int MimeMultipart_parse_child_line (MimeObject *, const char *, int32_t, + bool); +static int MimeMultipart_close_child(MimeObject *); + +extern "C" MimeObjectClass mimeMultipartAlternativeClass; +extern "C" MimeObjectClass mimeMultipartRelatedClass; +extern "C" MimeObjectClass mimeMultipartSignedClass; +extern "C" MimeObjectClass mimeInlineTextVCardClass; +extern "C" MimeExternalObjectClass mimeExternalObjectClass; +extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass; + +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeMultipart_debug_print (MimeObject *, PRFileDesc *, int32_t); +#endif + +static int +MimeMultipartClassInitialize(MimeMultipartClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeMultipart_initialize; + oclass->finalize = MimeMultipart_finalize; + oclass->parse_line = MimeMultipart_parse_line; + oclass->parse_eof = MimeMultipart_parse_eof; + + mclass->check_boundary = MimeMultipart_check_boundary; + mclass->create_child = MimeMultipart_create_child; + mclass->output_child_p = MimeMultipart_output_child_p; + mclass->parse_child_line = MimeMultipart_parse_child_line; + mclass->close_child = MimeMultipart_close_child; + +#if defined(DEBUG) && defined(XP_UNIX) + oclass->debug_print = MimeMultipart_debug_print; +#endif + + return 0; +} + + +static int +MimeMultipart_initialize (MimeObject *object) +{ + MimeMultipart *mult = (MimeMultipart *) object; + char *ct; + + /* This is an abstract class; it shouldn't be directly instantiated. */ + PR_ASSERT(object->clazz != (MimeObjectClass *) &mimeMultipartClass); + + ct = MimeHeaders_get (object->headers, HEADER_CONTENT_TYPE, false, false); + mult->boundary = (ct + ? MimeHeaders_get_parameter (ct, HEADER_PARM_BOUNDARY, NULL, NULL) + : 0); + PR_FREEIF(ct); + mult->state = MimeMultipartPreamble; + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + + +static void +MimeMultipart_finalize (MimeObject *object) +{ + MimeMultipart *mult = (MimeMultipart *) object; + + object->clazz->parse_eof(object, false); + + PR_FREEIF(mult->boundary); + if (mult->hdrs) + MimeHeaders_free(mult->hdrs); + mult->hdrs = 0; + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +int MimeWriteAString(MimeObject *obj, const nsACString &string) +{ + const nsCString &flatString = PromiseFlatCString(string); + return MimeObject_write(obj, flatString.get(), flatString.Length(), true); +} + +static int +MimeMultipart_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeContainer *container = (MimeContainer*) obj; + int status = 0; + MimeMultipartBoundaryType boundary; + + NS_ASSERTION(line && *line, "empty line in multipart parse_line"); + if (!line || !*line) return -1; + + NS_ASSERTION(!obj->closed_p, "obj shouldn't already be closed"); + if (obj->closed_p) return -1; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + obj->options->output_fn + && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach) + return MimeObject_write(obj, line, length, true); + + if (mult->state == MimeMultipartEpilogue) /* already done */ + boundary = MimeMultipartBoundaryTypeNone; + else + boundary = ((MimeMultipartClass *)obj->clazz)->check_boundary(obj, line, + length); + + if (boundary == MimeMultipartBoundaryTypeTerminator || + boundary == MimeMultipartBoundaryTypeSeparator) + { + /* Match! Close the currently-open part, move on to the next + state, and discard this line. + */ + bool endOfPart = (mult->state != MimeMultipartPreamble); + if (endOfPart) + status = ((MimeMultipartClass *)obj->clazz)->close_child(obj); + if (status < 0) return status; + + if (boundary == MimeMultipartBoundaryTypeTerminator) + mult->state = MimeMultipartEpilogue; + else + { + mult->state = MimeMultipartHeaders; + + /* Reset the header parser for this upcoming part. */ + NS_ASSERTION(!mult->hdrs, "mult->hdrs should be null here"); + if (mult->hdrs) + MimeHeaders_free(mult->hdrs); + mult->hdrs = MimeHeaders_new(); + if (!mult->hdrs) + return MIME_OUT_OF_MEMORY; + if (obj->options && obj->options->state && + obj->options->state->partsToStrip.Length() > 0) + { + nsAutoCString newPart(mime_part_address(obj)); + newPart.Append('.'); + newPart.AppendInt(container->nchildren + 1); + obj->options->state->strippingPart = false; + // check if this is a sub-part of a part we're stripping. + for (uint32_t partIndex = 0; partIndex < obj->options->state->partsToStrip.Length(); partIndex++) + { + nsCString &curPartToStrip = obj->options->state->partsToStrip[partIndex]; + if (newPart.Find(curPartToStrip) == 0 && (newPart.Length() == curPartToStrip.Length() || newPart.CharAt(curPartToStrip.Length()) == '.')) + { + obj->options->state->strippingPart = true; + if (partIndex < obj->options->state->detachToFiles.Length()) + obj->options->state->detachedFilePath = obj->options->state->detachToFiles[partIndex]; + break; + } + } + } + } + + // if stripping out attachments, write the boundary line. Otherwise, return + // to ignore it. + if (obj->options && obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) + { + // Because MimeMultipart_parse_child_line strips out the + // the CRLF of the last line before the end of a part, we need to add that + // back in here. + if (endOfPart) + MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK)); + + status = MimeObject_write(obj, line, length, true); + } + return 0; + } + + /* Otherwise, this isn't a boundary string. So do whatever it is we + should do with this line (parse it as a header, feed it to the + child part, ignore it, etc.) */ + + switch (mult->state) + { + case MimeMultipartPreamble: + case MimeMultipartEpilogue: + /* Ignore this line. */ + break; + + case MimeMultipartHeaders: + /* Parse this line as a header for the sub-part. */ + { + status = MimeHeaders_parse_line(line, length, mult->hdrs); + bool stripping = false; + + if (status < 0) return status; + + // If this line is blank, we're now done parsing headers, and should + // now examine the content-type to create this "body" part. + // + if (*line == '\r' || *line == '\n') + { + if (obj->options && obj->options->state && + obj->options->state->strippingPart) + { + stripping = true; + bool detachingPart = obj->options->state->detachedFilePath.Length() > 0; + + nsAutoCString fileName; + fileName.Adopt(MimeHeaders_get_name(mult->hdrs, obj->options)); + if (detachingPart) + { + char *contentType = MimeHeaders_get(mult->hdrs, "Content-Type", false, false); + if (contentType) + { + MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Type: ")); + MimeWriteAString(obj, nsDependentCString(contentType)); + PR_Free(contentType); + } + MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: attachment; filename=\"")); + MimeWriteAString(obj, fileName); + MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("X-Mozilla-External-Attachment-URL: ")); + MimeWriteAString(obj, obj->options->state->detachedFilePath); + MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("X-Mozilla-Altered: AttachmentDetached; date=\"")); + } + else + { + nsAutoCString header("Content-Type: text/x-moz-deleted; name=\"Deleted: "); + header.Append(fileName); + status = MimeWriteAString(obj, header); + if (status < 0) + return status; + status = MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK "Content-Transfer-Encoding: 8bit" MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: inline; filename=\"Deleted: ")); + MimeWriteAString(obj, fileName); + MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK "X-Mozilla-Altered: AttachmentDeleted; date=\"")); + } + nsCString result; + char timeBuffer[128]; + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); + PR_FormatTimeUSEnglish(timeBuffer, sizeof(timeBuffer), + "%a %b %d %H:%M:%S %Y", + &now); + MimeWriteAString(obj, nsDependentCString(timeBuffer)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK "You deleted an attachment from this message. The original MIME headers for the attachment were:" MSG_LINEBREAK)); + MimeHeaders_write_raw_headers(mult->hdrs, obj->options, false); + } + int32_t old_nchildren = container->nchildren; + status = ((MimeMultipartClass *) obj->clazz)->create_child(obj); + if (status < 0) return status; + NS_ASSERTION(mult->state != MimeMultipartHeaders, + "mult->state shouldn't be MimeMultipartHeaders"); + + if (!stripping && container->nchildren > old_nchildren && obj->options && + !mime_typep(obj, (MimeObjectClass*)&mimeMultipartAlternativeClass)) { + // Notify emitter about content type and part path. + MimeObject *kid = container->children[container->nchildren-1]; + MimeMultipart_notify_emitter(kid); + } + } + break; + } + + case MimeMultipartPartFirstLine: + /* Hand this line off to the sub-part. */ + status = (((MimeMultipartClass *) obj->clazz)->parse_child_line(obj, + line, length, true)); + if (status < 0) return status; + mult->state = MimeMultipartPartLine; + break; + + case MimeMultipartPartLine: + /* Hand this line off to the sub-part. */ + status = (((MimeMultipartClass *) obj->clazz)->parse_child_line(obj, + line, length, false)); + if (status < 0) return status; + break; + + default: + NS_ERROR("unexpected state in parse line"); + return -1; + } + + if (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageAttach && + (!(obj->options->state && obj->options->state->strippingPart) && + mult->state != MimeMultipartPartLine)) + return MimeObject_write(obj, line, length, false); + return 0; +} + +void MimeMultipart_notify_emitter(MimeObject *obj) +{ + char *ct = nullptr; + + NS_ASSERTION(obj->options, "MimeMultipart_notify_emitter called with null options"); + if (! obj->options) + return; + + ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, + false, false); + if (obj->options->notify_nested_bodies) { + mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE, + ct ? ct : TEXT_PLAIN); + char *part_path = mime_part_address(obj); + if (part_path) { + mimeEmitterAddHeaderField(obj->options, "x-jsemitter-part-path", + part_path); + PR_Free(part_path); + } + } + + // Examine the headers and see if there is a special charset + // (i.e. non US-ASCII) for this message. If so, we need to + // tell the emitter that this is the case for use in any + // possible reply or forward operation. + if (ct && (obj->options->notify_nested_bodies || + MimeObjectIsMessageBody(obj))) { + char *cset = MimeHeaders_get_parameter(ct, "charset", NULL, NULL); + if (cset) { + mimeEmitterUpdateCharacterSet(obj->options, cset); + if (!obj->options->override_charset) + // Also set this charset to msgWindow + SetMailCharacterSetToMsgWindow(obj, cset); + PR_Free(cset); + } + } + + PR_FREEIF(ct); +} + +static MimeMultipartBoundaryType +MimeMultipart_check_boundary(MimeObject *obj, const char *line, int32_t length) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + int32_t blen; + bool term_p; + + if (!mult->boundary || + line[0] != '-' || + line[1] != '-') + return MimeMultipartBoundaryTypeNone; + + /* This is a candidate line to be a boundary. Check it out... */ + blen = strlen(mult->boundary); + term_p = false; + + /* strip trailing whitespace (including the newline.) */ + while(length > 2 && IS_SPACE(line[length-1])) + length--; + + /* Could this be a terminating boundary? */ + if (length == blen + 4 && + line[length-1] == '-' && + line[length-2] == '-') + { + term_p = true; + } + + //looks like we have a separator but first, we need to check it's not for one of the part's children. + MimeContainer *cont = (MimeContainer *) obj; + if (cont->nchildren > 0) + { + MimeObject *kid = cont->children[cont->nchildren-1]; + if (kid) + if (mime_typep(kid, (MimeObjectClass*) &mimeMultipartClass)) + { + //Don't ask the kid to check the boundary if it has already detected a Teminator + MimeMultipart *mult = (MimeMultipart *) kid; + if (mult->state != MimeMultipartEpilogue) + if (MimeMultipart_check_boundary(kid, line, length) != MimeMultipartBoundaryTypeNone) + return MimeMultipartBoundaryTypeNone; + } + } + + if (term_p) + length -= 2; + + if (blen == length-2 && !strncmp(line+2, mult->boundary, length-2)) + return (term_p + ? MimeMultipartBoundaryTypeTerminator + : MimeMultipartBoundaryTypeSeparator); + else + return MimeMultipartBoundaryTypeNone; +} + + +static int +MimeMultipart_create_child(MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + int status; + char *ct = (mult->hdrs + ? MimeHeaders_get (mult->hdrs, HEADER_CONTENT_TYPE, + true, false) + : 0); + const char *dct = (((MimeMultipartClass *) obj->clazz)->default_part_type); + MimeObject *body = NULL; + + mult->state = MimeMultipartPartFirstLine; + if (obj->options) + obj->options->is_child = true; + + /* Don't pass in NULL as the content-type (this means that the + auto-uudecode-hack won't ever be done for subparts of a + multipart, but only for untyped children of message/rfc822. + */ + body = mime_create(((ct && *ct) ? ct : (dct ? dct: TEXT_PLAIN)), + mult->hdrs, obj->options); + PR_FREEIF(ct); + if (!body) return MIME_OUT_OF_MEMORY; + status = ((MimeContainerClass *) obj->clazz)->add_child(obj, body); + if (status < 0) + { + mime_free(body); + return status; + } + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->is_multipart_msg && + obj->options->decompose_file_init_fn ) + { + if ( !mime_typep(obj,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(obj,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(obj,(MimeObjectClass*)&mimeMultipartSignedClass) && +#ifdef MIME_DETAIL_CHECK + !mime_typep(body, (MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(body, (MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(body,(MimeObjectClass*)&mimeMultipartSignedClass) +#else + /* bug 21869 -- due to the fact that we are not generating the + correct mime class object for content-typ multipart/signed part + the above check failed. to solve the problem in general and not + to cause early temination when parsing message for opening as + draft we can simply make sure that the child is not a multipart + mime object. this way we could have a proper decomposing message + part functions set correctly */ + !mime_typep(body, (MimeObjectClass*) &mimeMultipartClass) +#endif + && !((mime_typep(body, (MimeObjectClass *)&mimeExternalObjectClass) || + mime_typep(body, (MimeObjectClass *)&mimeSuppressedCryptoClass)) && + !strcmp(body->content_type, "text/x-vcard")) + ) + { + status = obj->options->decompose_file_init_fn ( obj->options->stream_closure, mult->hdrs ); + if (status < 0) return status; + } + } +#endif /* MIME_DRAFTS */ + + + /* Now that we've added this new object to our list of children, + start its parser going (if we want to display it.) + */ + body->output_p = (((MimeMultipartClass *) obj->clazz)->output_child_p(obj, body)); + if (body->output_p) + { + status = body->clazz->parse_begin(body); + +#ifdef XP_MACOSX + /* if we are saving an apple double attachment, we need to set correctly the conten type of the channel */ + if (mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + { + mime_stream_data *msd = (mime_stream_data *)body->options->stream_closure; + if (!body->options->write_html_p && body->content_type && !PL_strcasecmp(body->content_type, APPLICATION_APPLEFILE)) + { + if (msd && msd->channel) + msd->channel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_APPLEFILE)); + } + } +#endif + + if (status < 0) return status; + } + + return 0; +} + + +static bool +MimeMultipart_output_child_p(MimeObject *obj, MimeObject *child) +{ + /* We don't output a child if we're stripping it. */ + if (obj->options && obj->options->state && obj->options->state->strippingPart) + return false; + /* if we are saving an apple double attachment, ignore the appledouble wrapper part */ + return (obj->options && obj->options->write_html_p) || + PL_strcasecmp(child->content_type, MULTIPART_APPLEDOUBLE); +} + + + +static int +MimeMultipart_close_child(MimeObject *object) +{ + MimeMultipart *mult = (MimeMultipart *) object; + MimeContainer *cont = (MimeContainer *) object; + + if (!mult->hdrs) + return 0; + + MimeHeaders_free(mult->hdrs); + mult->hdrs = 0; + + NS_ASSERTION(cont->nchildren > 0, "badly formed mime message"); + if (cont->nchildren > 0) + { + MimeObject *kid = cont->children[cont->nchildren-1]; + // If we have a child and it has not already been closed, process it. + // The kid would be already be closed if we encounter a multipart section + // that did not have a fully delineated header block. No header block means + // no creation of a new child, but the termination case still happens and + // we still end up here. Obviously, we don't want to close the child a + // second time and the best thing we can do is nothing. + if (kid && !kid->closed_p) + { + int status; + status = kid->clazz->parse_eof(kid, false); + if (status < 0) return status; + status = kid->clazz->parse_end(kid, false); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if ( object->options && + object->options->decompose_file_p && + object->options->is_multipart_msg && + object->options->decompose_file_close_fn ) + { + if ( !mime_typep(object,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(object,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(object,(MimeObjectClass*)&mimeMultipartSignedClass) && +#ifdef MIME_DETAIL_CHECK + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartSignedClass) +#else + /* bug 21869 -- due to the fact that we are not generating the + correct mime class object for content-typ multipart/signed part + the above check failed. to solve the problem in general and not + to cause early temination when parsing message for opening as + draft we can simply make sure that the child is not a multipart + mime object. this way we could have a proper decomposing message + part functions set correctly */ + !mime_typep(kid,(MimeObjectClass*) &mimeMultipartClass) +#endif + && !((mime_typep(kid, (MimeObjectClass *)&mimeExternalObjectClass) || + mime_typep(kid, (MimeObjectClass *)&mimeSuppressedCryptoClass)) && + !strcmp(kid->content_type, "text/x-vcard")) + ) + { + status = object->options->decompose_file_close_fn ( object->options->stream_closure ); + if (status < 0) return status; + } + } +#endif /* MIME_DRAFTS */ + + } + } + return 0; +} + + +static int +MimeMultipart_parse_child_line (MimeObject *obj, const char *line, int32_t length, + bool first_line_p) +{ + MimeContainer *cont = (MimeContainer *) obj; + int status; + MimeObject *kid; + + PR_ASSERT(cont->nchildren > 0); + if (cont->nchildren <= 0) + return -1; + + kid = cont->children[cont->nchildren-1]; + PR_ASSERT(kid); + if (!kid) return -1; + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->is_multipart_msg && + obj->options->decompose_file_output_fn ) + { + if (!mime_typep(obj,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(obj,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(obj,(MimeObjectClass*)&mimeMultipartSignedClass) && +#ifdef MIME_DETAIL_CHECK + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartSignedClass) +#else + /* bug 21869 -- due to the fact that we are not generating the + correct mime class object for content-typ multipart/signed part + the above check failed. to solve the problem in general and not + to cause early temination when parsing message for opening as + draft we can simply make sure that the child is not a multipart + mime object. this way we could have a proper decomposing message + part functions set correctly */ + !mime_typep(kid, (MimeObjectClass*) &mimeMultipartClass) +#endif + && !(mime_typep(kid, (MimeObjectClass*)&mimeExternalObjectClass) && !strcmp(kid->content_type, "text/x-vcard")) + ) + return obj->options->decompose_file_output_fn (line, length, obj->options->stream_closure); + } +#endif /* MIME_DRAFTS */ + + /* The newline issues here are tricky, since both the newlines before + and after the boundary string are to be considered part of the + boundary: this is so that a part can be specified such that it + does not end in a trailing newline. + + To implement this, we send a newline *before* each line instead + of after, except for the first line, which is not preceeded by a + newline. + */ + + /* Remove the trailing newline... */ + if (length > 0 && line[length-1] == '\n') length--; + if (length > 0 && line[length-1] == '\r') length--; + + if (!first_line_p) + { + /* Push out a preceeding newline... */ + char nl[] = MSG_LINEBREAK; + status = kid->clazz->parse_buffer (nl, MSG_LINEBREAK_LEN, kid); + if (status < 0) return status; + } + + /* Now push out the line sans trailing newline. */ + return kid->clazz->parse_buffer (line, length, kid); +} + + +static int +MimeMultipart_parse_eof (MimeObject *obj, bool abort_p) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeContainer *cont = (MimeContainer *) obj; + + if (obj->closed_p) return 0; + + /* Push out the last trailing line if there's one in the buffer. If + this happens, this object does not end in a trailing newline (and + the parse_line method will be called with a string with no trailing + newline, which isn't the usual case.) + */ + if (!abort_p && obj->ibuffer_fp > 0) + { + /* There is leftover data without a terminating newline. */ + int status = obj->clazz->parse_line(obj->ibuffer, obj->ibuffer_fp,obj); + obj->ibuffer_fp = 0; + if (status < 0) + { + obj->closed_p = true; + return status; + } + } + + /* Now call parse_eof for our active child, if there is one. + */ + if (cont->nchildren > 0 && + (mult->state == MimeMultipartPartLine || + mult->state == MimeMultipartPartFirstLine)) + { + MimeObject *kid = cont->children[cont->nchildren-1]; + NS_ASSERTION(kid, "not expecting null kid"); + if (kid) + { + int status = kid->clazz->parse_eof(kid, abort_p); + if (status < 0) return status; + } + } + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); +} + + +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeMultipart_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + /* MimeMultipart *mult = (MimeMultipart *) obj; */ + MimeContainer *cont = (MimeContainer *) obj; + char *addr = mime_part_address(obj); + int i; + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); +/** + fprintf(stream, "<%s %s (%d kid%s) boundary=%s 0x%08X>\n", + obj->clazz->class_name, + addr ? addr : "???", + cont->nchildren, (cont->nchildren == 1 ? "" : "s"), + (mult->boundary ? mult->boundary : "(none)"), + (uint32_t) mult); +**/ + PR_FREEIF(addr); + +/* + if (cont->nchildren > 0) + fprintf(stream, "\n"); + */ + + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *kid = cont->children[i]; + int status = kid->clazz->debug_print (kid, stream, depth+1); + if (status < 0) return status; + } + +/* + if (cont->nchildren > 0) + fprintf(stream, "\n"); + */ + + return 0; +} +#endif diff --git a/mailnews/mime/src/mimemult.h b/mailnews/mime/src/mimemult.h new file mode 100644 index 000000000..877afb4d9 --- /dev/null +++ b/mailnews/mime/src/mimemult.h @@ -0,0 +1,102 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMULT_H_ +#define _MIMEMULT_H_ + +#include "mimecont.h" + +/* The MimeMultipart class class implements the objects representing all of + the "multipart/" MIME types. In addition to the methods inherited from + MimeContainer, it provides the following methods and class variables: + + int create_child (MimeObject *obj) + + When it has been determined that a new sub-part should be created, + this method is called to do that. The default value for this method + does it in the usual multipart/mixed way. The headers of the object- + to-be-created may be found in the `hdrs' slot of the `MimeMultipart' + object. + + bool output_child_p (MimeObject *parent, MimeObject *child) + + Whether this child should be output. Default method always says `yes'. + + int parse_child_line (MimeObject *obj, const char *line, int32_t length, + bool first_line_p) + + When we have a line which should be handed off to the currently-active + child object, this method is called to do that. The `first_line_p' + variable will be true only for the very first line handed off to this + sub-part. The default method simply passes the line to the most- + recently-added child object. + + int close_child (MimeObject *self) + + When we reach the end of a sub-part (a separator line) this method is + called to shut down the currently-active child. The default method + simply calls `parse_eof' on the most-recently-added child object. + + MimeMultipartBoundaryType check_boundary (MimeObject *obj, + const char *line, int32_t length) + + This method is used to examine a line and determine whether it is a + part boundary, and if so, what kind. It should return a member of + the MimeMultipartBoundaryType describing the line. + + const char *default_part_type + + This is the type which should be assumed for sub-parts which have + no explicit type specified. The default is "text/plain", but the + "multipart/digest" subclass overrides this to "message/rfc822". + */ + +typedef struct MimeMultipartClass MimeMultipartClass; +typedef struct MimeMultipart MimeMultipart; + +typedef enum { + MimeMultipartPreamble, + MimeMultipartHeaders, + MimeMultipartPartFirstLine, + MimeMultipartPartLine, + MimeMultipartEpilogue +} MimeMultipartParseState; + +typedef enum { + MimeMultipartBoundaryTypeNone, + MimeMultipartBoundaryTypeSeparator, + MimeMultipartBoundaryTypeTerminator +} MimeMultipartBoundaryType; + + +struct MimeMultipartClass { + MimeContainerClass container; + const char *default_part_type; + + int (*create_child) (MimeObject *); + bool (*output_child_p) (MimeObject *self, MimeObject *child); + int (*close_child) (MimeObject *); + int (*parse_child_line) (MimeObject *, const char *line, int32_t length, + bool first_line_p); + MimeMultipartBoundaryType (*check_boundary) (MimeObject *, const char *line, + int32_t length); +}; + +extern MimeMultipartClass mimeMultipartClass; + +struct MimeMultipart { + MimeContainer container; /* superclass variables */ + char *boundary; /* Inter-part delimiter string */ + MimeHeaders *hdrs; /* headers of the part currently + being parsed, if any */ + MimeMultipartParseState state; /* State of parser */ +}; + +extern void MimeMultipart_notify_emitter(MimeObject *); + +#define MimeMultipartClassInitializer(ITYPE,CSUPER) \ + { MimeContainerClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMULT_H_ */ diff --git a/mailnews/mime/src/mimeobj.cpp b/mailnews/mime/src/mimeobj.cpp new file mode 100644 index 000000000..26eb618ce --- /dev/null +++ b/mailnews/mime/src/mimeobj.cpp @@ -0,0 +1,327 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#include "mimeobj.h" +#include "prmem.h" +#include "plstr.h" +#include "prio.h" +#include "mimebuf.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "nsMimeStringResources.h" +#include "nsMsgUtils.h" +#include "mimemsg.h" +#include "mimemapl.h" + +/* Way to destroy any notions of modularity or class hierarchy, Terry! */ +# include "mimetpla.h" +# include "mimethtm.h" +# include "mimecont.h" + +MimeDefClass (MimeObject, MimeObjectClass, mimeObjectClass, NULL); + +static int MimeObject_initialize (MimeObject *); +static void MimeObject_finalize (MimeObject *); +static int MimeObject_parse_begin (MimeObject *); +static int MimeObject_parse_buffer (const char *, int32_t, MimeObject *); +static int MimeObject_parse_line (const char *, int32_t, MimeObject *); +static int MimeObject_parse_eof (MimeObject *, bool); +static int MimeObject_parse_end (MimeObject *, bool); +static bool MimeObject_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeObject_debug_print (MimeObject *, PRFileDesc *, int32_t depth); +#endif + +static int +MimeObjectClassInitialize(MimeObjectClass *clazz) +{ + NS_ASSERTION(!clazz->class_initialized, "class shouldn't already be initialized"); + clazz->initialize = MimeObject_initialize; + clazz->finalize = MimeObject_finalize; + clazz->parse_begin = MimeObject_parse_begin; + clazz->parse_buffer = MimeObject_parse_buffer; + clazz->parse_line = MimeObject_parse_line; + clazz->parse_eof = MimeObject_parse_eof; + clazz->parse_end = MimeObject_parse_end; + clazz->displayable_inline_p = MimeObject_displayable_inline_p; + +#if defined(DEBUG) && defined(XP_UNIX) + clazz->debug_print = MimeObject_debug_print; +#endif + return 0; +} + +static int +MimeObject_initialize (MimeObject *obj) +{ + /* This is an abstract class; it shouldn't be directly instantiated. */ + NS_ASSERTION(obj->clazz != &mimeObjectClass, "should directly instantiate abstract class"); + + /* Set up the content-type and encoding. */ + if (!obj->content_type && obj->headers) + obj->content_type = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, + true, false); + if (!obj->encoding && obj->headers) + obj->encoding = MimeHeaders_get (obj->headers, + HEADER_CONTENT_TRANSFER_ENCODING, + true, false); + + /* Special case to normalize some types and encodings to a canonical form. + (These are nonstandard types/encodings which have been seen to appear in + multiple forms; we normalize them so that things like looking up icons + and extensions has consistent behavior for the receiver, regardless of + the "alias" type that the sender used.) + */ + if (!obj->content_type || !*(obj->content_type)) + ; + else if (!PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE2) || + !PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE3) || + !PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE4)) + { + PR_Free(obj->content_type); + obj->content_type = strdup(APPLICATION_UUENCODE); + } + else if (!PL_strcasecmp(obj->content_type, IMAGE_XBM2) || + !PL_strcasecmp(obj->content_type, IMAGE_XBM3)) + { + PR_Free(obj->content_type); + obj->content_type = strdup(IMAGE_XBM); + } + else { + // MIME-types are case-insenitive, but let's make it lower case internally + // to avoid some hassle later down the road. + nsAutoCString lowerCaseContentType; + ToLowerCase(nsDependentCString(obj->content_type), lowerCaseContentType); + PR_Free(obj->content_type); + obj->content_type = ToNewCString(lowerCaseContentType); + } + + if (!obj->encoding) + ; + else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4)) + { + PR_Free(obj->encoding); + obj->encoding = strdup(ENCODING_UUENCODE); + } + else if (!PL_strcasecmp(obj->encoding, ENCODING_COMPRESS2)) + { + PR_Free(obj->encoding); + obj->encoding = strdup(ENCODING_COMPRESS); + } + else if (!PL_strcasecmp(obj->encoding, ENCODING_GZIP2)) + { + PR_Free(obj->encoding); + obj->encoding = strdup(ENCODING_GZIP); + } + + return 0; +} + +static void +MimeObject_finalize (MimeObject *obj) +{ + obj->clazz->parse_eof (obj, false); + obj->clazz->parse_end (obj, false); + + if (obj->headers) + { + MimeHeaders_free(obj->headers); + obj->headers = 0; + } + + /* Should have been freed by parse_eof, but just in case... */ + NS_ASSERTION(!obj->ibuffer, "buffer not freed"); + NS_ASSERTION(!obj->obuffer, "buffer not freed"); + PR_FREEIF (obj->ibuffer); + PR_FREEIF (obj->obuffer); + + PR_FREEIF(obj->content_type); + PR_FREEIF(obj->encoding); + + if (obj->options && obj->options->state) + { + delete obj->options->state; + obj->options->state = nullptr; + } +} + +static int +MimeObject_parse_begin (MimeObject *obj) +{ + NS_ASSERTION (!obj->closed_p, "object shouldn't be already closed"); + + /* If we haven't set up the state object yet, then this should be + the outermost object... */ + if (obj->options && !obj->options->state) + { + NS_ASSERTION(!obj->headers, "headers should be null"); /* should be the outermost object. */ + + obj->options->state = new MimeParseStateObject; + if (!obj->options->state) return MIME_OUT_OF_MEMORY; + obj->options->state->root = obj; + obj->options->state->separator_suppressed_p = true; /* no first sep */ + const char *delParts = PL_strcasestr(obj->options->url, "&del="); + const char *detachLocations = PL_strcasestr(obj->options->url, "&detachTo="); + if (delParts) + { + const char *delEnd = PL_strcasestr(delParts + 1, "&"); + if (!delEnd) + delEnd = delParts + strlen(delParts); + ParseString(Substring(delParts + 5, delEnd), ',', obj->options->state->partsToStrip); + } + if (detachLocations) + { + detachLocations += 10; // advance past "&detachTo=" + ParseString(nsDependentCString(detachLocations), ',', obj->options->state->detachToFiles); + } + } + + /* Decide whether this object should be output or not... */ + if (!obj->options || obj->options->no_output_p || !obj->options->output_fn + /* if we are decomposing the message in files and processing a multipart object, + we must not output it without parsing it first */ + || (obj->options->decompose_file_p && obj->options->decompose_file_output_fn && + mime_typep(obj, (MimeObjectClass*) &mimeMultipartClass)) + ) + obj->output_p = false; + else if (!obj->options->part_to_load) + obj->output_p = true; + else + { + char *id = mime_part_address(obj); + if (!id) return MIME_OUT_OF_MEMORY; + + // We need to check if a part is the subpart of the part to load. + // If so and this is a raw or body display output operation, then + // we should mark the part for subsequent output. + + // First, check for an exact match + obj->output_p = !strcmp(id, obj->options->part_to_load); + if (!obj->output_p && (obj->options->format_out == nsMimeOutput::nsMimeMessageRaw || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay || + obj->options->format_out == nsMimeOutput::nsMimeMessageAttach)) + { + // Then, check for subpart + unsigned int partlen = strlen(obj->options->part_to_load); + obj->output_p = (strlen(id) >= partlen + 2) && (id[partlen] == '.') && + !strncmp(id, obj->options->part_to_load, partlen); + } + + PR_Free(id); + } + + // If we've decided not to output this part, we also shouldn't be showing it + // as an attachment. + obj->dontShowAsAttachment = !obj->output_p; + + return 0; +} + +static int +MimeObject_parse_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + NS_ASSERTION(!obj->closed_p, "object shouldn't be closed"); + if (obj->closed_p) return -1; + + return mime_LineBuffer (buffer, size, + &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp, + true, + ((int (*) (char *, int32_t, void *)) + /* This cast is to turn void into MimeObject */ + obj->clazz->parse_line), + obj); +} + +static int +MimeObject_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("shouldn't call this method"); + return -1; +} + +static int +MimeObject_parse_eof (MimeObject *obj, bool abort_p) +{ + if (obj->closed_p) return 0; + NS_ASSERTION(!obj->parsed_p, "obj already parsed"); + + /* If there is still data in the ibuffer, that means that the last line of + this part didn't end in a newline; so push it out anyway (this means that + the parse_line method will be called with a string with no trailing + newline, which isn't the usual case.) + */ + if (!abort_p && + obj->ibuffer_fp > 0) + { + int status = obj->clazz->parse_line (obj->ibuffer, obj->ibuffer_fp, obj); + obj->ibuffer_fp = 0; + if (status < 0) + { + obj->closed_p = true; + return status; + } + } + + obj->closed_p = true; + return 0; +} + +static int +MimeObject_parse_end (MimeObject *obj, bool abort_p) +{ + if (obj->parsed_p) + { + NS_ASSERTION(obj->closed_p, "object should be closed"); + return 0; + } + + /* We won't be needing these buffers any more; nuke 'em. */ + PR_FREEIF(obj->ibuffer); + obj->ibuffer_fp = 0; + obj->ibuffer_size = 0; + PR_FREEIF(obj->obuffer); + obj->obuffer_fp = 0; + obj->obuffer_size = 0; + + obj->parsed_p = true; + return 0; +} + +static bool +MimeObject_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs) +{ + NS_ERROR("shouldn't call this method"); + return false; +} + +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeObject_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + int i; + char *addr = mime_part_address(obj); + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); +/* + fprintf(stream, "<%s %s 0x%08X>\n", obj->clazz->class_name, + addr ? addr : "???", + (uint32_t) obj); +*/ + PR_FREEIF(addr); + return 0; +} +#endif diff --git a/mailnews/mime/src/mimeobj.h b/mailnews/mime/src/mimeobj.h new file mode 100644 index 000000000..7ae3eda68 --- /dev/null +++ b/mailnews/mime/src/mimeobj.h @@ -0,0 +1,186 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEOBJ_H_ +#define _MIMEOBJ_H_ + +#include "mimei.h" +#include "prio.h" +/* MimeObject is the base-class for the objects representing all other + MIME types. It provides several methods: + + int initialize (MimeObject *obj) + + This is called from mime_new() when a new instance is allocated. + Subclasses should do whatever setup is necessary from this method, + and should call the superclass's initialize method, unless there's + a specific reason not to. + + void finalize (MimeObject *obj) + + This is called from mime_free() and should free all data associated + with the object. If the object points to other MIME objects, they + should be finalized as well (by calling mime_free(), not by calling + their finalize() methods directly.) + + int parse_buffer (const char *buf, int32_t size, MimeObject *obj) + + This is the method by which you feed arbitrary data into the parser + for this object. Most subclasses will probably inherit this method + from the MimeObject base-class, which line-buffers the data and then + hands it off to the parse_line() method. + + If this object uses a Content-Transfer-Encoding (base64, qp, uue) + then the data may be decoded by parse_buffer() before parse_line() + is called. (The MimeLeaf class provides this functionality.) + + int parse_begin (MimeObject *obj) + Called after `init' but before `parse_line' or `parse_buffer'. + Can be used to initialize various parsing machinery. + + int parse_line (const char *line, int32_t length, MimeObject *obj) + + This method is called (by parse_buffer()) for each complete line of + data handed to the parser, and is the method which most subclasses + will override to implement their parsers. + + When handing data off to a MIME object for parsing, one should always + call the parse_buffer() method, and not call the parse_line() method + directly, since the parse_buffer() method may do other transformations + on the data (like base64 decoding.) + + One should generally not call parse_line() directly, since that could + bypass decoding. One should call parse_buffer() instead. + + int parse_eof (MimeObject *obj, bool abort_p) + + This is called when there is no more data to be handed to the object: + when the parent object is done feeding data to an object being parsed. + Implementors of this method should be sure to also call the parse_eof() + methods of any sub-objects to which they have pointers. + + This is also called by the finalize() method, just before object + destruction, if it has not already been called. + + The `closed_p' instance variable is used to prevent multiple calls to + `parse_eof'. + + int parse_end (MimeObject *obj) + Called after `parse_eof' but before `finalize'. + This can be used to free up any memory no longer needed now that parsing + is done (to avoid surprises due to unexpected method combination, it's + best to free things in this method in preference to `parse_eof'.) + Implementors of this method should be sure to also call the parse_end() + methods of any sub-objects to which they have pointers. + + This is also called by the finalize() method, just before object + destruction, if it has not already been called. + + The `parsed_p' instance variable is used to prevent multiple calls to + `parse_end'. + + + bool displayable_inline_p (MimeObjectClass *class, MimeHeaders *hdrs) + + This method should return true if this class of object will be displayed + directly, as opposed to being displayed as a link. This information is + used by the "multipart/alternative" parser to decide which of its children + is the ``best'' one to display. Note that this is a class method, not + an object method -- there is not yet an instance of this class at the time + that it is called. The `hdrs' provided are the headers of the object that + might be instantiated -- from this, the method may extract additional + infomation that it might need to make its decision. + */ + + +/* this one is typdedef'ed in mimei.h, since it is the base-class. */ +struct MimeObjectClass { + + /* Note: the order of these first five slots is known by MimeDefClass(). + Technically, these are part of the object system, not the MIME code. + */ + const char *class_name; + int instance_size; + struct MimeObjectClass *superclass; + int (*class_initialize) (MimeObjectClass *clazz); + bool class_initialized; + + /* These are the methods shared by all MIME objects. See comment above. + */ + int (*initialize) (MimeObject *obj); + void (*finalize) (MimeObject *obj); + int (*parse_begin) (MimeObject *obj); + int (*parse_buffer) (const char *buf, int32_t size, MimeObject *obj); + int (*parse_line) (const char *line, int32_t length, MimeObject *obj); + int (*parse_eof) (MimeObject *obj, bool abort_p); + int (*parse_end) (MimeObject *obj, bool abort_p); + + bool (*displayable_inline_p) (MimeObjectClass *clazz, MimeHeaders *hdrs); + +#if defined(DEBUG) && defined(XP_UNIX) + int (*debug_print) (MimeObject *obj, PRFileDesc *stream, int32_t depth); +#endif +}; + +extern "C" MimeObjectClass mimeObjectClass; + +/* this one is typdedef'ed in mimei.h, since it is the base-class. */ +struct MimeObject { + MimeObjectClass *clazz; /* Pointer to class object, for `type-of' */ + + MimeHeaders *headers; /* The header data associated with this object; + this is where the content-type, disposition, + description, and other meta-data live. + + For example, the outermost message/rfc822 object + would have NULL here (since it has no parent, + thus no headers to describe it.) However, a + multipart/mixed object, which was the sole + child of that message/rfc822 object, would have + here a copy of the headers which began the + parent object (the headers which describe the + child.) + */ + + char *content_type; /* The MIME content-type and encoding. */ + char *encoding; /* In most cases, these will be the same as the + values to be found in the `headers' object, + but in some cases, the values in these slots + will be more correct than the headers. + */ + + + MimeObject *parent; /* Backpointer to a MimeContainer object. */ + + MimeDisplayOptions *options; /* Display preferences set by caller. */ + + bool closed_p; /* Whether it's done being written to. */ + bool parsed_p; /* Whether the parser has been shut down. */ + bool output_p; /* Whether it should be written. */ + bool dontShowAsAttachment; /* Force an object to not be shown as attachment, + but when is false, it doesn't mean it will be + shown as attachment; specifically, body parts + are never shown as attachments. */ + + /* Read-buffer and write-buffer (on input, `parse_buffer' uses ibuffer to + compose calls to `parse_line'; on output, `obuffer' is used in various + ways by various routines.) These buffers are created and grow as needed. + `ibuffer' should be generally be considered hands-off, and `obuffer' + should generally be considered fair game. + */ + char *ibuffer, *obuffer; + int32_t ibuffer_size, obuffer_size; + int32_t ibuffer_fp, obuffer_fp; +}; + + +#define MimeObject_grow_obuffer(obj, desired_size) \ + (((desired_size) >= (obj)->obuffer_size) ? \ + mime_GrowBuffer ((uint32_t)(desired_size), (uint32_t)sizeof(char), 1024, \ + &(obj)->obuffer, (int32_t*)&(obj)->obuffer_size) \ + : 0) + + +#endif /* _MIMEOBJ_H_ */ diff --git a/mailnews/mime/src/mimepbuf.cpp b/mailnews/mime/src/mimepbuf.cpp new file mode 100644 index 000000000..8e352ed22 --- /dev/null +++ b/mailnews/mime/src/mimepbuf.cpp @@ -0,0 +1,296 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "mimepbuf.h" +#include "mimemoz2.h" +#include "prmem.h" +#include "prio.h" +#include "plstr.h" +#include "nsMimeStringResources.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" +// +// External Defines... +// +extern nsresult +nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile); + +/* See mimepbuf.h for a description of the mission of this file. + + Implementation: + + When asked to buffer an object, we first try to malloc() a buffer to + hold the upcoming part. First we try to allocate a 50k buffer, and + then back off by 5k until we are able to complete the allocation, + or are unable to allocate anything. + + As data is handed to us, we store it in the memory buffer, until the + size of the memory buffer is exceeded (including the case where no + memory buffer was able to be allocated at all.) + + Once we've filled the memory buffer, we open a temp file on disk. + Anything that is currently in the memory buffer is then flushed out + to the disk file (and the memory buffer is discarded.) Subsequent + data that is passed in is appended to the file. + + Thus only one of the memory buffer or the disk buffer ever exist at + the same time; and small parts tend to live completely in memory + while large parts tend to live on disk. + + When we are asked to read the data back out of the buffer, we call + the provided read-function with either: the contents of the memory + buffer; or blocks read from the disk file. + */ + +#define TARGET_MEMORY_BUFFER_SIZE (1024 * 50) /* try for 50k mem buffer */ +#define TARGET_MEMORY_BUFFER_QUANTUM (1024 * 5) /* decrease in steps of 5k */ +#define DISK_BUFFER_SIZE (1024 * 10) /* read disk in 10k chunks */ + + +struct MimePartBufferData +{ + char *part_buffer; /* Buffer used for part-lookahead. */ + int32_t part_buffer_fp; /* Active length. */ + int32_t part_buffer_size; /* How big it is. */ + + nsCOMPtr <nsIFile> file_buffer; /* The nsIFile of a temp file used when we + run out of room in the head_buffer. */ + nsCOMPtr <nsIInputStream> input_file_stream; /* A stream to it. */ + nsCOMPtr <nsIOutputStream> output_file_stream; /* A stream to it. */ +}; + +MimePartBufferData * +MimePartBufferCreate (void) +{ + MimePartBufferData *data = PR_NEW(MimePartBufferData); + if (!data) return 0; + memset(data, 0, sizeof(*data)); + return data; +} + + +void +MimePartBufferClose (MimePartBufferData *data) +{ + NS_ASSERTION(data, "MimePartBufferClose: no data"); + if (!data) return; + + if (data->input_file_stream) + { + data->input_file_stream->Close(); + data->input_file_stream = nullptr; + } + + if (data->output_file_stream) + { + data->output_file_stream->Close(); + data->output_file_stream = nullptr; + } +} + + +void +MimePartBufferReset (MimePartBufferData *data) +{ + NS_ASSERTION(data, "MimePartBufferReset: no data"); + if (!data) return; + + PR_FREEIF(data->part_buffer); + data->part_buffer_fp = 0; + + if (data->input_file_stream) + { + data->input_file_stream->Close(); + data->input_file_stream = nullptr; + } + + if (data->output_file_stream) + { + data->output_file_stream->Close(); + data->output_file_stream = nullptr; + } + + if (data->file_buffer) + { + data->file_buffer->Remove(false); + data->file_buffer = nullptr; + } +} + + +void +MimePartBufferDestroy (MimePartBufferData *data) +{ + NS_ASSERTION(data, "MimePartBufferDestroy: no data"); + if (!data) return; + MimePartBufferReset (data); + PR_Free(data); +} + + +int +MimePartBufferWrite (MimePartBufferData *data, + const char *buf, int32_t size) +{ + NS_ASSERTION(data && buf && size > 0, "MimePartBufferWrite: Bad param"); + if (!data || !buf || size <= 0) + return -1; + + /* If we don't yet have a buffer (either memory or file) try and make a + memory buffer. + */ + if (!data->part_buffer && + !data->file_buffer) + { + int target_size = TARGET_MEMORY_BUFFER_SIZE; + while (target_size > 0) + { + data->part_buffer = (char *) PR_MALLOC(target_size); + if (data->part_buffer) break; /* got it! */ + target_size -= TARGET_MEMORY_BUFFER_QUANTUM; /* decrease it and try + again */ + } + + if (data->part_buffer) + data->part_buffer_size = target_size; + else + data->part_buffer_size = 0; + + data->part_buffer_fp = 0; + } + + /* Ok, if at this point we still don't have either kind of buffer, try and + make a file buffer. */ + if (!data->part_buffer && !data->file_buffer) + { + nsCOMPtr <nsIFile> tmpFile; + nsresult rv = nsMsgCreateTempFile("nsma", getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE); + data->file_buffer = do_QueryInterface(tmpFile); + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(data->output_file_stream), data->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600); + NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE); + } + + NS_ASSERTION(data->part_buffer || data->output_file_stream, "no part_buffer or file_stream"); + + /* If this buf will fit in the memory buffer, put it there. + */ + if (data->part_buffer && + data->part_buffer_fp + size < data->part_buffer_size) + { + memcpy(data->part_buffer + data->part_buffer_fp, + buf, size); + data->part_buffer_fp += size; + } + + /* Otherwise it won't fit; write it to the file instead. */ + else + { + /* If the file isn't open yet, open it, and dump the memory buffer + to it. */ + if (!data->output_file_stream) + { + nsresult rv; + if (!data->file_buffer) + { + nsCOMPtr <nsIFile> tmpFile; + rv = nsMsgCreateTempFile("nsma", getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE); + data->file_buffer = do_QueryInterface(tmpFile); + + } + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(data->output_file_stream), data->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600); + NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE); + + if (data->part_buffer && data->part_buffer_fp) + { + uint32_t bytesWritten; + nsresult rv = data->output_file_stream->Write(data->part_buffer, + data->part_buffer_fp, &bytesWritten); + NS_ENSURE_SUCCESS(rv, MIME_ERROR_WRITING_FILE); + } + + PR_FREEIF(data->part_buffer); + data->part_buffer_fp = 0; + data->part_buffer_size = 0; + } + + /* Dump this buf to the file. */ + uint32_t bytesWritten; + nsresult rv = data->output_file_stream->Write (buf, size, &bytesWritten); + if (NS_FAILED(rv) || (int32_t) bytesWritten < size) + return MIME_OUT_OF_MEMORY; + } + + return 0; +} + + +int +MimePartBufferRead (MimePartBufferData *data, + MimeConverterOutputCallback read_fn, + void *closure) +{ + int status = 0; + NS_ASSERTION(data, "no data"); + if (!data) return -1; + + if (data->part_buffer) + { + // Read it out of memory. + status = read_fn(data->part_buffer, data->part_buffer_fp, closure); + } + else if (data->file_buffer) + { + /* Read it off disk. + */ + char *buf; + int32_t buf_size = DISK_BUFFER_SIZE; + + NS_ASSERTION(data->part_buffer_size == 0 && data->part_buffer_fp == 0, "buffer size is not null"); + NS_ASSERTION(data->file_buffer, "no file buffer name"); + if (!data->file_buffer) + return -1; + + buf = (char *) PR_MALLOC(buf_size); + if (!buf) + return MIME_OUT_OF_MEMORY; + + // First, close the output file to open the input file! + if (data->output_file_stream) + data->output_file_stream->Close(); + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(data->input_file_stream), data->file_buffer); + if (NS_FAILED(rv)) + { + PR_Free(buf); + return MIME_UNABLE_TO_OPEN_TMP_FILE; + } + while(1) + { + uint32_t bytesRead = 0; + rv = data->input_file_stream->Read(buf, buf_size - 1, &bytesRead); + if (NS_FAILED(rv) || !bytesRead) + { + break; + } + else + { + /* It would be really nice to be able to yield here, and let + some user events and other input sources get processed. + Oh well. */ + + status = read_fn (buf, bytesRead, closure); + if (status < 0) break; + } + } + PR_Free(buf); + } + + return 0; +} + diff --git a/mailnews/mime/src/mimepbuf.h b/mailnews/mime/src/mimepbuf.h new file mode 100644 index 000000000..200b64a89 --- /dev/null +++ b/mailnews/mime/src/mimepbuf.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEPBUF_H_ +#define _MIMEPBUF_H_ + +#include "mimei.h" +#include "modmimee.h" // for MimeConverterOutputCallback + +/* This file provides the ability to save up the entire contents of a MIME + object (of arbitrary size), and then emit it all at once later. The + buffering is done in an efficient way that works well for both very large + and very small objects. + + This is used in two places: + + = The implementation of multipart/alternative uses this code to do a + one-part-lookahead. As it traverses its children, it moves forward + until it finds a part which cannot be displayed; and then it displays + the *previous* part (the last which *could* be displayed.) This code + is used to hold the previous part until it is needed. +*/ + +/* An opaque object used to represent the buffered data. + */ +typedef struct MimePartBufferData MimePartBufferData; + +/* Create an empty part buffer object. + */ +extern MimePartBufferData *MimePartBufferCreate (void); + +/* Assert that the buffer is now full (EOF has been reached on the current + part.) This will free some resources, but leaves the part in the buffer. + After calling MimePartBufferReset, the buffer may be used to store a + different object. + */ +void MimePartBufferClose (MimePartBufferData *data); + +/* Reset a part buffer object to the default state, discarding any currently- + buffered data. + */ +extern void MimePartBufferReset (MimePartBufferData *data); + +/* Free the part buffer itself, and discard any buffered data. + */ +extern void MimePartBufferDestroy (MimePartBufferData *data); + +/* Push a chunk of a MIME object into the buffer. + */ +extern int MimePartBufferWrite (MimePartBufferData *data, + const char *buf, int32_t size); + +/* Read the contents of the buffer back out. This will invoke the provided + read_fn with successive chunks of data until the buffer has been drained. + The provided function may be called once, or multiple times. + */ +extern int +MimePartBufferRead (MimePartBufferData *data, + MimeConverterOutputCallback read_fn, + void *closure); + +#endif /* _MIMEPBUF_H_ */ diff --git a/mailnews/mime/src/mimesun.cpp b/mailnews/mime/src/mimesun.cpp new file mode 100644 index 000000000..84f06a885 --- /dev/null +++ b/mailnews/mime/src/mimesun.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimesun.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeSunAttachment, MimeSunAttachmentClass, + mimeSunAttachmentClass, &MIME_SUPERCLASS); + +static MimeMultipartBoundaryType MimeSunAttachment_check_boundary(MimeObject *, + const char *, + int32_t); +static int MimeSunAttachment_create_child(MimeObject *); +static int MimeSunAttachment_parse_child_line (MimeObject *, const char *, int32_t, + bool); +static int MimeSunAttachment_parse_begin (MimeObject *); +static int MimeSunAttachment_parse_eof (MimeObject *, bool); + +static int +MimeSunAttachmentClassInitialize(MimeSunAttachmentClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + + PR_ASSERT(!oclass->class_initialized); + oclass->parse_begin = MimeSunAttachment_parse_begin; + oclass->parse_eof = MimeSunAttachment_parse_eof; + mclass->check_boundary = MimeSunAttachment_check_boundary; + mclass->create_child = MimeSunAttachment_create_child; + mclass->parse_child_line = MimeSunAttachment_parse_child_line; + return 0; +} + + +static int +MimeSunAttachment_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + /* Sun messages always have separators at the beginning. */ + return MimeObject_write_separator(obj); +} + +static int +MimeSunAttachment_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + /* Sun messages always have separators at the end. */ + if (!abort_p) + { + status = MimeObject_write_separator(obj); + if (status < 0) return status; + } + + return 0; +} + + +static MimeMultipartBoundaryType +MimeSunAttachment_check_boundary(MimeObject *obj, const char *line, + int32_t length) +{ + /* ten dashes */ + + if (line && + line[0] == '-' && line[1] == '-' && line[2] == '-' && line[3] == '-' && + line[4] == '-' && line[5] == '-' && line[6] == '-' && line[7] == '-' && + line[8] == '-' && line[9] == '-' && + (line[10] == '\r' || line[10] == '\n')) + return MimeMultipartBoundaryTypeSeparator; + else + return MimeMultipartBoundaryTypeNone; +} + + +static int +MimeSunAttachment_create_child(MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + int status = 0; + + char *sun_data_type = 0; + const char *mime_ct = 0, *sun_enc_info = 0, *mime_cte = 0; + char *mime_ct2 = 0; /* sometimes we need to copy; this is for freeing. */ + MimeObject *child = 0; + + mult->state = MimeMultipartPartLine; + + sun_data_type = (mult->hdrs + ? MimeHeaders_get (mult->hdrs, HEADER_X_SUN_DATA_TYPE, + true, false) + : 0); + if (sun_data_type) + { + int i; + static const struct { const char *in, *out; } sun_types[] = { + + /* Convert recognised Sun types to the corresponding MIME types, + and convert unrecognized ones based on the file extension and + the mime.types file. + + These are the magic types used by MailTool that I can determine. + The only actual written spec I've found only listed the first few. + The rest were found by inspection (both of real-world messages, + and by running `strings' on the MailTool binary, and on the file + /usr/openwin/lib/cetables/cetables (the "Class Engine", Sun's + equivalent to .mailcap and mime.types.) + */ + { "default", TEXT_PLAIN }, + { "default-doc", TEXT_PLAIN }, + { "text", TEXT_PLAIN }, + { "scribe", TEXT_PLAIN }, + { "sgml", TEXT_PLAIN }, + { "tex", TEXT_PLAIN }, + { "troff", TEXT_PLAIN }, + { "c-file", TEXT_PLAIN }, + { "h-file", TEXT_PLAIN }, + { "readme-file", TEXT_PLAIN }, + { "shell-script", TEXT_PLAIN }, + { "cshell-script", TEXT_PLAIN }, + { "makefile", TEXT_PLAIN }, + { "hidden-docs", TEXT_PLAIN }, + { "message", MESSAGE_RFC822 }, + { "mail-message", MESSAGE_RFC822 }, + { "mail-file", TEXT_PLAIN }, + { "gif-file", IMAGE_GIF }, + { "jpeg-file", IMAGE_JPG }, + { "ppm-file", IMAGE_PPM }, + { "pgm-file", "image/x-portable-graymap" }, + { "pbm-file", "image/x-portable-bitmap" }, + { "xpm-file", "image/x-xpixmap" }, + { "ilbm-file", "image/ilbm" }, + { "tiff-file", "image/tiff" }, + { "photocd-file", "image/x-photo-cd" }, + { "sun-raster", "image/x-sun-raster" }, + { "audio-file", AUDIO_BASIC }, + { "postscript", APPLICATION_POSTSCRIPT }, + { "postscript-file", APPLICATION_POSTSCRIPT }, + { "framemaker-document", "application/x-framemaker" }, + { "sundraw-document", "application/x-sun-draw" }, + { "sunpaint-document", "application/x-sun-paint" }, + { "sunwrite-document", "application/x-sun-write" }, + { "islanddraw-document", "application/x-island-draw" }, + { "islandpaint-document", "application/x-island-paint" }, + { "islandwrite-document", "application/x-island-write" }, + { "sun-executable", APPLICATION_OCTET_STREAM }, + { "default-app", APPLICATION_OCTET_STREAM }, + { 0, 0 }}; + for (i = 0; sun_types[i].in; i++) + if (!PL_strcasecmp(sun_data_type, sun_types[i].in)) + { + mime_ct = sun_types[i].out; + break; + } + } + + /* If we didn't find a type, look at the extension on the file name. + */ + if (!mime_ct && + obj->options && + obj->options->file_type_fn) + { + char *name = MimeHeaders_get_name(mult->hdrs, obj->options); + if (name) + { + mime_ct2 = obj->options->file_type_fn(name, + obj->options->stream_closure); + mime_ct = mime_ct2; + PR_Free(name); + if (!mime_ct2 || !PL_strcasecmp (mime_ct2, UNKNOWN_CONTENT_TYPE)) + { + PR_FREEIF(mime_ct2); + mime_ct = APPLICATION_OCTET_STREAM; + } + } + } + if (!mime_ct) + mime_ct = APPLICATION_OCTET_STREAM; + + PR_FREEIF(sun_data_type); + + + /* Convert recognised Sun encodings to the corresponding MIME encodings. + However, if the X-Sun-Encoding-Info field contains more than one + encoding (that is, contains a comma) then assign it the encoding of + the *rightmost* element in the list; and change its Content-Type to + application/octet-stream. Examples: + + Sun Type: Translates To: + ================== ==================== + type: TEXT type: text/plain + encoding: COMPRESS encoding: x-compress + + type: POSTSCRIPT type: application/x-compress + encoding: COMPRESS,UUENCODE encoding: x-uuencode + + type: TEXT type: application/octet-stream + encoding: UNKNOWN,UUENCODE encoding: x-uuencode + */ + + sun_data_type = (mult->hdrs + ? MimeHeaders_get (mult->hdrs, HEADER_X_SUN_ENCODING_INFO, + false,false) + : 0); + sun_enc_info = sun_data_type; + + + /* this "adpcm-compress" pseudo-encoding is some random junk that + MailTool adds to the encoding description of .AU files: we can + ignore it if it is the leftmost element of the encoding field. + (It looks like it's created via `audioconvert -f g721'. Why? + Who knows.) + */ + if (sun_enc_info && !PL_strncasecmp (sun_enc_info, "adpcm-compress", 14)) + { + sun_enc_info += 14; + while (IS_SPACE(*sun_enc_info) || *sun_enc_info == ',') + sun_enc_info++; + } + + /* Extract the last element of the encoding field, changing the content + type if necessary (as described above.) + */ + if (sun_enc_info && *sun_enc_info) + { + const char *prev; + const char *end = PL_strrchr(sun_enc_info, ','); + if (end) + { + const char *start = sun_enc_info; + sun_enc_info = end + 1; + while (IS_SPACE(*sun_enc_info)) + sun_enc_info++; + for (prev = end-1; prev > start && *prev != ','; prev--) + ; + if (*prev == ',') prev++; + + if (!PL_strncasecmp (prev, "uuencode", end-prev)) + mime_ct = APPLICATION_UUENCODE; + else if (!PL_strncasecmp (prev, "gzip", end-prev)) + mime_ct = APPLICATION_GZIP; + else if (!PL_strncasecmp (prev, "compress", end-prev)) + mime_ct = APPLICATION_COMPRESS; + else if (!PL_strncasecmp (prev, "default-compress", end-prev)) + mime_ct = APPLICATION_COMPRESS; + else + mime_ct = APPLICATION_OCTET_STREAM; + } + } + + /* Convert the remaining Sun encoding to a MIME encoding. + If it isn't known, change the content-type instead. + */ + if (!sun_enc_info || !*sun_enc_info) + ; + else if (!PL_strcasecmp(sun_enc_info,"compress")) mime_cte = ENCODING_COMPRESS; + else if (!PL_strcasecmp(sun_enc_info,"uuencode")) mime_cte = ENCODING_UUENCODE; + else if (!PL_strcasecmp(sun_enc_info,"gzip")) mime_cte = ENCODING_GZIP; + else mime_ct = APPLICATION_OCTET_STREAM; + + PR_FREEIF(sun_data_type); + + + /* Now that we know its type and encoding, create a MimeObject to represent + this part. + */ + child = mime_create(mime_ct, mult->hdrs, obj->options); + if (!child) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + + /* Fake out the child's content-type and encoding (it probably doesn't have + one right now, because the X-Sun- headers aren't generally recognised by + the rest of this library.) + */ + PR_FREEIF(child->content_type); + PR_FREEIF(child->encoding); + PR_ASSERT(mime_ct); + child->content_type = (mime_ct ? strdup(mime_ct) : 0); + child->encoding = (mime_cte ? strdup(mime_cte) : 0); + + status = ((MimeContainerClass *) obj->clazz)->add_child(obj, child); + if (status < 0) + { + mime_free(child); + child = 0; + goto FAIL; + } + + /* Sun attachments always have separators between parts. */ + status = MimeObject_write_separator(obj); + if (status < 0) goto FAIL; + + /* And now that we've added this new object to our list of + children, start its parser going. */ + status = child->clazz->parse_begin(child); + if (status < 0) goto FAIL; + + FAIL: + PR_FREEIF(mime_ct2); + PR_FREEIF(sun_data_type); + return status; +} + + +static int +MimeSunAttachment_parse_child_line (MimeObject *obj, const char *line, int32_t length, + bool first_line_p) +{ + MimeContainer *cont = (MimeContainer *) obj; + MimeObject *kid; + + /* This is simpler than MimeMultipart->parse_child_line in that it doesn't + play games about body parts without trailing newlines. + */ + + PR_ASSERT(cont->nchildren > 0); + if (cont->nchildren <= 0) + return -1; + + kid = cont->children[cont->nchildren-1]; + PR_ASSERT(kid); + if (!kid) return -1; + + return kid->clazz->parse_buffer (line, length, kid); +} diff --git a/mailnews/mime/src/mimesun.h b/mailnews/mime/src/mimesun.h new file mode 100644 index 000000000..5ffd7dade --- /dev/null +++ b/mailnews/mime/src/mimesun.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMESUN_H_ +#define _MIMESUN_H_ + +#include "mimemult.h" + +/* MimeSunAttachment is the class for X-Sun-Attachment message contents, which + is the Content-Type assigned by that pile of garbage called MailTool. This + is not a MIME type per se, but it's very similar to multipart/mixed, so it's + easy to parse. Lots of people use MailTool, so what the hell. + + The format is this: + + = Content-Type is X-Sun-Attachment + = parts are separated by lines of exactly ten dashes + = just after the dashes comes a block of headers, including: + + X-Sun-Data-Type: (manditory) + Values are Text, Postscript, Scribe, SGML, TeX, Troff, DVI, + and Message. + + X-Sun-Encoding-Info: (optional) + Ordered, comma-separated values, including Compress and Uuencode. + + X-Sun-Data-Name: (optional) + File name, maybe. + + X-Sun-Data-Description: (optional) + Longer text. + + X-Sun-Content-Lines: (manditory, unless Length is present) + Number of lines in the body, not counting headers and the blank + line that follows them. + + X-Sun-Content-Length: (manditory, unless Lines is present) + Bytes, presumably using Unix line terminators. + */ + +typedef struct MimeSunAttachmentClass MimeSunAttachmentClass; +typedef struct MimeSunAttachment MimeSunAttachment; + +struct MimeSunAttachmentClass { + MimeMultipartClass multipart; +}; + +extern MimeSunAttachmentClass mimeSunAttachmentClass; + +struct MimeSunAttachment { + MimeMultipart multipart; +}; + +#define MimeSunAttachmentClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMESUN_H_ */ diff --git a/mailnews/mime/src/mimetenr.cpp b/mailnews/mime/src/mimetenr.cpp new file mode 100644 index 000000000..4fecb6bcf --- /dev/null +++ b/mailnews/mime/src/mimetenr.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimetenr.h" +#include "prlog.h" + +/* All the magic for this class is in mimetric.c; since text/enriched and + text/richtext are so similar, it was easiest to implement them in the + same method (but this is a subclass anyway just for general goodness.) + */ + +#define MIME_SUPERCLASS mimeInlineTextRichtextClass +MimeDefClass(MimeInlineTextEnriched, MimeInlineTextEnrichedClass, + mimeInlineTextEnrichedClass, &MIME_SUPERCLASS); + +static int +MimeInlineTextEnrichedClassInitialize(MimeInlineTextEnrichedClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); +#endif + MimeInlineTextRichtextClass *rclass = (MimeInlineTextRichtextClass *) clazz; + rclass->enriched_p = true; + return 0; +} diff --git a/mailnews/mime/src/mimetenr.h b/mailnews/mime/src/mimetenr.h new file mode 100644 index 000000000..34608f547 --- /dev/null +++ b/mailnews/mime/src/mimetenr.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETENR_H_ +#define _MIMETENR_H_ + +#include "mimetric.h" + +/* The MimeInlineTextEnriched class implements the text/enriched MIME content + type, as defined in RFC 1563. It does this largely by virtue of being a + subclass of the MimeInlineTextRichtext class. + */ + +typedef struct MimeInlineTextEnrichedClass MimeInlineTextEnrichedClass; +typedef struct MimeInlineTextEnriched MimeInlineTextEnriched; + +struct MimeInlineTextEnrichedClass { + MimeInlineTextRichtextClass text; +}; + +extern MimeInlineTextEnrichedClass mimeInlineTextEnrichedClass; + +struct MimeInlineTextEnriched { + MimeInlineTextRichtext richtext; +}; + +#define MimeInlineTextEnrichedClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextRichtextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETENR_H_ */ diff --git a/mailnews/mime/src/mimetext.cpp b/mailnews/mime/src/mimetext.cpp new file mode 100644 index 000000000..a854348c5 --- /dev/null +++ b/mailnews/mime/src/mimetext.cpp @@ -0,0 +1,544 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +#include "mimetext.h" +#include "mimebuf.h" +#include "mimethtm.h" +#include "comi18n.h" +#include "mimemoz2.h" + +#include "prlog.h" +#include "prmem.h" +#include "plstr.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIServiceManager.h" +#include "nsIPrefLocalizedString.h" +#include "nsMsgUtils.h" +#include "nsMimeTypes.h" +#include "nsServiceManagerUtils.h" + +#define MIME_SUPERCLASS mimeLeafClass +MimeDefClass(MimeInlineText, MimeInlineTextClass, mimeInlineTextClass, + &MIME_SUPERCLASS); + +static int MimeInlineText_initialize (MimeObject *); +static void MimeInlineText_finalize (MimeObject *); +static int MimeInlineText_rot13_line (MimeObject *, char *line, int32_t length); +static int MimeInlineText_parse_eof (MimeObject *obj, bool abort_p); +static int MimeInlineText_parse_end (MimeObject *, bool); +static int MimeInlineText_parse_decoded_buffer (const char *, int32_t, MimeObject *); +static int MimeInlineText_rotate_convert_and_parse_line(char *, int32_t, + MimeObject *); +static int MimeInlineText_open_dam(char *line, int32_t length, MimeObject *obj); +static int MimeInlineText_initializeCharset(MimeObject *obj); + +static int +MimeInlineTextClassInitialize(MimeInlineTextClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeLeafClass *lclass = (MimeLeafClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeInlineText_initialize; + oclass->finalize = MimeInlineText_finalize; + oclass->parse_eof = MimeInlineText_parse_eof; + oclass->parse_end = MimeInlineText_parse_end; + clazz->rot13_line = MimeInlineText_rot13_line; + clazz->initialize_charset = MimeInlineText_initializeCharset; + lclass->parse_decoded_buffer = MimeInlineText_parse_decoded_buffer; + return 0; +} + +static int +MimeInlineText_initialize (MimeObject *obj) +{ + /* This is an abstract class; it shouldn't be directly instantiated. */ + PR_ASSERT(obj->clazz != (MimeObjectClass *) &mimeInlineTextClass); + + ((MimeInlineText *) obj)->initializeCharset = false; + ((MimeInlineText *) obj)->needUpdateMsgWinCharset = false; + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + +static int MimeInlineText_initializeCharset(MimeObject *obj) +{ + MimeInlineText *text = (MimeInlineText *) obj; + + text->inputAutodetect = false; + text->charsetOverridable = false; + + /* Figure out an appropriate charset for this object. + */ + if (!text->charset && obj->headers) + { + if (obj->options && obj->options->override_charset) + { + text->charset = strdup(obj->options->default_charset); + } + else + { + char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, + false, false); + if (ct) + { + text->charset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL); + PR_Free(ct); + } + + if (!text->charset) + { + /* If we didn't find "Content-Type: ...; charset=XX" then look + for "X-Sun-Charset: XX" instead. (Maybe this should be done + in MimeSunAttachmentClass, but it's harder there than here.) + */ + text->charset = MimeHeaders_get (obj->headers, + HEADER_X_SUN_CHARSET, + false, false); + } + + /* iMIP entities without an explicit charset parameter default to + US-ASCII (RFC 2447, section 2.4). However, Microsoft Outlook generates + UTF-8 but omits the charset parameter. + When no charset is defined by the container (e.g. iMIP), iCalendar + files default to UTF-8 (RFC 2445, section 4.1.4). + */ + if (!text->charset && + obj->content_type && + !PL_strcasecmp(obj->content_type, TEXT_CALENDAR)) + text->charset = strdup("UTF-8"); + + if (!text->charset) + { + nsresult res; + + text->charsetOverridable = true; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &res)); + if (NS_SUCCEEDED(res)) + { + nsCOMPtr<nsIPrefLocalizedString> str; + if (NS_SUCCEEDED(prefBranch->GetComplexValue("intl.charset.detector", NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str)))) { + //only if we can get autodetector name correctly, do we set this to true + text->inputAutodetect = true; + } + } + + if (obj->options && obj->options->default_charset) + text->charset = strdup(obj->options->default_charset); + else + { + if (NS_SUCCEEDED(res)) + { + nsString value; + NS_GetLocalizedUnicharPreferenceWithDefault(prefBranch, "mailnews.view_default_charset", EmptyString(), value); + text->charset = ToNewUTF8String(value); + } + else + text->charset = strdup(""); + } + } + } + } + + if (text->inputAutodetect) + { + //we need to prepare lineDam for charset detection + text->lineDamBuffer = (char*)PR_Malloc(DAM_MAX_BUFFER_SIZE); + text->lineDamPtrs = (char**)PR_Malloc(DAM_MAX_LINES*sizeof(char*)); + text->curDamOffset = 0; + text->lastLineInDam = 0; + if (!text->lineDamBuffer || !text->lineDamPtrs) + { + text->inputAutodetect = false; + PR_FREEIF(text->lineDamBuffer); + PR_FREEIF(text->lineDamPtrs); + } + } + + text->initializeCharset = true; + + return 0; +} + +static void +MimeInlineText_finalize (MimeObject *obj) +{ + MimeInlineText *text = (MimeInlineText *) obj; + + obj->clazz->parse_eof (obj, false); + obj->clazz->parse_end (obj, false); + + text->inputDecoder = nullptr; + text->utf8Encoder = nullptr; + PR_FREEIF(text->charset); + + /* Should have been freed by parse_eof, but just in case... */ + PR_ASSERT(!text->cbuffer); + PR_FREEIF (text->cbuffer); + + if (text->inputAutodetect) { + PR_FREEIF(text->lineDamBuffer); + PR_FREEIF(text->lineDamPtrs); + text->inputAutodetect = false; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj); +} + + +static int +MimeInlineText_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + + if (obj->closed_p) return 0; + NS_ASSERTION(!obj->parsed_p, "obj already parsed"); + + MimeInlineText *text = (MimeInlineText *) obj; + + /* Flush any buffered data from the MimeLeaf's decoder */ + status = ((MimeLeafClass*)&MIME_SUPERCLASS)->close_decoder(obj); + if (status < 0) return status; + + /* If there is still data in the ibuffer, that means that the last + line of this part didn't end in a newline; so push it out anyway + (this means that the parse_line method will be called with a string + with no trailing newline, which isn't the usual case). We do this + here, rather than in MimeObject_parse_eof, because MimeObject isn't + aware of the rotating-and-converting / charset detection we need to + do first. + */ + if (!abort_p && obj->ibuffer_fp > 0) + { + status = MimeInlineText_rotate_convert_and_parse_line (obj->ibuffer, + obj->ibuffer_fp, + obj); + obj->ibuffer_fp = 0; + if (status < 0) + { + //we haven't find charset yet? Do it before return + if (text->inputAutodetect) + status = MimeInlineText_open_dam(nullptr, 0, obj); + + obj->closed_p = true; + return status; + } + } + + //we haven't find charset yet? now its the time + if (text->inputAutodetect) + status = MimeInlineText_open_dam(nullptr, 0, obj); + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p); +} + +static int +MimeInlineText_parse_end (MimeObject *obj, bool abort_p) +{ + MimeInlineText *text = (MimeInlineText *) obj; + + if (obj->parsed_p) + { + PR_ASSERT(obj->closed_p); + return 0; + } + + /* We won't be needing this buffer any more; nuke it. */ + PR_FREEIF(text->cbuffer); + text->cbuffer_size = 0; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end (obj, abort_p); +} + + +/* This maps A-M to N-Z and N-Z to A-M. All other characters are left alone. + (Comments in GNUS imply that for Japanese, one should rotate by 47?) + */ +static const unsigned char MimeInlineText_rot13_table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 91, 92, 93, 94, 95, 96, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 97, 98, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, + 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, + 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, + 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, + 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, + 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, + 247, 248, 249, 250, 251, 252, 253, 254, 255 }; + +static int +MimeInlineText_rot13_line (MimeObject *obj, char *line, int32_t length) +{ + unsigned char *s, *end; + PR_ASSERT(line); + if (!line) return -1; + s = (unsigned char *) line; + end = s + length; + while (s < end) + { + *s = MimeInlineText_rot13_table[*s]; + s++; + } + return 0; +} + + +static int +MimeInlineText_parse_decoded_buffer (const char *buf, int32_t size, MimeObject *obj) +{ + PR_ASSERT(!obj->closed_p); + if (obj->closed_p) return -1; + + /* MimeLeaf takes care of this. */ + PR_ASSERT(obj->output_p && obj->options && obj->options->output_fn); + if (!obj->options) return -1; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (!obj->options->write_html_p && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach) + return MimeObject_write(obj, buf, size, true); + + /* This is just like the parse_decoded_buffer method we inherit from the + MimeLeaf class, except that we line-buffer to our own wrapper on the + `parse_line' method instead of calling the `parse_line' method directly. + */ + return mime_LineBuffer (buf, size, + &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp, + true, + ((int (*) (char *, int32_t, void *)) + /* This cast is to turn void into MimeObject */ + MimeInlineText_rotate_convert_and_parse_line), + obj); +} + + +#define MimeInlineText_grow_cbuffer(text, desired_size) \ + (((desired_size) >= (text)->cbuffer_size) ? \ + mime_GrowBuffer ((desired_size), sizeof(char), 100, \ + &(text)->cbuffer, &(text)->cbuffer_size) \ + : 0) + +static int +MimeInlineText_convert_and_parse_line(char *line, int32_t length, MimeObject *obj) +{ + int status; + char *converted = 0; + int32_t converted_len = 0; + + MimeInlineText *text = (MimeInlineText *) obj; + + //in case of charset autodetection, charset can be override by meta charset + if (text->charsetOverridable) { + if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextHTMLClass)) + { + MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj; + if (textHTML->charset && + *textHTML->charset && + strcmp(textHTML->charset, text->charset)) + { + //if meta tag specified charset is different from our detected result, use meta charset. + //but we don't want to redo previous lines + MIME_get_unicode_decoder(textHTML->charset, getter_AddRefs(text->inputDecoder)); + PR_FREEIF(text->charset); + text->charset = strdup(textHTML->charset); + + //update MsgWindow charset if we are instructed to do so + if (text->needUpdateMsgWinCharset && *text->charset) + SetMailCharacterSetToMsgWindow(obj, text->charset); + } + } + } + + //initiate decoder if not yet + if (text->inputDecoder == nullptr) + MIME_get_unicode_decoder(text->charset, getter_AddRefs(text->inputDecoder)); + // If no decoder found, use ""UTF-8"", that will map most non-US-ASCII chars as invalid + // A pure-ASCII only decoder would be better, but there is none + if (text->inputDecoder == nullptr) + MIME_get_unicode_decoder("UTF-8", getter_AddRefs(text->inputDecoder)); + if (text->utf8Encoder == nullptr) + MIME_get_unicode_encoder("UTF-8", getter_AddRefs(text->utf8Encoder)); + + bool useInputCharsetConverter = obj->options->m_inputCharsetToUnicodeDecoder && !PL_strcasecmp(text->charset, obj->options->charsetForCachedInputDecoder.get()); + + if (useInputCharsetConverter) + status = obj->options->charset_conversion_fn(line, length, + text->charset, + "UTF-8", + &converted, + &converted_len, + obj->options->stream_closure, obj->options->m_inputCharsetToUnicodeDecoder, + obj->options->m_unicodeToUTF8Encoder); + else + status = obj->options->charset_conversion_fn(line, length, + text->charset, + "UTF-8", + &converted, + &converted_len, + obj->options->stream_closure, (nsIUnicodeDecoder*)text->inputDecoder, + (nsIUnicodeEncoder*)text->utf8Encoder); + + if (status < 0) + { + PR_FREEIF(converted); + return status; + } + + if (converted) + { + line = converted; + length = converted_len; + } + + /* Now that the line has been converted, call the subclass's parse_line + method with the decoded data. */ + status = obj->clazz->parse_line(line, length, obj); + PR_FREEIF(converted); + + return status; +} + +//In this function call, all buffered lines in lineDam will be sent to charset detector +// and a charset will be used to parse all those line and following lines in this mime obj. +static int +MimeInlineText_open_dam(char *line, int32_t length, MimeObject *obj) +{ + MimeInlineText *text = (MimeInlineText *) obj; + const char* detectedCharset = nullptr; + nsresult res = NS_OK; + int status = 0; + int32_t i; + + if (text->curDamOffset <= 0) { + //there is nothing in dam, use current line for detection + if (length > 0) { + res = MIME_detect_charset(line, length, &detectedCharset); + } + } else { + //we have stuff in dam, use the one + res = MIME_detect_charset(text->lineDamBuffer, text->curDamOffset, &detectedCharset); + } + + //set the charset for this obj + if (NS_SUCCEEDED(res) && detectedCharset && *detectedCharset) { + PR_FREEIF(text->charset); + text->charset = strdup(detectedCharset); + + //update MsgWindow charset if we are instructed to do so + if (text->needUpdateMsgWinCharset && *text->charset) + SetMailCharacterSetToMsgWindow(obj, text->charset); + } + + //process dam and line using the charset + if (text->curDamOffset) { + for (i = 0; i < text->lastLineInDam-1; i++) + { + status = MimeInlineText_convert_and_parse_line( + text->lineDamPtrs[i], + text->lineDamPtrs[i+1] - text->lineDamPtrs[i], + obj ); + } + status = MimeInlineText_convert_and_parse_line( + text->lineDamPtrs[i], + text->lineDamBuffer + text->curDamOffset - text->lineDamPtrs[i], + obj ); + } + + if (length) + status = MimeInlineText_convert_and_parse_line(line, length, obj); + + PR_Free(text->lineDamPtrs); + PR_Free(text->lineDamBuffer); + text->lineDamPtrs = nullptr; + text->lineDamBuffer = nullptr; + text->inputAutodetect = false; + + return status; +} + + +static int +MimeInlineText_rotate_convert_and_parse_line(char *line, int32_t length, + MimeObject *obj) +{ + int status = 0; + MimeInlineTextClass *textc = (MimeInlineTextClass *) obj->clazz; + + PR_ASSERT(!obj->closed_p); + if (obj->closed_p) return -1; + + /* Rotate the line, if desired (this happens on the raw data, before any + charset conversion.) */ + if (obj->options && obj->options->rot13_p) + { + status = textc->rot13_line(obj, line, length); + if (status < 0) return status; + } + + // Now convert to the canonical charset, if desired. + // + bool doConvert = true; + // Don't convert vCard data + if ( ( (obj->content_type) && (!PL_strcasecmp(obj->content_type, TEXT_VCARD)) ) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) + || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) + doConvert = false; + + // Only convert if the user prefs is false + if ( (obj->options && obj->options->charset_conversion_fn) && + (!obj->options->force_user_charset) && + (doConvert) + ) + { + MimeInlineText *text = (MimeInlineText *) obj; + + if (!text->initializeCharset) + { + MimeInlineText_initializeCharset(obj); + //update MsgWindow charset if we are instructed to do so + if (text->needUpdateMsgWinCharset && *text->charset) + SetMailCharacterSetToMsgWindow(obj, text->charset); + } + + //if autodetect is on, push line to dam + if (text->inputAutodetect) + { + //see if we reach the lineDam buffer limit, if so, there is no need to keep buffering + if (text->lastLineInDam >= DAM_MAX_LINES || + DAM_MAX_BUFFER_SIZE - text->curDamOffset <= length) { + //we let open dam process this line as well as thing that already in Dam + //In case there is nothing in dam because this line is too big, we need to + //perform autodetect on this line + status = MimeInlineText_open_dam(line, length, obj); + } + else { + //buffering current line + text->lineDamPtrs[text->lastLineInDam] = text->lineDamBuffer + text->curDamOffset; + memcpy(text->lineDamPtrs[text->lastLineInDam], line, length); + text->lastLineInDam++; + text->curDamOffset += length; + } + } + else + status = MimeInlineText_convert_and_parse_line(line, length, obj); + } + else + status = obj->clazz->parse_line(line, length, obj); + + return status; +} diff --git a/mailnews/mime/src/mimetext.h b/mailnews/mime/src/mimetext.h new file mode 100644 index 000000000..78cb6bf3a --- /dev/null +++ b/mailnews/mime/src/mimetext.h @@ -0,0 +1,82 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETEXT_H_ +#define _MIMETEXT_H_ + +#include "mimeleaf.h" + +/* The MimeInlineText class is the superclass of all handlers for the + MIME text/ content types (which convert various text formats to HTML, + in one form or another.) + + It provides two services: + + = if ROT13 decoding is desired, the text will be rotated before + the `parse_line' method it called; + + = text will be converted from the message's charset to the "target" + charset before the `parse_line' method is called. + + The contract with charset-conversion is that the converted data will + be such that one may interpret any octets (8-bit bytes) in the data + which are in the range of the ASCII characters (0-127) as ASCII + characters. It is explicitly legal, for example, to scan through + the string for "<" and replace it with "<", and to search for things + that look like URLs and to wrap them with interesting HTML tags. + + The charset to which we convert will probably be UTF-8 (an encoding of + the Unicode character set, with the feature that all octets with the + high bit off have the same interpretations as ASCII.) + + #### NOTE: if it turns out that we use JIS (ISO-2022-JP) as the target + encoding, then this is not quite true; it is safe to search for the + low ASCII values (under hex 0x40, octal 0100, which is '@') but it + is NOT safe to search for values higher than that -- they may be + being used as the subsequent bytes in a multi-byte escape sequence. + It's a nice coincidence that HTML's critical characters ("<", ">", + and "&") have values under 0x40... + */ + +typedef struct MimeInlineTextClass MimeInlineTextClass; +typedef struct MimeInlineText MimeInlineText; + +struct MimeInlineTextClass { + MimeLeafClass leaf; + int (*rot13_line) (MimeObject *obj, char *line, int32_t length); + int (*convert_line_charset) (MimeObject *obj, char *line, int32_t length); + int (*initialize_charset) (MimeObject *obj); +}; + +extern MimeInlineTextClass mimeInlineTextClass; + +#define DAM_MAX_BUFFER_SIZE 8*1024 +#define DAM_MAX_LINES 1024 + +struct MimeInlineText { + MimeLeaf leaf; /* superclass variables */ + char *charset; /* The charset from the content-type of this + object, or the caller-specified overrides + or defaults. */ + bool charsetOverridable; + bool needUpdateMsgWinCharset; + char *cbuffer; /* Buffer used for charset conversion. */ + int32_t cbuffer_size; + + nsCOMPtr<nsIUnicodeDecoder> inputDecoder; + nsCOMPtr<nsIUnicodeEncoder> utf8Encoder; + + bool inputAutodetect; + bool initializeCharset; + int32_t lastLineInDam; + int32_t curDamOffset; + char *lineDamBuffer; + char **lineDamPtrs; +}; + +#define MimeInlineTextClassInitializer(ITYPE,CSUPER) \ + { MimeLeafClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETEXT_H_ */ diff --git a/mailnews/mime/src/mimethpl.cpp b/mailnews/mime/src/mimethpl.cpp new file mode 100644 index 000000000..9d01229f9 --- /dev/null +++ b/mailnews/mime/src/mimethpl.cpp @@ -0,0 +1,165 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* TODO: + - If you Save As File .html with this mode, you get a total mess. + - Print is untested (crashes in all modes). +*/ +/* If you fix a bug here, check, if the same is also in mimethsa, because that + class is based on this class. */ + +#include "mimethpl.h" +#include "prlog.h" +#include "msgCore.h" +#include "mimemoz2.h" +#include "nsStringGlue.h" +#include "nsIDocumentEncoder.h" // for output flags + +#define MIME_SUPERCLASS mimeInlineTextPlainClass +/* I should use the Flowed class as base (because our HTML->TXT converter + can generate flowed, and we tell it to) - this would get a bit nicer + rendering. However, that class is more picky about line endings + and I currently don't feel like splitting up the generated plaintext + into separate lines again. So, I just throw the whole message at once + at the TextPlain_parse_line function - it happens to work *g*. */ +MimeDefClass(MimeInlineTextHTMLAsPlaintext, MimeInlineTextHTMLAsPlaintextClass, + mimeInlineTextHTMLAsPlaintextClass, &MIME_SUPERCLASS); + +static int MimeInlineTextHTMLAsPlaintext_parse_line (const char *, int32_t, + MimeObject *); +static int MimeInlineTextHTMLAsPlaintext_parse_begin (MimeObject *obj); +static int MimeInlineTextHTMLAsPlaintext_parse_eof (MimeObject *, bool); +static void MimeInlineTextHTMLAsPlaintext_finalize (MimeObject *obj); + +static int +MimeInlineTextHTMLAsPlaintextClassInitialize(MimeInlineTextHTMLAsPlaintextClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "problem with superclass"); + oclass->parse_line = MimeInlineTextHTMLAsPlaintext_parse_line; + oclass->parse_begin = MimeInlineTextHTMLAsPlaintext_parse_begin; + oclass->parse_eof = MimeInlineTextHTMLAsPlaintext_parse_eof; + oclass->finalize = MimeInlineTextHTMLAsPlaintext_finalize; + + return 0; +} + +static int +MimeInlineTextHTMLAsPlaintext_parse_begin (MimeObject *obj) +{ + MimeInlineTextHTMLAsPlaintext *textHTMLPlain = + (MimeInlineTextHTMLAsPlaintext *) obj; + textHTMLPlain->complete_buffer = new nsString(); + // Let's just hope that libmime won't have the idea to call begin twice... + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); +} + +static int +MimeInlineTextHTMLAsPlaintext_parse_eof (MimeObject *obj, bool abort_p) +{ + if (obj->closed_p) + return 0; + + // This is a hack. We need to call parse_eof() of the super class to flush out any buffered data. + // We can't call it yet for our direct super class, because it would "close" the output + // (write tags such as </pre> and </div>). We'll do that after parsing the buffer. + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->superclass->parse_eof(obj, abort_p); + if (status < 0) + return status; + + MimeInlineTextHTMLAsPlaintext *textHTMLPlain = + (MimeInlineTextHTMLAsPlaintext *) obj; + + if (!textHTMLPlain || !textHTMLPlain->complete_buffer) + return 0; + + nsString& cb = *(textHTMLPlain->complete_buffer); + + // could be empty, e.g., if part isn't actually being displayed + if (cb.Length()) + { + nsString asPlaintext; + uint32_t flags = nsIDocumentEncoder::OutputFormatted + | nsIDocumentEncoder::OutputWrap + | nsIDocumentEncoder::OutputFormatFlowed + | nsIDocumentEncoder::OutputLFLineBreak + | nsIDocumentEncoder::OutputNoScriptContent + | nsIDocumentEncoder::OutputNoFramesContent + | nsIDocumentEncoder::OutputBodyOnly; + HTML2Plaintext(cb, asPlaintext, flags, 80); + + NS_ConvertUTF16toUTF8 resultCStr(asPlaintext); + // TODO parse each line independently + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line( + resultCStr.BeginWriting(), + resultCStr.Length(), + obj); + cb.Truncate(); + } + + if (status < 0) + return status; + + // Second part of the flush hack. Pretend obj wasn't closed yet, so that our super class + // gets a chance to write the closing. + bool save_closed_p = obj->closed_p; + obj->closed_p = false; + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + // Restore closed_p. + obj->closed_p = save_closed_p; + return status; +} + +void +MimeInlineTextHTMLAsPlaintext_finalize (MimeObject *obj) +{ + MimeInlineTextHTMLAsPlaintext *textHTMLPlain = + (MimeInlineTextHTMLAsPlaintext *) obj; + if (textHTMLPlain && textHTMLPlain->complete_buffer) + { + // If there's content in the buffer, make sure that we output it. + // don't care about return codes + obj->clazz->parse_eof(obj, false); + + delete textHTMLPlain->complete_buffer; + textHTMLPlain->complete_buffer = NULL; + /* It is important to zero the pointer, so we can reliably check for + the validity of it in the other functions. See above. */ + } + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj); +} + +static int +MimeInlineTextHTMLAsPlaintext_parse_line (const char *line, int32_t length, + MimeObject *obj) +{ + MimeInlineTextHTMLAsPlaintext *textHTMLPlain = + (MimeInlineTextHTMLAsPlaintext *) obj; + + if (!textHTMLPlain || !(textHTMLPlain->complete_buffer)) + { +#if DEBUG +printf("Can't output: %s\n", line); +#endif + return -1; + } + + /* + To convert HTML->TXT syncronously, I need the full source at once, + not line by line (how do you convert "<li>foo\n" to plaintext?). + parse_decoded_buffer claims to give me that, but in fact also gives + me single lines. + It might be theoretically possible to drive this asyncronously, but + I don't know, which odd circumstances might arise and how libmime + will behave then. It's not worth the trouble for me to figure this all out. + */ + nsCString linestr(line, length); + NS_ConvertUTF8toUTF16 line_ucs2(linestr.get()); + if (length && line_ucs2.IsEmpty()) + CopyASCIItoUTF16 (linestr, line_ucs2); + (textHTMLPlain->complete_buffer)->Append(line_ucs2); + + return 0; +} diff --git a/mailnews/mime/src/mimethpl.h b/mailnews/mime/src/mimethpl.h new file mode 100644 index 000000000..fc302f4f1 --- /dev/null +++ b/mailnews/mime/src/mimethpl.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* The MimeInlineTextHTMLAsPlaintext class converts HTML->TXT->HTML, i.e. + HTML to Plaintext and the result to HTML again. + This might sound crazy, maybe it is, but it is for the "View as Plaintext" + option, if the sender didn't supply a plaintext alternative (bah!). + */ + +#ifndef _MIMETHPL_H_ +#define _MIMETHPL_H_ + +#include "mimetpla.h" +#include "nsStringGlue.h" + +typedef struct MimeInlineTextHTMLAsPlaintextClass MimeInlineTextHTMLAsPlaintextClass; +typedef struct MimeInlineTextHTMLAsPlaintext MimeInlineTextHTMLAsPlaintext; + +struct MimeInlineTextHTMLAsPlaintextClass { + MimeInlineTextPlainClass plaintext; +}; + +extern MimeInlineTextHTMLAsPlaintextClass mimeInlineTextHTMLAsPlaintextClass; + +struct MimeInlineTextHTMLAsPlaintext { + MimeInlineTextPlain plaintext; + nsString *complete_buffer; // Gecko parser expects wide strings +}; + +#define MimeInlineTextHTMLAsPlaintextClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextPlainClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETHPL_H_ */ diff --git a/mailnews/mime/src/mimethsa.cpp b/mailnews/mime/src/mimethsa.cpp new file mode 100644 index 000000000..58441dee0 --- /dev/null +++ b/mailnews/mime/src/mimethsa.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Most of this code is copied from mimethpl; see there for source comments. + If you find a bug here, check that class, too. +*/ + +/* The MimeInlineTextHTMLSanitized class cleans up HTML + + This removes offending HTML features that have no business in mail. + It is a low-level stop gap for many classes of attacks, + and intended for security conscious users. + Paranoia is a feature here, and has served very well in practice. + + It has already prevented countless serious exploits. + + It pushes the HTML that we get from the sender of the message + through a sanitizer (nsTreeSanitizer), which lets only allowed tags through. + With the appropriate configuration, this protects from most of the + security and visual-formatting problems that otherwise usually come with HTML + (and which partly gave HTML in email the bad reputation that it has). + + However, due to the parsing and serializing (and later parsing again) + required, there is an inherent, significant performance hit, when doing the + santinizing here at the MIME / HTML source level. But users of this class + will most likely find it worth the cost. + */ + +#include "mimethsa.h" +#include "prmem.h" +#include "prlog.h" +#include "msgCore.h" +#include "mimemoz2.h" +#include "nsIPrefBranch.h" +#include "mimethtm.h" + +#define MIME_SUPERCLASS mimeInlineTextHTMLClass +MimeDefClass(MimeInlineTextHTMLSanitized, MimeInlineTextHTMLSanitizedClass, + mimeInlineTextHTMLSanitizedClass, &MIME_SUPERCLASS); + +static int MimeInlineTextHTMLSanitized_parse_line(const char *, int32_t, + MimeObject *); +static int MimeInlineTextHTMLSanitized_parse_begin(MimeObject *obj); +static int MimeInlineTextHTMLSanitized_parse_eof(MimeObject *, bool); +static void MimeInlineTextHTMLSanitized_finalize(MimeObject *obj); + +static int +MimeInlineTextHTMLSanitizedClassInitialize(MimeInlineTextHTMLSanitizedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *)clazz; + NS_ASSERTION(!oclass->class_initialized, "problem with superclass"); + oclass->parse_line = MimeInlineTextHTMLSanitized_parse_line; + oclass->parse_begin = MimeInlineTextHTMLSanitized_parse_begin; + oclass->parse_eof = MimeInlineTextHTMLSanitized_parse_eof; + oclass->finalize = MimeInlineTextHTMLSanitized_finalize; + + return 0; +} + +static int +MimeInlineTextHTMLSanitized_parse_begin(MimeObject *obj) +{ + MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj; + me->complete_buffer = new nsString(); + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) + return status; + + return 0; +} + +static int +MimeInlineTextHTMLSanitized_parse_eof(MimeObject *obj, bool abort_p) +{ + if (obj->closed_p) + return 0; + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) + return status; + MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj; + + // We have to cache all lines and parse the whole document at once. + // There's a useful sounding function parseFromStream(), but it only allows XML + // mimetypes, not HTML. Methinks that's because the HTML soup parser + // needs the entire doc to make sense of the gibberish that people write. + if (!me || !me->complete_buffer) + return 0; + + nsString& cb = *(me->complete_buffer); + if (cb.IsEmpty()) + return 0; + nsString sanitized; + + // Sanitize. + HTMLSanitize(cb, sanitized); + + // Write it out. + NS_ConvertUTF16toUTF8 resultCStr(sanitized); + MimeInlineTextHTML_insert_lang_div(obj, resultCStr); + // Call to MimeInlineTextHTML_remove_plaintext_tag() not needed since + // sanitization already removes that tag. + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line( + resultCStr.BeginWriting(), + resultCStr.Length(), + obj); + cb.Truncate(); + return status; +} + +void +MimeInlineTextHTMLSanitized_finalize(MimeObject *obj) +{ + MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj; + + if (me && me->complete_buffer) + { + obj->clazz->parse_eof(obj, false); + delete me->complete_buffer; + me->complete_buffer = NULL; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + +static int +MimeInlineTextHTMLSanitized_parse_line(const char *line, int32_t length, + MimeObject *obj) +{ + MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj; + + if (!me || !(me->complete_buffer)) + return -1; + + nsCString linestr(line, length); + NS_ConvertUTF8toUTF16 line_ucs2(linestr.get()); + if (length && line_ucs2.IsEmpty()) + CopyASCIItoUTF16(linestr, line_ucs2); + (me->complete_buffer)->Append(line_ucs2); + + return 0; +} diff --git a/mailnews/mime/src/mimethsa.h b/mailnews/mime/src/mimethsa.h new file mode 100644 index 000000000..42a7f81b4 --- /dev/null +++ b/mailnews/mime/src/mimethsa.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETHSA_H_ +#define _MIMETHSA_H_ + +#include "mimethtm.h" + +typedef struct MimeInlineTextHTMLSanitizedClass MimeInlineTextHTMLSanitizedClass; +typedef struct MimeInlineTextHTMLSanitized MimeInlineTextHTMLSanitized; + +struct MimeInlineTextHTMLSanitizedClass { + MimeInlineTextHTMLClass html; +}; + +extern MimeInlineTextHTMLSanitizedClass mimeInlineTextHTMLSanitizedClass; + +struct MimeInlineTextHTMLSanitized { + MimeInlineTextHTML html; + nsString *complete_buffer; // Gecko parser expects wide strings +}; + +#define MimeInlineTextHTMLSanitizedClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETHPL_H_ */ diff --git a/mailnews/mime/src/mimethtm.cpp b/mailnews/mime/src/mimethtm.cpp new file mode 100644 index 000000000..edf39b35e --- /dev/null +++ b/mailnews/mime/src/mimethtm.cpp @@ -0,0 +1,254 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mimethtm.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "prprf.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeInlineTextClass +MimeDefClass(MimeInlineTextHTML, MimeInlineTextHTMLClass, + mimeInlineTextHTMLClass, &MIME_SUPERCLASS); + +static int MimeInlineTextHTML_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextHTML_parse_eof (MimeObject *, bool); +static int MimeInlineTextHTML_parse_begin (MimeObject *obj); + +static int +MimeInlineTextHTMLClassInitialize(MimeInlineTextHTMLClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->parse_begin = MimeInlineTextHTML_parse_begin; + oclass->parse_line = MimeInlineTextHTML_parse_line; + oclass->parse_eof = MimeInlineTextHTML_parse_eof; + + return 0; +} + +static int +MimeInlineTextHTML_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)&mimeLeafClass)->parse_begin(obj); + if (status < 0) return status; + + if (!obj->output_p) return 0; + + status = MimeObject_write_separator(obj); + if (status < 0) return status; + + MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj; + + textHTML->charset = nullptr; + + /* If this HTML part has a Content-Base header, and if we're displaying + to the screen (that is, not writing this part "raw") then translate + that Content-Base header into a <BASE> tag in the HTML. + */ + if (obj->options && + obj->options->write_html_p && + obj->options->output_fn) + { + char *base_hdr = MimeHeaders_get (obj->headers, HEADER_CONTENT_BASE, + false, false); + + /* rhp - for MHTML Spec changes!!! */ + if (!base_hdr) + { + base_hdr = MimeHeaders_get (obj->headers, HEADER_CONTENT_LOCATION, false, false); + } + /* rhp - for MHTML Spec changes!!! */ + + if (base_hdr) + { + uint32_t buflen = strlen(base_hdr) + 20; + char *buf = (char *) PR_MALLOC(buflen); + const char *in; + char *out; + if (!buf) + return MIME_OUT_OF_MEMORY; + + /* The value of the Content-Base header is a number of "words". + Whitespace in this header is not significant -- it is assumed + that any real whitespace in the URL has already been encoded, + and whitespace has been inserted to allow the lines in the + mail header to be wrapped reasonably. Creators are supposed + to insert whitespace every 40 characters or less. + */ + PL_strncpyz(buf, "<BASE HREF=\"", buflen); + out = buf + strlen(buf); + + for (in = base_hdr; *in; in++) + /* ignore whitespace and quotes */ + if (!IS_SPACE(*in) && *in != '"') + *out++ = *in; + + /* Close the tag and argument. */ + *out++ = '"'; + *out++ = '>'; + *out++ = 0; + + PR_Free(base_hdr); + + status = MimeObject_write(obj, buf, strlen(buf), false); + PR_Free(buf); + if (status < 0) return status; + } + } + + return 0; +} + + +static int +MimeInlineTextHTML_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj; + + if (!obj->output_p) + return 0; + + if (!obj->options || !obj->options->output_fn) + return 0; + + if (!textHTML->charset) + { + char * cp; + // First, try to detect a charset via a META tag! + if ((cp = PL_strncasestr(line, "META", length)) && + (cp = PL_strncasestr(cp, "HTTP-EQUIV=", length - (int)(cp - line))) && + (cp = PL_strncasestr(cp, "CONTENT=", length - (int)(cp - line))) && + (cp = PL_strncasestr(cp, "CHARSET=", length - (int)(cp - line))) + ) + { + char* cp1 = cp + 8; //8 for the length of "CHARSET=" + char* cp2 = PL_strnpbrk(cp1, " \"\'", length - (int)(cp1 - line)); + if (cp2) + { + char* charset = PL_strndup(cp1, (int)(cp2 - cp1)); + + // Fix bug 101434, in this case since this parsing is a char* + // operation, a real UTF-16 or UTF-32 document won't be parse + // correctly, if it got parse, it cannot be UTF-16 nor UTF-32 + // there fore, we ignore them if somehow we got that value + // 6 == strlen("UTF-16") or strlen("UTF-32"), this will cover + // UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE + if ((charset != nullptr) && + PL_strncasecmp(charset, "UTF-16", 6) && + PL_strncasecmp(charset, "UTF-32", 6)) + { + textHTML->charset = charset; + + // write out the data without the charset part... + if (textHTML->charset) + { + int err = MimeObject_write(obj, line, cp - line, true); + if (err == 0) + err = MimeObject_write(obj, cp2, length - (int)(cp2 - line), true); + + return err; + } + } + PR_FREEIF(charset); + } + } + } + + // Now, just write out the data... + return MimeObject_write(obj, line, length, true); +} + +static int +MimeInlineTextHTML_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj; + if (obj->closed_p) return 0; + + PR_FREEIF(textHTML->charset); + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + return 0; +} + +/* + * The following function adds <div class="moz-text-html" lang="..."> or + * <div class="moz-text-html"> as the first tag following the <body> tag in the + * serialised HTML of a message. This becomes a no-op if no <body> tag is found. + */ +void +MimeInlineTextHTML_insert_lang_div(MimeObject *obj, nsCString &message) +{ + if (obj->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay && + obj->options->format_out != nsMimeOutput::nsMimeMessagePrintOutput) + return; + + // Make sure we have a <body> before we start. + int32_t index = message.Find("<body", /* ignoreCase = */ true); + if (index == kNotFound) + return; + index = message.FindChar('>', index) + 1; + + // Insert <div class="moz-text-html" lang="..."> for the following two purposes: + // 1) Users can configure their HTML display via CSS for .moz-text-html. + // 2) The language group in the 'lang' attribure is used by Gecko to determine + // which font to use. + int32_t fontSize; // default font size + int32_t fontSizePercentage; // size percentage + nsAutoCString fontLang; // langgroup of the font. + if (NS_SUCCEEDED(GetMailNewsFont(obj, false, &fontSize, &fontSizePercentage, fontLang))) + { + message.Insert(NS_LITERAL_CSTRING("<div class=\"moz-text-html\" lang=\"") + + fontLang + + NS_LITERAL_CSTRING("\">"), + index); + } + else + { + message.Insert(NS_LITERAL_CSTRING("<div class=\"moz-text-html\">"), + index); + } + + index = message.RFind("</body>", /* ignoreCase = */ true); + if (index != kNotFound) + message.Insert(NS_LITERAL_CSTRING("</div>"), index); +} + +/* + * The following function replaces <plaintext> tags with <x-plaintext>. + * <plaintext> is a funny beast: It leads to everything following it + * being displayed verbatim, even a </plaintext> tag is ignored. + */ +void +MimeInlineTextHTML_remove_plaintext_tag(MimeObject *obj, nsCString &message) +{ + if (obj->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay && + obj->options->format_out != nsMimeOutput::nsMimeMessagePrintOutput) + return; + + // Replace all <plaintext> and </plaintext> tags. + int32_t index = 0; + bool replaced = false; + while ((index = message.Find("<plaintext", /* ignoreCase = */ true, index)) != kNotFound) { + message.Insert("x-", index+1); + index += 12; + replaced = true; + } + if (replaced) { + index = 0; + while ((index = message.Find("</plaintext", /* ignoreCase = */ true, index)) != kNotFound) { + message.Insert("x-", index+2); + index += 13; + } + } +} + diff --git a/mailnews/mime/src/mimethtm.h b/mailnews/mime/src/mimethtm.h new file mode 100644 index 000000000..fbd730cd2 --- /dev/null +++ b/mailnews/mime/src/mimethtm.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETHTM_H_ +#define _MIMETHTM_H_ + +#include "mimetext.h" + +/* The MimeInlineTextHTML class implements the text/html MIME content type. + */ + +typedef struct MimeInlineTextHTMLClass MimeInlineTextHTMLClass; +typedef struct MimeInlineTextHTML MimeInlineTextHTML; + +struct MimeInlineTextHTMLClass { + MimeInlineTextClass text; +}; + +extern MimeInlineTextHTMLClass mimeInlineTextHTMLClass; + +struct MimeInlineTextHTML { + MimeInlineText text; + char *charset; /* If we sniffed a charset, do some converting! */ +}; + +#define MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +void +MimeInlineTextHTML_insert_lang_div(MimeObject *obj, nsCString &message); +void +MimeInlineTextHTML_remove_plaintext_tag(MimeObject *obj, nsCString &message); +#endif /* _MIMETHTM_H_ */ diff --git a/mailnews/mime/src/mimetpfl.cpp b/mailnews/mime/src/mimetpfl.cpp new file mode 100644 index 000000000..da7eff413 --- /dev/null +++ b/mailnews/mime/src/mimetpfl.cpp @@ -0,0 +1,630 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimetpfl.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "mozITXTToHTMLConv.h" +#include "nsStringGlue.h" +#include "nsMimeStringResources.h" +#include "nsIPrefBranch.h" +#include "nsIServiceManager.h" +#include "mimemoz2.h" +#include "prprf.h" +#include "nsMsgI18N.h" + +static const uint32_t kSpacesForATab = 4; // Must be at least 1. + +#define MIME_SUPERCLASS mimeInlineTextClass +MimeDefClass(MimeInlineTextPlainFlowed, MimeInlineTextPlainFlowedClass, + mimeInlineTextPlainFlowedClass, &MIME_SUPERCLASS); + +static int MimeInlineTextPlainFlowed_parse_begin (MimeObject *); +static int MimeInlineTextPlainFlowed_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextPlainFlowed_parse_eof (MimeObject *, bool); + +static MimeInlineTextPlainFlowedExData *MimeInlineTextPlainFlowedExDataList = nullptr; + +// From mimetpla.cpp +extern "C" void MimeTextBuildPrefixCSS( + int32_t quotedSizeSetting, // mail.quoted_size + int32_t quotedStyleSetting, // mail.quoted_style + char *citationColor, // mail.citation_color + nsACString &style); +// Definition below +static +nsresult Line_convert_whitespace(const nsString& a_line, + const bool a_convert_all_whitespace, + nsString& a_out_line); + +static int +MimeInlineTextPlainFlowedClassInitialize(MimeInlineTextPlainFlowedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "class not initialized"); + oclass->parse_begin = MimeInlineTextPlainFlowed_parse_begin; + oclass->parse_line = MimeInlineTextPlainFlowed_parse_line; + oclass->parse_eof = MimeInlineTextPlainFlowed_parse_eof; + + return 0; +} + +static int +MimeInlineTextPlainFlowed_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + status = MimeObject_write(obj, "", 0, true); /* force out any separators... */ + if(status<0) return status; + + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // The output will be inserted in the composer as quotation + bool plainHTML = quoting || (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs); + // Just good(tm) HTML. No reliance on CSS. + + // Setup the data structure that is connected to the actual document + // Saved in a linked list in case this is called with several documents + // at the same time. + /* This memory is freed when parse_eof is called. So it better be! */ + struct MimeInlineTextPlainFlowedExData *exdata = + (MimeInlineTextPlainFlowedExData *)PR_MALLOC(sizeof(struct MimeInlineTextPlainFlowedExData)); + if(!exdata) return MIME_OUT_OF_MEMORY; + + MimeInlineTextPlainFlowed *text = (MimeInlineTextPlainFlowed *) obj; + + // Link it up. + exdata->next = MimeInlineTextPlainFlowedExDataList; + MimeInlineTextPlainFlowedExDataList = exdata; + + // Initialize data + + exdata->ownerobj = obj; + exdata->inflow = false; + exdata->quotelevel = 0; + exdata->isSig = false; + + // check for DelSp=yes (RFC 3676) + + char *content_type_row = + (obj->headers + ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, false, false) + : 0); + char *content_type_delsp = + (content_type_row + ? MimeHeaders_get_parameter(content_type_row, "delsp", NULL,NULL) + : 0); + ((MimeInlineTextPlainFlowed *)obj)->delSp = content_type_delsp && !PL_strcasecmp(content_type_delsp, "yes"); + PR_Free(content_type_delsp); + PR_Free(content_type_row); + + // Get Prefs for viewing + + exdata->fixedwidthfont = false; + // Quotes + text->mQuotedSizeSetting = 0; // mail.quoted_size + text->mQuotedStyleSetting = 0; // mail.quoted_style + text->mCitationColor = nullptr; // mail.citation_color + text->mStripSig = true; // mail.strip_sig_on_reply + + nsIPrefBranch *prefBranch = GetPrefBranch(obj->options); + if (prefBranch) + { + prefBranch->GetIntPref("mail.quoted_size", &(text->mQuotedSizeSetting)); + prefBranch->GetIntPref("mail.quoted_style", &(text->mQuotedStyleSetting)); + prefBranch->GetCharPref("mail.citation_color", &(text->mCitationColor)); + prefBranch->GetBoolPref("mail.strip_sig_on_reply", &(text->mStripSig)); + mozilla::DebugOnly<nsresult> rv = + prefBranch->GetBoolPref("mail.fixed_width_messages", &(exdata->fixedwidthfont)); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get pref"); + // Check at least the success of one + } + + // Get font + // only used for viewing (!plainHTML) + nsAutoCString fontstyle; + nsAutoCString fontLang; // langgroup of the font + + + // generic font-family name ( -moz-fixed for fixed font and NULL for + // variable font ) is sufficient now that bug 105199 has been fixed. + + if (exdata->fixedwidthfont) + fontstyle = "font-family: -moz-fixed"; + + if (nsMimeOutput::nsMimeMessageBodyDisplay == obj->options->format_out || + nsMimeOutput::nsMimeMessagePrintOutput == obj->options->format_out) + { + int32_t fontSize; // default font size + int32_t fontSizePercentage; // size percentage + nsresult rv = GetMailNewsFont(obj, exdata->fixedwidthfont, + &fontSize, &fontSizePercentage, fontLang); + if (NS_SUCCEEDED(rv)) + { + if ( ! fontstyle.IsEmpty() ) { + fontstyle += "; "; + } + fontstyle += "font-size: "; + fontstyle.AppendInt(fontSize); + fontstyle += "px;"; + } + } + + // Opening <div>. + if (!quoting) + /* 4.x' editor can't break <div>s (e.g. to interleave comments). + We'll add the class to the <blockquote type=cite> later. */ + { + nsAutoCString openingDiv("<div class=\"moz-text-flowed\""); + // We currently have to add formatting here. :-( + if (!plainHTML && !fontstyle.IsEmpty()) + { + openingDiv += " style=\""; + openingDiv += fontstyle; + openingDiv += '"'; + } + if (!plainHTML && !fontLang.IsEmpty()) + { + openingDiv += " lang=\""; + openingDiv += fontLang; + openingDiv += '\"'; + } + openingDiv += ">"; + status = MimeObject_write(obj, openingDiv.get(), openingDiv.Length(), false); + if (status < 0) return status; + } + + return 0; +} + +static int +MimeInlineTextPlainFlowed_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + struct MimeInlineTextPlainFlowedExData *exdata = nullptr; + + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // see above + + // Has this method already been called for this object? + // In that case return. + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) goto EarlyOut; + + // Look up and unlink "our" extended data structure + // We do it in the beginning so that if an error occur, we can + // just free |exdata|. + struct MimeInlineTextPlainFlowedExData **prevexdata; + prevexdata = &MimeInlineTextPlainFlowedExDataList; + + while ((exdata = *prevexdata) != nullptr) { + if (exdata->ownerobj == obj) { + // Fill hole + *prevexdata = exdata->next; + break; + } + prevexdata = &exdata->next; + } + NS_ASSERTION (exdata, "The extra data has disappeared!"); + + if (!obj->output_p) { + status = 0; + goto EarlyOut; + } + + for(; exdata->quotelevel > 0; exdata->quotelevel--) { + status = MimeObject_write(obj, "</blockquote>", 13, false); + if(status<0) goto EarlyOut; + } + + if (exdata->isSig && !quoting) { + status = MimeObject_write(obj, "</div>", 6, false); // .moz-txt-sig + if (status<0) goto EarlyOut; + } + if (!quoting) // HACK (see above) + { + status = MimeObject_write(obj, "</div>", 6, false); // .moz-text-flowed + if (status<0) goto EarlyOut; + } + + status = 0; + +EarlyOut: + PR_Free(exdata); + + // Free mCitationColor + MimeInlineTextPlainFlowed *text = (MimeInlineTextPlainFlowed *) obj; + PR_FREEIF(text->mCitationColor); + text->mCitationColor = nullptr; + + return status; +} + + +static int +MimeInlineTextPlainFlowed_parse_line (const char *aLine, int32_t length, MimeObject *obj) +{ + int status; + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // see above + bool plainHTML = quoting || (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs); + // see above + + struct MimeInlineTextPlainFlowedExData *exdata; + exdata = MimeInlineTextPlainFlowedExDataList; + while(exdata && (exdata->ownerobj != obj)) { + exdata = exdata->next; + } + + NS_ASSERTION(exdata, "The extra data has disappeared!"); + + NS_ASSERTION(length > 0, "zero length"); + if (length <= 0) return 0; + + uint32_t linequotelevel = 0; + nsAutoCString real_line(aLine, length); + char *line = real_line.BeginWriting(); + const char *linep = real_line.BeginReading(); + // Space stuffed? + if(' ' == *linep) { + linep++; + } else { + // count '>':s before the first non-'>' + while('>' == *linep) { + linep++; + linequotelevel++; + } + // Space stuffed? + if(' ' == *linep) { + linep++; + } + } + + // Look if the last character (after stripping ending end + // of lines and quoting stuff) is a SPACE. If it is, we are looking at a + // flowed line. Normally we assume that the last two chars + // are CR and LF as said in RFC822, but that doesn't seem to + // be the case always. + bool flowed = false; + bool sigSeparator = false; + int32_t index = length-1; + while(index >= 0 && ('\r' == line[index] || '\n' == line[index])) { + index--; + } + if (index > linep - line && ' ' == line[index]) + /* Ignore space stuffing, i.e. lines with just + (quote marks and) a space count as empty */ + { + flowed = true; + sigSeparator = (index - (linep - line) + 1 == 3) && !strncmp(linep, "-- ", 3); + if (((MimeInlineTextPlainFlowed *) obj)->delSp && !sigSeparator) + /* If line is flowed and DelSp=yes, logically + delete trailing space. Line consisting of + dash dash space ("-- "), commonly used as + signature separator, gets special handling + (RFC 3676) */ + { + length = index; + line[index] = '\0'; + } + } + + if (obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_output_fn) + { + return obj->options->decompose_file_output_fn(line, + length, + obj->options->stream_closure); + } + + mozITXTToHTMLConv *conv = GetTextConverter(obj->options); + + bool skipConversion = !conv || + (obj->options && obj->options->force_user_charset); + + nsAutoString lineSource; + nsString lineResult; + + char *mailCharset = NULL; + nsresult rv; + + if (!skipConversion) + { + // Convert only if the source string is not empty + if (length - (linep - line) > 0) + { + uint32_t whattodo = obj->options->whattodo; + if (plainHTML) + { + if (quoting) + whattodo = 0; + else + whattodo = whattodo & ~mozITXTToHTMLConv::kGlyphSubstitution; + /* Do recognition for the case, the result is viewed in + Mozilla, but not GlyphSubstitution, because other UAs + might not be able to display the glyphs. */ + } + + const nsDependentCSubstring& inputStr = Substring(linep, linep + (length - (linep - line))); + + // For 'SaveAs', |line| is in |mailCharset|. + // convert |line| to UTF-16 before 'html'izing (calling ScanTXT()) + if (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) + { + // Get the mail charset of this message. + MimeInlineText *inlinetext = (MimeInlineText *) obj; + if (!inlinetext->initializeCharset) + ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj); + mailCharset = inlinetext->charset; + if (mailCharset && *mailCharset) { + rv = nsMsgI18NConvertToUnicode(mailCharset, PromiseFlatCString(inputStr), lineSource); + NS_ENSURE_SUCCESS(rv, -1); + } + else // this probably never happens... + CopyUTF8toUTF16(inputStr, lineSource); + } + else // line is in UTF-8 + CopyUTF8toUTF16(inputStr, lineSource); + + // This is the main TXT to HTML conversion: + // escaping (very important), eventually recognizing etc. + rv = conv->ScanTXT(lineSource.get(), whattodo, getter_Copies(lineResult)); + NS_ENSURE_SUCCESS(rv, -1); + } + } + else + { + CopyUTF8toUTF16(nsDependentCString(line, length), lineResult); + status = 0; + } + + nsAutoCString preface; + + /* Correct number of blockquotes */ + int32_t quoteleveldiff=linequotelevel - exdata->quotelevel; + if((quoteleveldiff != 0) && flowed && exdata->inflow) { + // From RFC 2646 4.5 + // The receiver SHOULD handle this error by using the 'quote-depth-wins' rule, + // which is to ignore the flowed indicator and treat the line as fixed. That + // is, the change in quote depth ends the paragraph. + + // We get that behaviour by just going on. + } + + // Cast so we have access to the prefs we need. + MimeInlineTextPlainFlowed *tObj = (MimeInlineTextPlainFlowed *) obj; + while(quoteleveldiff>0) { + quoteleveldiff--; + preface += "<blockquote type=cite"; + + nsAutoCString style; + MimeTextBuildPrefixCSS(tObj->mQuotedSizeSetting, tObj->mQuotedStyleSetting, + tObj->mCitationColor, style); + if (!plainHTML && !style.IsEmpty()) + { + preface += " style=\""; + preface += style; + preface += '"'; + } + preface += '>'; + } + while(quoteleveldiff<0) { + quoteleveldiff++; + preface += "</blockquote>"; + } + exdata->quotelevel = linequotelevel; + + nsAutoString lineResult2; + + if(flowed) { + // Check RFC 2646 "4.3. Usenet Signature Convention": "-- "+CRLF is + // not a flowed line + if (sigSeparator) + { + if (linequotelevel > 0 || exdata->isSig) + { + preface += "-- <br>"; + } else { + exdata->isSig = true; + preface += "<div class=\"moz-txt-sig\"><span class=\"moz-txt-tag\">" + "-- <br></span>"; + } + } else { + Line_convert_whitespace(lineResult, false /* Allow wraps */, + lineResult2); + } + + exdata->inflow=true; + } else { + // Fixed paragraph. + Line_convert_whitespace(lineResult, + !plainHTML && !obj->options->wrap_long_lines_p + /* If wrap, convert all spaces but the last in + a row into nbsp, otherwise all. */, + lineResult2); + lineResult2.AppendLiteral("<br>"); + exdata->inflow = false; + } // End Fixed line + + if (!(exdata->isSig && quoting && tObj->mStripSig)) + { + status = MimeObject_write(obj, preface.get(), preface.Length(), true); + if (status < 0) return status; + nsAutoCString outString; + if (obj->options->format_out != nsMimeOutput::nsMimeMessageSaveAs || + !mailCharset || !*mailCharset) + CopyUTF16toUTF8(lineResult2, outString); + else + { // convert back to mailCharset before writing. + rv = nsMsgI18NConvertFromUnicode(mailCharset, lineResult2, outString); + NS_ENSURE_SUCCESS(rv, -1); + } + status = MimeObject_write(obj, outString.get(), outString.Length(), true); + return status; + } + return 0; +} + + +/** + * Maintains a small state machine with three states. "Not in tag", + * "In tag, but not in quote" and "In quote inside a tag". It also + * remembers what character started the quote (" or '). The state + * variables are kept outside this function and are included as + * parameters. + * + * @param in/out a_in_tag, if we are in a tag right now. + * @param in/out a_in_quote_in_tag, if we are in a quote inside a tag. + * @param in/out a_quote_char, the kind of quote (" or '). + * @param in a_current_char, the next char. It decides which state + * will be next. + */ +static void Update_in_tag_info(bool *a_in_tag, /* IN/OUT */ + bool *a_in_quote_in_tag, /* IN/OUT */ + char16_t *a_quote_char, /* IN/OUT (pointer to single char) */ + char16_t a_current_char) /* IN */ +{ + if(*a_in_tag) { + // Keep us informed of what's quoted so that we + // don't end the tag too soon. For instance in + // <font face="weird>font<name"> + if(*a_in_quote_in_tag) { + // We are in a quote. A quote is ended by the same + // character that started it ('...' or "...") + if(*a_quote_char == a_current_char) { + *a_in_quote_in_tag = false; + } + } else { + // We are not currently in a quote, but we may enter + // one right this minute. + switch(a_current_char) { + case '"': + case '\'': + *a_in_quote_in_tag = true; + *a_quote_char = a_current_char; + break; + case '>': + // Tag is ended + *a_in_tag = false; + break; + default: + // Do nothing + ; + } + } + return; + } + + // Not in a tag. + // Check if we are entering a tag by looking for '<'. + // All normal occurrences of '<' should have been replaced + // by < + if ('<' == a_current_char) { + *a_in_tag = true; + *a_in_quote_in_tag = false; + } +} + + +/** + * Converts whitespace to | |, if appropriate. + * + * @param in a_current_char, the char to convert. + * @param in a_next_char, the char after the char to convert. + * @param in a_convert_all_whitespace, if also the last whitespace + * in a sequence should be + * converted. + * @param out a_out_string, result will be appended. +*/ +static void Convert_whitespace(const char16_t a_current_char, + const char16_t a_next_char, + const bool a_convert_all_whitespace, + nsString& a_out_string) +{ + NS_ASSERTION('\t' == a_current_char || ' ' == a_current_char, + "Convert_whitespace got something else than a whitespace!"); + + uint32_t number_of_nbsp = 0; + uint32_t number_of_space = 1; // Assume we're going to output one space. + + /* Output the spaces for a tab. All but the last are made into . + The last is treated like a normal space. + */ + if('\t' == a_current_char) { + number_of_nbsp = kSpacesForATab - 1; + } + + if(' ' == a_next_char || '\t' == a_next_char || a_convert_all_whitespace) { + number_of_nbsp += number_of_space; + number_of_space = 0; + } + + while(number_of_nbsp--) { + a_out_string.AppendLiteral(" "); + } + + while(number_of_space--) { + // a_out_string += ' '; gives error + a_out_string.AppendLiteral(" "); + } + + return; +} + +/** + * Passes over the line and converts whitespace to | |, if appropriate + * + * @param in a_convert_all_whitespace, if also the last whitespace + * in a sequence should be + * converted. + * @param out a_out_string, result will be appended. +*/ +static +nsresult Line_convert_whitespace(const nsString& a_line, + const bool a_convert_all_whitespace, + nsString& a_out_line) +{ + bool in_tag = false; + bool in_quote_in_tag = false; + char16_t quote_char; + + for (uint32_t i = 0; a_line.Length() > i; i++) + { + const char16_t ic = a_line[i]; // Cache + + Update_in_tag_info(&in_tag, &in_quote_in_tag, "e_char, ic); + // We don't touch anything inside a tag. + if (!in_tag) { + if (ic == ' ' || ic == '\t') { + // Convert the whitespace to something appropriate + Convert_whitespace(ic, a_line.Length() > i + 1 ? a_line[i + 1] : '\0', + a_convert_all_whitespace || + !i, // First char on line + a_out_line); + } else if (ic == '\r') { + // strip CRs + } else { + a_out_line += ic; + } + } else { + // In tag. Don't change anything + a_out_line += ic; + } + } + return NS_OK; +} diff --git a/mailnews/mime/src/mimetpfl.h b/mailnews/mime/src/mimetpfl.h new file mode 100644 index 000000000..c3b20c445 --- /dev/null +++ b/mailnews/mime/src/mimetpfl.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETPFL_H_ +#define _MIMETPFL_H_ + +#include "mimetext.h" + +/* The MimeInlineTextPlainFlowed class implements the + text/plain MIME content type for the special case of a supplied + format=flowed. See + ftp://ftp.ietf.org/internet-drafts/draft-gellens-format-06.txt for + more information. + */ + +typedef struct MimeInlineTextPlainFlowedClass MimeInlineTextPlainFlowedClass; +typedef struct MimeInlineTextPlainFlowed MimeInlineTextPlainFlowed; + +struct MimeInlineTextPlainFlowedClass { + MimeInlineTextClass text; +}; + +extern MimeInlineTextPlainFlowedClass mimeInlineTextPlainFlowedClass; + +struct MimeInlineTextPlainFlowed { + MimeInlineText text; + bool delSp; // DelSp=yes (RFC 3676) + int32_t mQuotedSizeSetting; // mail.quoted_size + int32_t mQuotedStyleSetting; // mail.quoted_style + char *mCitationColor; // mail.citation_color + bool mStripSig; // mail.strip_sig_on_reply +}; + + +/* + * Made to contain information to be kept during the whole message parsing. + */ +struct MimeInlineTextPlainFlowedExData { + struct MimeObject *ownerobj; /* The owner of this struct */ + bool inflow; /* If we currently are in flow */ + bool fixedwidthfont; /* If we output text for fixed width font */ + uint32_t quotelevel; /* How deep is your love, uhr, quotelevel I meen. */ + bool isSig; // we're currently in a signature + struct MimeInlineTextPlainFlowedExData *next; +}; + +#define MimeInlineTextPlainFlowedClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETPFL_H_ */ diff --git a/mailnews/mime/src/mimetpla.cpp b/mailnews/mime/src/mimetpla.cpp new file mode 100644 index 000000000..72a974a73 --- /dev/null +++ b/mailnews/mime/src/mimetpla.cpp @@ -0,0 +1,451 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimetpla.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "mozITXTToHTMLConv.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsStringGlue.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsIServiceManager.h" +#include "nsIPrefBranch.h" +#include "prprf.h" +#include "nsMsgI18N.h" + +#define MIME_SUPERCLASS mimeInlineTextClass +MimeDefClass(MimeInlineTextPlain, MimeInlineTextPlainClass, + mimeInlineTextPlainClass, &MIME_SUPERCLASS); + +static int MimeInlineTextPlain_parse_begin (MimeObject *); +static int MimeInlineTextPlain_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextPlain_parse_eof (MimeObject *, bool); + +static int +MimeInlineTextPlainClassInitialize(MimeInlineTextPlainClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "class not initialized"); + oclass->parse_begin = MimeInlineTextPlain_parse_begin; + oclass->parse_line = MimeInlineTextPlain_parse_line; + oclass->parse_eof = MimeInlineTextPlain_parse_eof; + return 0; +} + +extern "C" +void +MimeTextBuildPrefixCSS(int32_t quotedSizeSetting, // mail.quoted_size + int32_t quotedStyleSetting, // mail.quoted_style + char *citationColor, // mail.citation_color + nsACString &style) +{ + switch (quotedStyleSetting) + { + case 0: // regular + break; + case 1: // bold + style.Append("font-weight: bold; "); + break; + case 2: // italic + style.Append("font-style: italic; "); + break; + case 3: // bold-italic + style.Append("font-weight: bold; font-style: italic; "); + break; + } + + switch (quotedSizeSetting) + { + case 0: // regular + break; + case 1: // large + style.Append("font-size: large; "); + break; + case 2: // small + style.Append("font-size: small; "); + break; + } + + if (citationColor && *citationColor) + { + style += "color: "; + style += citationColor; + style += ';'; + } +} + +static int +MimeInlineTextPlain_parse_begin (MimeObject *obj) +{ + int status = 0; + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // The output will be inserted in the composer as quotation + bool plainHTML = quoting || (obj->options && + (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs)); + // Just good(tm) HTML. No reliance on CSS. + bool rawPlainText = obj->options && + (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer + || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach); + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + if (!obj->output_p) return 0; + + if (obj->options && + obj->options->write_html_p && + obj->options->output_fn) + { + MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj; + text->mCiteLevel = 0; + + // Get the prefs + + // Quoting + text->mBlockquoting = true; // mail.quoteasblock + + // Viewing + text->mQuotedSizeSetting = 0; // mail.quoted_size + text->mQuotedStyleSetting = 0; // mail.quoted_style + text->mCitationColor = nullptr; // mail.citation_color + text->mStripSig = true; // mail.strip_sig_on_reply + bool graphicalQuote = true; // mail.quoted_graphical + + nsIPrefBranch *prefBranch = GetPrefBranch(obj->options); + if (prefBranch) + { + prefBranch->GetIntPref("mail.quoted_size", &(text->mQuotedSizeSetting)); + prefBranch->GetIntPref("mail.quoted_style", &(text->mQuotedStyleSetting)); + prefBranch->GetCharPref("mail.citation_color", &(text->mCitationColor)); + prefBranch->GetBoolPref("mail.strip_sig_on_reply", &(text->mStripSig)); + prefBranch->GetBoolPref("mail.quoted_graphical", &graphicalQuote); + prefBranch->GetBoolPref("mail.quoteasblock", &(text->mBlockquoting)); + } + + if (!rawPlainText) + { + // Get font + // only used for viewing (!plainHTML) + nsAutoCString fontstyle; + nsAutoCString fontLang; // langgroup of the font + + // generic font-family name ( -moz-fixed for fixed font and NULL for + // variable font ) is sufficient now that bug 105199 has been fixed. + + if (!obj->options->variable_width_plaintext_p) + fontstyle = "font-family: -moz-fixed"; + + if (nsMimeOutput::nsMimeMessageBodyDisplay == obj->options->format_out || + nsMimeOutput::nsMimeMessagePrintOutput == obj->options->format_out) + { + int32_t fontSize; // default font size + int32_t fontSizePercentage; // size percentage + nsresult rv = GetMailNewsFont(obj, + !obj->options->variable_width_plaintext_p, + &fontSize, &fontSizePercentage, fontLang); + if (NS_SUCCEEDED(rv)) + { + if ( ! fontstyle.IsEmpty() ) { + fontstyle += "; "; + } + fontstyle += "font-size: "; + fontstyle.AppendInt(fontSize); + fontstyle += "px;"; + } + } + + // Opening <div>. We currently have to add formatting here. :-( + nsAutoCString openingDiv; + if (!quoting) + /* 4.x' editor can't break <div>s (e.g. to interleave comments). + We'll add the class to the <blockquote type=cite> later. */ + { + openingDiv = "<div class=\"moz-text-plain\""; + if (!plainHTML) + { + if (obj->options->wrap_long_lines_p) + openingDiv += " wrap=true"; + else + openingDiv += " wrap=false"; + + if (graphicalQuote) + openingDiv += " graphical-quote=true"; + else + openingDiv += " graphical-quote=false"; + + if (!fontstyle.IsEmpty()) + { + openingDiv += " style=\""; + openingDiv += fontstyle; + openingDiv += '\"'; + } + if (!fontLang.IsEmpty()) + { + openingDiv += " lang=\""; + openingDiv += fontLang; + openingDiv += '\"'; + } + } + openingDiv += "><pre wrap>\n"; + } + else + openingDiv = "<pre wrap>\n"; + + /* text/plain objects always have separators before and after them. + Note that this is not the case for text/enriched objects. */ + status = MimeObject_write_separator(obj); + if (status < 0) return status; + + status = MimeObject_write(obj, openingDiv.get(), openingDiv.Length(), true); + if (status < 0) return status; + } + } + + return 0; +} + +static int +MimeInlineTextPlain_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + + // Has this method already been called for this object? + // In that case return. + if (obj->closed_p) return 0; + + nsCString citationColor; + MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj; + if (text && text->mCitationColor) + citationColor.Adopt(text->mCitationColor); + + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // see above + + bool rawPlainText = obj->options && + (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer + || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach); + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + if (!obj->output_p) return 0; + + if (obj->options && + obj->options->write_html_p && + obj->options->output_fn && + !abort_p && !rawPlainText) + { + MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj; + if (text->mIsSig && !quoting) + { + status = MimeObject_write(obj, "</div>", 6, false); // .moz-txt-sig + if (status < 0) return status; + } + status = MimeObject_write(obj, "</pre>", 6, false); + if (status < 0) return status; + if (!quoting) + { + status = MimeObject_write(obj, "</div>", 6, false); + // .moz-text-plain + if (status < 0) return status; + } + + /* text/plain objects always have separators before and after them. + Note that this is not the case for text/enriched objects. + */ + status = MimeObject_write_separator(obj); + if (status < 0) return status; + } + + return 0; +} + + +static int +MimeInlineTextPlain_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + int status; + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // see above + bool plainHTML = quoting || (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs); + // see above + + bool rawPlainText = obj->options && + (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer + || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach); + + // this routine gets called for every line of data that comes through the + // mime converter. It's important to make sure we are efficient with + // how we allocate memory in this routine. be careful if you go to add + // more to this routine. + + NS_ASSERTION(length > 0, "zero length"); + if (length <= 0) return 0; + + mozITXTToHTMLConv *conv = GetTextConverter(obj->options); + MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj; + + bool skipConversion = !conv || rawPlainText || + (obj->options && obj->options->force_user_charset); + + char *mailCharset = NULL; + nsresult rv; + + if (!skipConversion) + { + nsDependentCString inputStr(line, length); + nsAutoString lineSourceStr; + + // For 'SaveAs', |line| is in |mailCharset|. + // convert |line| to UTF-16 before 'html'izing (calling ScanTXT()) + if (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) + { // Get the mail charset of this message. + MimeInlineText *inlinetext = (MimeInlineText *) obj; + if (!inlinetext->initializeCharset) + ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj); + mailCharset = inlinetext->charset; + if (mailCharset && *mailCharset) { + rv = nsMsgI18NConvertToUnicode(mailCharset, inputStr, lineSourceStr); + NS_ENSURE_SUCCESS(rv, -1); + } + else // this probably never happens ... + CopyUTF8toUTF16(inputStr, lineSourceStr); + } + else // line is in UTF-8 + CopyUTF8toUTF16(inputStr, lineSourceStr); + + nsAutoCString prefaceResultStr; // Quoting stuff before the real text + + // Recognize quotes + uint32_t oldCiteLevel = text->mCiteLevel; + uint32_t logicalLineStart = 0; + rv = conv->CiteLevelTXT(lineSourceStr.get(), + &logicalLineStart, &(text->mCiteLevel)); + NS_ENSURE_SUCCESS(rv, -1); + + // Find out, which recognitions to do + uint32_t whattodo = obj->options->whattodo; + if (plainHTML) + { + if (quoting) + whattodo = 0; // This is done on Send. Don't do it twice. + else + whattodo = whattodo & ~mozITXTToHTMLConv::kGlyphSubstitution; + /* Do recognition for the case, the result is viewed in + Mozilla, but not GlyphSubstitution, because other UAs + might not be able to display the glyphs. */ + if (!text->mBlockquoting) + text->mCiteLevel = 0; + } + + // Write blockquote + if (text->mCiteLevel > oldCiteLevel) + { + prefaceResultStr += "</pre>"; + for (uint32_t i = 0; i < text->mCiteLevel - oldCiteLevel; i++) + { + nsAutoCString style; + MimeTextBuildPrefixCSS(text->mQuotedSizeSetting, text->mQuotedStyleSetting, + text->mCitationColor, style); + if (!plainHTML && !style.IsEmpty()) + { + prefaceResultStr += "<blockquote type=cite style=\""; + prefaceResultStr += style; + prefaceResultStr += "\">"; + } + else + prefaceResultStr += "<blockquote type=cite>"; + } + prefaceResultStr += "<pre wrap>\n"; + } + else if (text->mCiteLevel < oldCiteLevel) + { + prefaceResultStr += "</pre>"; + for (uint32_t i = 0; i < oldCiteLevel - text->mCiteLevel; i++) + prefaceResultStr += "</blockquote>"; + prefaceResultStr += "<pre wrap>\n"; + } + + // Write plain text quoting tags + if (logicalLineStart != 0 && !(plainHTML && text->mBlockquoting)) + { + if (!plainHTML) + prefaceResultStr += "<span class=\"moz-txt-citetags\">"; + + nsString citeTagsSource(StringHead(lineSourceStr, logicalLineStart)); + + // Convert to HTML + nsString citeTagsResultUnichar; + rv = conv->ScanTXT(citeTagsSource.get(), 0 /* no recognition */, + getter_Copies(citeTagsResultUnichar)); + if (NS_FAILED(rv)) return -1; + + prefaceResultStr.Append(NS_ConvertUTF16toUTF8(citeTagsResultUnichar)); + if (!plainHTML) + prefaceResultStr += "</span>"; + } + + + // recognize signature + if ((lineSourceStr.Length() >= 4) + && lineSourceStr.First() == '-' + && Substring(lineSourceStr, 0, 3).EqualsLiteral("-- ") + && (lineSourceStr[3] == '\r' || lineSourceStr[3] == '\n') ) + { + text->mIsSig = true; + if (!quoting) + prefaceResultStr += "<div class=\"moz-txt-sig\">"; + } + + + /* This is the main TXT to HTML conversion: + escaping (very important), eventually recognizing etc. */ + nsString lineResultUnichar; + + rv = conv->ScanTXT(lineSourceStr.get() + logicalLineStart, + whattodo, getter_Copies(lineResultUnichar)); + NS_ENSURE_SUCCESS(rv, -1); + + if (!(text->mIsSig && quoting && text->mStripSig)) + { + status = MimeObject_write(obj, prefaceResultStr.get(), prefaceResultStr.Length(), true); + if (status < 0) return status; + nsAutoCString outString; + if (obj->options->format_out != nsMimeOutput::nsMimeMessageSaveAs || + !mailCharset || !*mailCharset) + CopyUTF16toUTF8(lineResultUnichar, outString); + else + { // convert back to mailCharset before writing. + rv = nsMsgI18NConvertFromUnicode(mailCharset, + lineResultUnichar, outString); + NS_ENSURE_SUCCESS(rv, -1); + } + + status = MimeObject_write(obj, outString.get(), outString.Length(), true); + } + else + { + status = 0; + } + } + else + { + status = MimeObject_write(obj, line, length, true); + } + + return status; +} + diff --git a/mailnews/mime/src/mimetpla.h b/mailnews/mime/src/mimetpla.h new file mode 100644 index 000000000..b846403bc --- /dev/null +++ b/mailnews/mime/src/mimetpla.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* The MimeInlineTextPlain class implements the text/plain MIME content type, + and is also used for all otherwise-unknown text/ subtypes. + */ + +#ifndef _MIMETPLA_H_ +#define _MIMETPLA_H_ + +#include "mimetext.h" + +typedef struct MimeInlineTextPlainClass MimeInlineTextPlainClass; +typedef struct MimeInlineTextPlain MimeInlineTextPlain; + +struct MimeInlineTextPlainClass { + MimeInlineTextClass text; +}; + +extern MimeInlineTextPlainClass mimeInlineTextPlainClass; + +struct MimeInlineTextPlain { + MimeInlineText text; + uint32_t mCiteLevel; + bool mBlockquoting; + //bool mInsideQuote; + int32_t mQuotedSizeSetting; // mail.quoted_size + int32_t mQuotedStyleSetting; // mail.quoted_style + char *mCitationColor; // mail.citation_color + bool mStripSig; // mail.strip_sig_on_reply + bool mIsSig; +}; + +#define MimeInlineTextPlainClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETPLA_H_ */ diff --git a/mailnews/mime/src/mimetric.cpp b/mailnews/mime/src/mimetric.cpp new file mode 100644 index 000000000..95e08ac9b --- /dev/null +++ b/mailnews/mime/src/mimetric.cpp @@ -0,0 +1,353 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mimetric.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "msgCore.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeInlineTextClass +MimeDefClass(MimeInlineTextRichtext, MimeInlineTextRichtextClass, + mimeInlineTextRichtextClass, &MIME_SUPERCLASS); + +static int MimeInlineTextRichtext_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextRichtext_parse_begin (MimeObject *); +static int MimeInlineTextRichtext_parse_eof (MimeObject *, bool); + +static int +MimeInlineTextRichtextClassInitialize(MimeInlineTextRichtextClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->parse_begin = MimeInlineTextRichtext_parse_begin; + oclass->parse_line = MimeInlineTextRichtext_parse_line; + oclass->parse_eof = MimeInlineTextRichtext_parse_eof; + return 0; +} + +/* This function has this clunky interface because it needs to be called + from outside this module (no MimeObject, etc.) + */ +int +MimeRichtextConvert (const char *line, int32_t length, + MimeObject *obj, + char **obufferP, + int32_t *obuffer_sizeP, + bool enriched_p) +{ + /* RFC 1341 (the original MIME spec) defined text/richtext. + RFC 1563 superceded text/richtext with text/enriched. + The changes from text/richtext to text/enriched are: + - CRLF semantics are different + - << maps to < + - These tags were added: + <VERBATIM>, <NOFILL>, <PARAM>, <FLUSHBOTH> + - These tags were removed: + <COMMENT>, <OUTDENT>, <OUTDENTRIGHT>, <SAMEPAGE>, <SUBSCRIPT>, + <SUPERSCRIPT>, <HEADING>, <FOOTING>, <PARAGRAPH>, <SIGNATURE>, + <LT>, <NL>, <NP> + This method implements them both. + + draft-resnick-text-enriched-03.txt is a proposed update to 1563. + - These tags were added: + <FONTFAMILY>, <COLOR>, <PARAINDENT>, <LANG>. + However, all of these rely on the magic <PARAM> tag, which we + don't implement, so we're ignoring all of these. + Interesting fact: it's by Peter W. Resnick from Qualcomm (Eudora). + And it also says "It is fully expected that other text formatting + standards like HTML and SGML will supplant text/enriched in + Internet mail." + */ + int status = 0; + char *out; + const char *data_end; + const char *last_end; + const char *this_start; + const char *this_end; + unsigned int desired_size; + + // The code below must never expand the input by more than 5x; + // if it does, the desired_size multiplier (5) below must be changed too +#define BGROWTH 5 + if ( (uint32_t)length >= ( (uint32_t) 0xfffffffe)/BGROWTH ) + return -1; + desired_size = (length * BGROWTH) + 1; +#undef BGROWTH + if (desired_size >= (uint32_t) *obuffer_sizeP) + status = mime_GrowBuffer (desired_size, sizeof(char), 1024, + obufferP, obuffer_sizeP); + if (status < 0) return status; + + if (enriched_p) + { + for (this_start = line; this_start < line + length; this_start++) + if (!IS_SPACE (*this_start)) break; + if (this_start >= line + length) /* blank line */ + { + PL_strncpyz (*obufferP, "<BR>", *obuffer_sizeP); + return MimeObject_write(obj, *obufferP, strlen(*obufferP), true); + } + } + + uint32_t outlen = (uint32_t) *obuffer_sizeP; + out = *obufferP; + *out = 0; + + data_end = line + length; + last_end = line; + this_start = last_end; + this_end = this_start; + uint32_t addedlen = 0; + while (this_end < data_end) + { + /* Skip forward to next special character. */ + while (this_start < data_end && + *this_start != '<' && *this_start != '>' && + *this_start != '&') + this_start++; + + this_end = this_start; + + /* Skip to the end of the tag. */ + if (this_start < data_end && *this_start == '<') + { + this_end++; + while (this_end < data_end && + !IS_SPACE(*this_end) && + *this_end != '<' && *this_end != '>' && + *this_end != '&') + this_end++; + } + + this_end++; + + /* Push out the text preceeding the tag. */ + if (last_end && last_end != this_start) + { + memcpy (out, last_end, this_start - last_end); + out += this_start - last_end; + *out = 0; + outlen -= (this_start - last_end); + } + + if (this_start >= data_end) + break; + else if (*this_start == '&') + { + PL_strncpyz (out, "&", outlen); + addedlen = strlen(out); + outlen -= addedlen; + out += addedlen; + } + else if (*this_start == '>') + { + PL_strncpyz (out, ">", outlen); + addedlen = strlen(out); + outlen -= addedlen; + out += addedlen; + } + else if (enriched_p && + this_start < data_end + 1 && + this_start[0] == '<' && + this_start[1] == '<') + { + PL_strncpyz (out, "<", outlen); + addedlen = strlen(out); + outlen -= addedlen; + out += addedlen; + } + else if (this_start != this_end) + { + /* Push out this ID. */ + const char *old = this_start + 1; + const char *tag_open = 0; + const char *tag_close = 0; + if (*old == '/') + { + /* This is </tag> */ + old++; + } + + switch (*old) + { + case 'b': case 'B': + if (!PL_strncasecmp ("BIGGER>", old, 7)) + tag_open = "<FONT SIZE=\"+1\">", tag_close = "</FONT>"; + else if (!PL_strncasecmp ("BLINK>", old, 5)) + /* Of course, both text/richtext and text/enriched must be + enhanced *somehow*... Or else what would people think. */ + tag_open = "<BLINK>", tag_close = "</BLINK>"; + else if (!PL_strncasecmp ("BOLD>", old, 5)) + tag_open = "<B>", tag_close = "</B>"; + break; + case 'c': case 'C': + if (!PL_strncasecmp ("CENTER>", old, 7)) + tag_open = "<CENTER>", tag_close = "</CENTER>"; + else if (!enriched_p && + !PL_strncasecmp ("COMMENT>", old, 8)) + tag_open = "<!-- ", tag_close = " -->"; + break; + case 'e': case 'E': + if (!PL_strncasecmp ("EXCERPT>", old, 8)) + tag_open = "<BLOCKQUOTE>", tag_close = "</BLOCKQUOTE>"; + break; + case 'f': case 'F': + if (!PL_strncasecmp ("FIXED>", old, 6)) + tag_open = "<TT>", tag_close = "</TT>"; + else if (enriched_p && + !PL_strncasecmp ("FLUSHBOTH>", old, 10)) + tag_open = "<P ALIGN=LEFT>", tag_close = "</P>"; + else if (!PL_strncasecmp ("FLUSHLEFT>", old, 10)) + tag_open = "<P ALIGN=LEFT>", tag_close = "</P>"; + else if (!PL_strncasecmp ("FLUSHRIGHT>", old, 11)) + tag_open = "<P ALIGN=RIGHT>", tag_close = "</P>"; + else if (!enriched_p && + !PL_strncasecmp ("FOOTING>", old, 8)) + tag_open = "<H6>", tag_close = "</H6>"; + break; + case 'h': case 'H': + if (!enriched_p && + !PL_strncasecmp ("HEADING>", old, 8)) + tag_open = "<H6>", tag_close = "</H6>"; + break; + case 'i': case 'I': + if (!PL_strncasecmp ("INDENT>", old, 7)) + tag_open = "<UL>", tag_close = "</UL>"; + else if (!PL_strncasecmp ("INDENTRIGHT>", old, 12)) + tag_open = 0, tag_close = 0; +/* else if (!enriched_p && + !PL_strncasecmp ("ISO-8859-", old, 9)) + tag_open = 0, tag_close = 0; */ + else if (!PL_strncasecmp ("ITALIC>", old, 7)) + tag_open = "<I>", tag_close = "</I>"; + break; + case 'l': case 'L': + if (!enriched_p && + !PL_strncasecmp ("LT>", old, 3)) + tag_open = "<", tag_close = 0; + break; + case 'n': case 'N': + if (!enriched_p && + !PL_strncasecmp ("NL>", old, 3)) + tag_open = "<BR>", tag_close = 0; + if (enriched_p && + !PL_strncasecmp ("NOFILL>", old, 7)) + tag_open = "<NOBR>", tag_close = "</NOBR>"; +/* else if (!enriched_p && + !PL_strncasecmp ("NO-OP>", old, 6)) + tag_open = 0, tag_close = 0; */ +/* else if (!enriched_p && + !PL_strncasecmp ("NP>", old, 3)) + tag_open = 0, tag_close = 0; */ + break; + case 'o': case 'O': + if (!enriched_p && + !PL_strncasecmp ("OUTDENT>", old, 8)) + tag_open = 0, tag_close = 0; + else if (!enriched_p && + !PL_strncasecmp ("OUTDENTRIGHT>", old, 13)) + tag_open = 0, tag_close = 0; + break; + case 'p': case 'P': + if (enriched_p && + !PL_strncasecmp ("PARAM>", old, 6)) + tag_open = "<!-- ", tag_close = " -->"; + else if (!enriched_p && + !PL_strncasecmp ("PARAGRAPH>", old, 10)) + tag_open = "<P>", tag_close = 0; + break; + case 's': case 'S': + if (!enriched_p && + !PL_strncasecmp ("SAMEPAGE>", old, 9)) + tag_open = 0, tag_close = 0; + else if (!enriched_p && + !PL_strncasecmp ("SIGNATURE>", old, 10)) + tag_open = "<I><FONT SIZE=\"-1\">", tag_close = "</FONT></I>"; + else if (!PL_strncasecmp ("SMALLER>", old, 8)) + tag_open = "<FONT SIZE=\"-1\">", tag_close = "</FONT>"; + else if (!enriched_p && + !PL_strncasecmp ("SUBSCRIPT>", old, 10)) + tag_open = "<SUB>", tag_close = "</SUB>"; + else if (!enriched_p && + !PL_strncasecmp ("SUPERSCRIPT>", old, 12)) + tag_open = "<SUP>", tag_close = "</SUP>"; + break; + case 'u': case 'U': + if (!PL_strncasecmp ("UNDERLINE>", old, 10)) + tag_open = "<U>", tag_close = "</U>"; +/* else if (!enriched_p && + !PL_strncasecmp ("US-ASCII>", old, 10)) + tag_open = 0, tag_close = 0; */ + break; + case 'v': case 'V': + if (enriched_p && + !PL_strncasecmp ("VERBATIM>", old, 9)) + tag_open = "<PRE>", tag_close = "</PRE>"; + break; + } + + if (this_start[1] == '/') + { + if (tag_close) PL_strncpyz (out, tag_close, outlen); + addedlen = strlen (out); + outlen -= addedlen; + out += addedlen; + } + else + { + if (tag_open) PL_strncpyz (out, tag_open, outlen); + addedlen = strlen (out); + outlen -= addedlen; + out += addedlen; + } + } + + /* now go around again */ + last_end = this_end; + this_start = last_end; + } + *out = 0; + + return MimeObject_write(obj, *obufferP, out - *obufferP, true); +} + + +static int +MimeInlineTextRichtext_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + bool enriched_p = (((MimeInlineTextRichtextClass *) obj->clazz) + ->enriched_p); + + return MimeRichtextConvert (line, length, + obj, + &obj->obuffer, &obj->obuffer_size, + enriched_p); +} + + +static int +MimeInlineTextRichtext_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + char s[] = ""; + if (status < 0) return status; + return MimeObject_write(obj, s, 0, true); /* force out any separators... */ +} + + +static int +MimeInlineTextRichtext_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + return 0; +} diff --git a/mailnews/mime/src/mimetric.h b/mailnews/mime/src/mimetric.h new file mode 100644 index 000000000..fe6c66974 --- /dev/null +++ b/mailnews/mime/src/mimetric.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETRIC_H_ +#define _MIMETRIC_H_ + +#include "mimetext.h" + +/* The MimeInlineTextRichtext class implements the (obsolete and deprecated) + text/richtext MIME content type, as defined in RFC 1341, and also the + text/enriched MIME content type, as defined in RFC 1563. + */ + +typedef struct MimeInlineTextRichtextClass MimeInlineTextRichtextClass; +typedef struct MimeInlineTextRichtext MimeInlineTextRichtext; + +struct MimeInlineTextRichtextClass { + MimeInlineTextClass text; + bool enriched_p; /* Whether we should act like text/enriched instead. */ +}; + +extern MimeInlineTextRichtextClass mimeInlineTextRichtextClass; + +struct MimeInlineTextRichtext { + MimeInlineText text; +}; + +#define MimeInlineTextRichtextClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETRIC_H_ */ diff --git a/mailnews/mime/src/mimeunty.cpp b/mailnews/mime/src/mimeunty.cpp new file mode 100644 index 000000000..212b08ba8 --- /dev/null +++ b/mailnews/mime/src/mimeunty.cpp @@ -0,0 +1,588 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimeunty.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeContainerClass +MimeDefClass(MimeUntypedText, MimeUntypedTextClass, + mimeUntypedTextClass, &MIME_SUPERCLASS); + +static int MimeUntypedText_initialize (MimeObject *); +static void MimeUntypedText_finalize (MimeObject *); +static int MimeUntypedText_parse_begin (MimeObject *); +static int MimeUntypedText_parse_line (const char *, int32_t, MimeObject *); + +static int MimeUntypedText_open_subpart (MimeObject *obj, + MimeUntypedTextSubpartType ttype, + const char *type, + const char *enc, + const char *name, + const char *desc); +static int MimeUntypedText_close_subpart (MimeObject *obj); + +static bool MimeUntypedText_uu_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt, + char **type_ret, + char **name_ret); +static bool MimeUntypedText_uu_end_line_p(const char *line, int32_t length); + +static bool MimeUntypedText_yenc_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt, + char **type_ret, + char **name_ret); +static bool MimeUntypedText_yenc_end_line_p(const char *line, int32_t length); + +static bool MimeUntypedText_binhex_begin_line_p(const char *line, + int32_t length, + MimeDisplayOptions *opt); +static bool MimeUntypedText_binhex_end_line_p(const char *line, + int32_t length); + +static int +MimeUntypedTextClassInitialize(MimeUntypedTextClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeUntypedText_initialize; + oclass->finalize = MimeUntypedText_finalize; + oclass->parse_begin = MimeUntypedText_parse_begin; + oclass->parse_line = MimeUntypedText_parse_line; + return 0; +} + + +static int +MimeUntypedText_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeUntypedText_finalize (MimeObject *object) +{ + MimeUntypedText *uty = (MimeUntypedText *) object; + + if (uty->open_hdrs) + { + /* Oops, those shouldn't still be here... */ + MimeHeaders_free(uty->open_hdrs); + uty->open_hdrs = 0; + } + + /* What about the open_subpart? We're gonna have to assume that it + is also on the MimeContainer->children list, and will get cleaned + up by that class. */ + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeUntypedText_parse_begin (MimeObject *obj) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); +} + +static int +MimeUntypedText_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeUntypedText *uty = (MimeUntypedText *) obj; + int status = 0; + char *name = 0, *type = 0; + bool begin_line_p = false; + + NS_ASSERTION(line && *line, "empty line in mime untyped parse_line"); + if (!line || !*line) return -1; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + obj->options->output_fn) + return MimeObject_write(obj, line, length, true); + + + /* Open a new sub-part if this line demands it. + */ + if (line[0] == 'b' && + MimeUntypedText_uu_begin_line_p(line, length, obj->options, + &type, &name)) + { + /* Close the old part and open a new one. */ + status = MimeUntypedText_open_subpart (obj, + MimeUntypedTextSubpartTypeUUE, + type, ENCODING_UUENCODE, + name, NULL); + PR_FREEIF(name); + PR_FREEIF(type); + if (status < 0) return status; + begin_line_p = true; + } + + else if (line[0] == '=' && + MimeUntypedText_yenc_begin_line_p(line, length, obj->options, + &type, &name)) + { + /* Close the old part and open a new one. */ + status = MimeUntypedText_open_subpart (obj, + MimeUntypedTextSubpartTypeYEnc, + type, ENCODING_YENCODE, + name, NULL); + PR_FREEIF(name); + PR_FREEIF(type); + if (status < 0) return status; + begin_line_p = true; + } + + else if (line[0] == '(' && line[1] == 'T' && + MimeUntypedText_binhex_begin_line_p(line, length, obj->options)) + { + /* Close the old part and open a new one. */ + status = MimeUntypedText_open_subpart (obj, + MimeUntypedTextSubpartTypeBinhex, + APPLICATION_BINHEX, NULL, + NULL, NULL); + if (status < 0) return status; + begin_line_p = true; + } + + /* Open a text/plain sub-part if there is no sub-part open. + */ + if (!uty->open_subpart) + { + // rhp: If we get here and we are being fed a line ending, we should + // just eat it and continue and if we really get more data, we'll open + // up the subpart then. + // + if (line[0] == '\r') return 0; + if (line[0] == '\n') return 0; + + PR_ASSERT(!begin_line_p); + status = MimeUntypedText_open_subpart (obj, + MimeUntypedTextSubpartTypeText, + TEXT_PLAIN, NULL, NULL, NULL); + PR_ASSERT(uty->open_subpart); + if (!uty->open_subpart) return -1; + if (status < 0) return status; + } + + /* Hand this line to the currently-open sub-part. + */ + status = uty->open_subpart->clazz->parse_buffer(line, length, + uty->open_subpart); + if (status < 0) return status; + + /* Close this sub-part if this line demands it. + */ + if (begin_line_p) + ; + else if (line[0] == 'e' && + uty->type == MimeUntypedTextSubpartTypeUUE && + MimeUntypedText_uu_end_line_p(line, length)) + { + status = MimeUntypedText_close_subpart (obj); + if (status < 0) return status; + NS_ASSERTION(!uty->open_subpart, "no open subpart"); + } + else if (line[0] == '=' && + uty->type == MimeUntypedTextSubpartTypeYEnc && + MimeUntypedText_yenc_end_line_p(line, length)) + { + status = MimeUntypedText_close_subpart (obj); + if (status < 0) return status; + NS_ASSERTION(!uty->open_subpart, "no open subpart"); + } + else if (uty->type == MimeUntypedTextSubpartTypeBinhex && + MimeUntypedText_binhex_end_line_p(line, length)) + { + status = MimeUntypedText_close_subpart (obj); + if (status < 0) return status; + NS_ASSERTION(!uty->open_subpart, "no open subpart"); + } + + return 0; +} + + +static int +MimeUntypedText_close_subpart (MimeObject *obj) +{ + MimeUntypedText *uty = (MimeUntypedText *) obj; + int status; + + if (uty->open_subpart) + { + status = uty->open_subpart->clazz->parse_eof(uty->open_subpart, false); + uty->open_subpart = 0; + + PR_ASSERT(uty->open_hdrs); + if (uty->open_hdrs) + { + MimeHeaders_free(uty->open_hdrs); + uty->open_hdrs = 0; + } + uty->type = MimeUntypedTextSubpartTypeText; + if (status < 0) return status; + + /* Never put out a separator between sub-parts of UntypedText. + (This bypasses the rule that text/plain subparts always + have separators before and after them.) + */ + if (obj->options && obj->options->state) + obj->options->state->separator_suppressed_p = true; + } + + PR_ASSERT(!uty->open_hdrs); + return 0; +} + +static int +MimeUntypedText_open_subpart (MimeObject *obj, + MimeUntypedTextSubpartType ttype, + const char *type, + const char *enc, + const char *name, + const char *desc) +{ + MimeUntypedText *uty = (MimeUntypedText *) obj; + int status = 0; + char *h = 0; + + if (!type || !*type || !PL_strcasecmp(type, UNKNOWN_CONTENT_TYPE)) + type = APPLICATION_OCTET_STREAM; + if (enc && !*enc) + enc = 0; + if (desc && !*desc) + desc = 0; + if (name && !*name) + name = 0; + + if (uty->open_subpart) + { + status = MimeUntypedText_close_subpart (obj); + if (status < 0) return status; + } + NS_ASSERTION(!uty->open_subpart, "no open subpart"); + NS_ASSERTION(!uty->open_hdrs, "no open headers"); + + /* To make one of these implicitly-typed sub-objects, we make up a fake + header block, containing only the minimum number of MIME headers needed. + We could do most of this (Type and Encoding) by making a null header + block, and simply setting obj->content_type and obj->encoding; but making + a fake header block is better for two reasons: first, it means that + something will actually be displayed when in `Show All Headers' mode; + and second, it's the only way to communicate the filename parameter, + aside from adding a new slot to MimeObject (which is something to be + avoided when possible.) + */ + + uty->open_hdrs = MimeHeaders_new(); + if (!uty->open_hdrs) return MIME_OUT_OF_MEMORY; + + uint32_t hlen = strlen(type) + + (enc ? strlen(enc) : 0) + + (desc ? strlen(desc) : 0) + + (name ? strlen(name) : 0) + + 100; + h = (char *) PR_MALLOC(hlen); + if (!h) return MIME_OUT_OF_MEMORY; + + PL_strncpyz(h, HEADER_CONTENT_TYPE ": ", hlen); + PL_strcatn(h, hlen, type); + PL_strcatn(h, hlen, MSG_LINEBREAK); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + + if (enc) + { + PL_strncpyz(h, HEADER_CONTENT_TRANSFER_ENCODING ": ", hlen); + PL_strcatn(h, hlen, enc); + PL_strcatn(h, hlen, MSG_LINEBREAK); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + } + + if (desc) + { + PL_strncpyz(h, HEADER_CONTENT_DESCRIPTION ": ", hlen); + PL_strcatn(h, hlen, desc); + PL_strcatn(h, hlen, MSG_LINEBREAK); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + } + if (name) + { + PL_strncpyz(h, HEADER_CONTENT_DISPOSITION ": inline; filename=\"", hlen); + PL_strcatn(h, hlen, name); + PL_strcatn(h, hlen, "\"" MSG_LINEBREAK); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + } + + /* push out a blank line. */ + PL_strncpyz(h, MSG_LINEBREAK, hlen); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + + + /* Create a child... */ + { + bool horrid_kludge = (obj->options && obj->options->state && + obj->options->state->first_part_written_p); + if (horrid_kludge) + obj->options->state->first_part_written_p = false; + + uty->open_subpart = mime_create(type, uty->open_hdrs, obj->options); + + if (horrid_kludge) + obj->options->state->first_part_written_p = true; + + if (!uty->open_subpart) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + } + + /* Add it to the list... */ + status = ((MimeContainerClass *) obj->clazz)->add_child(obj, + uty->open_subpart); + if (status < 0) + { + mime_free(uty->open_subpart); + uty->open_subpart = 0; + goto FAIL; + } + + /* And start its parser going. */ + status = uty->open_subpart->clazz->parse_begin(uty->open_subpart); + if (status < 0) + { + /* MimeContainer->finalize will take care of shutting it down now. */ + uty->open_subpart = 0; + goto FAIL; + } + + uty->type = ttype; + + FAIL: + PR_FREEIF(h); + + if (status < 0 && uty->open_hdrs) + { + MimeHeaders_free(uty->open_hdrs); + uty->open_hdrs = 0; + } + + return status; +} + +static bool +MimeUntypedText_uu_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt, + char **type_ret, char **name_ret) +{ + const char *s; + char *name = 0; + char *type = 0; + + if (type_ret) *type_ret = 0; + if (name_ret) *name_ret = 0; + + if (strncmp (line, "begin ", 6)) return false; + /* ...then three or four octal digits. */ + s = line + 6; + if (*s < '0' || *s > '7') return false; + s++; + if (*s < '0' || *s > '7') return false; + s++; + if (*s < '0' || *s > '7') return false; + s++; + if (*s == ' ') + s++; + else + { + if (*s < '0' || *s > '7') return false; + s++; + if (*s != ' ') return false; + } + + while (IS_SPACE(*s)) + s++; + + name = (char *) PR_MALLOC(((line+length)-s) + 1); + if (!name) return false; /* grr... */ + memcpy(name, s, (line+length)-s); + name[(line+length)-s] = 0; + + /* take off newline. */ + if (name[strlen(name)-1] == '\n') name[strlen(name)-1] = 0; + if (name[strlen(name)-1] == '\r') name[strlen(name)-1] = 0; + + /* Now try and figure out a type. + */ + if (opt && opt->file_type_fn) + type = opt->file_type_fn(name, opt->stream_closure); + else + type = 0; + + if (name_ret) + *name_ret = name; + else + PR_FREEIF(name); + + if (type_ret) + *type_ret = type; + else + PR_FREEIF(type); + + return true; +} + +static bool +MimeUntypedText_uu_end_line_p(const char *line, int32_t length) +{ +#if 0 + /* A strictly conforming uuencode end line. */ + return (line[0] == 'e' && + line[1] == 'n' && + line[2] == 'd' && + (line[3] == 0 || IS_SPACE(line[3]))); +#else + /* ...but, why don't we accept any line that begins with the three + letters "END" in any case: I've seen lots of partial messages + that look like + + BEGIN----- Cut Here----- + begin 644 foo.gif + Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + END------- Cut Here----- + + so let's be lenient here. (This is only for the untyped-text-plain + case -- the uudecode parser itself is strict.) + */ + return (line[0] == ' ' || + line[0] == '\t' || + ((line[0] == 'e' || line[0] == 'E') && + (line[1] == 'n' || line[1] == 'N') && + (line[2] == 'd' || line[2] == 'D'))); +#endif +} + +static bool +MimeUntypedText_yenc_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt, + char **type_ret, char **name_ret) +{ + const char *s; + const char *endofline = line + length; + char *name = 0; + char *type = 0; + + if (type_ret) *type_ret = 0; + if (name_ret) *name_ret = 0; + + /* we don't support yenc V2 neither multipart yencode, + therefore the second parameter should always be "line="*/ + if (length < 13 || strncmp (line, "=ybegin line=", 13)) return false; + + /* ...then couple digits. */ + for (s = line + 13; s < endofline; s ++) + if (*s < '0' || *s > '9') + break; + + /* ...next, look for <space>size= */ + if ((endofline - s) < 6 || strncmp (s, " size=", 6)) return false; + + /* ...then couple digits. */ + for (s += 6; s < endofline; s ++) + if (*s < '0' || *s > '9') + break; + + /* ...next, look for <space>name= */ + if ((endofline - s) < 6 || strncmp (s, " name=", 6)) return false; + + /* anything left is the file name */ + s += 6; + name = (char *) PR_MALLOC((endofline-s) + 1); + if (!name) return false; /* grr... */ + memcpy(name, s, endofline-s); + name[endofline-s] = 0; + + /* take off newline. */ + if (name[strlen(name)-1] == '\n') name[strlen(name)-1] = 0; + if (name[strlen(name)-1] == '\r') name[strlen(name)-1] = 0; + + /* Now try and figure out a type. + */ + if (opt && opt->file_type_fn) + type = opt->file_type_fn(name, opt->stream_closure); + else + type = 0; + + if (name_ret) + *name_ret = name; + else + PR_FREEIF(name); + + if (type_ret) + *type_ret = type; + else + PR_FREEIF(type); + + return true; +} + +static bool +MimeUntypedText_yenc_end_line_p(const char *line, int32_t length) +{ + if (length < 11 || strncmp (line, "=yend size=", 11)) return false; + + return true; +} + + +#define BINHEX_MAGIC "(This file must be converted with BinHex 4.0)" +#define BINHEX_MAGIC_LEN 45 + +static bool +MimeUntypedText_binhex_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt) +{ + if (length <= BINHEX_MAGIC_LEN) + return false; + + while(length > 0 && IS_SPACE(line[length-1])) + length--; + + if (length != BINHEX_MAGIC_LEN) + return false; + + if (!strncmp(line, BINHEX_MAGIC, BINHEX_MAGIC_LEN)) + return true; + else + return false; +} + +static bool +MimeUntypedText_binhex_end_line_p(const char *line, int32_t length) +{ + if (length > 0 && line[length-1] == '\n') length--; + if (length > 0 && line[length-1] == '\r') length--; + + if (length != 0 && length != 64) + return true; + else + return false; +} diff --git a/mailnews/mime/src/mimeunty.h b/mailnews/mime/src/mimeunty.h new file mode 100644 index 000000000..9661f68bc --- /dev/null +++ b/mailnews/mime/src/mimeunty.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEUNTY_H_ +#define _MIMEUNTY_H_ + +#include "mimecont.h" + +/* The MimeUntypedText class is used for untyped message contents, that is, + it is the class used for the body of a message/rfc822 object which had + *no* Content-Type header, as opposed to an unrecognized content-type. + Such a message, technically, does not contain MIME data (it follows only + RFC 822, not RFC 1521.) + + This is a container class, and the reason for that is that it loosely + parses the body of the message looking for ``sub-parts'' and then + creates appropriate containers for them. + + More specifically, it looks for uuencoded data. It may do more than that + some day. + + Basically, the algorithm followed is: + + if line is "begin 644 foo.gif" + if there is an open sub-part, close it + add a sub-part with type: image/gif; encoding: x-uue + hand this line to it + and hand subsequent lines to that subpart + else if there is an open uuencoded sub-part, and line is "end" + hand this line to it + close off the uuencoded sub-part + else if there is an open sub-part + hand this line to it + else + open a text/plain subpart + hand this line to it + + Adding other types than uuencode to this (for example, PGP) would be + pretty straightforward. + */ + +typedef struct MimeUntypedTextClass MimeUntypedTextClass; +typedef struct MimeUntypedText MimeUntypedText; + +struct MimeUntypedTextClass { + MimeContainerClass container; +}; + +extern MimeUntypedTextClass mimeUntypedTextClass; + +typedef enum { + MimeUntypedTextSubpartTypeText, /* text/plain */ + MimeUntypedTextSubpartTypeUUE, /* uuencoded data */ + MimeUntypedTextSubpartTypeYEnc, /* yencoded data */ + MimeUntypedTextSubpartTypeBinhex /* Mac BinHex data */ +} MimeUntypedTextSubpartType; + +struct MimeUntypedText { + MimeContainer container; /* superclass variables */ + MimeObject *open_subpart; /* The part still-being-parsed */ + MimeUntypedTextSubpartType type; /* What kind of type it is */ + MimeHeaders *open_hdrs; /* The faked-up headers describing it */ +}; + +#define MimeUntypedTextClassInitializer(ITYPE,CSUPER) \ + { MimeContainerClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEUNTY_H_ */ diff --git a/mailnews/mime/src/modlmime.h b/mailnews/mime/src/modlmime.h new file mode 100644 index 000000000..547739885 --- /dev/null +++ b/mailnews/mime/src/modlmime.h @@ -0,0 +1,398 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _LIBMIME_H_ +#define _LIBMIME_H_ + +#ifdef XP_UNIX +#undef Bool +#endif + +#include "nsStringGlue.h" +#include "nsMailHeaders.h" +#include "nsIMimeStreamConverter.h" +#include "nsIUnicodeDecoder.h" +#include "nsIUnicodeEncoder.h" +#include "nsIPrefBranch.h" +#include "mozITXTToHTMLConv.h" +#include "nsCOMPtr.h" +#include "modmimee.h" // for MimeConverterOutputCallback + +#define MIME_DRAFTS + +/* Opaque object describing a block of message headers, and a couple of + routines for extracting data from one. + */ + +typedef struct MimeHeaders +{ + char *all_headers; /* A char* of the entire header section. */ + int32_t all_headers_fp; /* The length (it is not NULL-terminated.) */ + int32_t all_headers_size; /* The size of the allocated block. */ + + bool done_p; /* Whether we've read the end-of-headers marker + (the terminating blank line.) */ + + char **heads; /* An array of length n_headers which points + to the beginning of each distinct header: + just after the newline which terminated + the previous one. This is to speed search. + + This is not initialized until all the + headers have been read. + */ + int32_t heads_size; /* The length (and consequently, how many + distinct headers are in here.) */ + + + char *obuffer; /* This buffer is used for output. */ + int32_t obuffer_size; + int32_t obuffer_fp; + + char *munged_subject; /* What a hack. This is a place to write down + the subject header, after it's been + charset-ified and stuff. Remembered so that + we can later use it to generate the + <TITLE> tag. (Also works for giving names to RFC822 attachments) */ +} MimeHeaders; + +class MimeDisplayOptions; +class MimeParseStateObject; +typedef struct MSG_AttachmentData MSG_AttachmentData; + +/* Given the name of a header, returns the contents of that header as + a newly-allocated string (which the caller must free.) If the header + is not present, or has no contents, NULL is returned. + + If `strip_p' is true, then the data returned will be the first token + of the header; else it will be the full text of the header. (This is + useful for getting just "text/plain" from "text/plain; name=foo".) + + If `all_p' is false, then the first header encountered is used, and + any subsequent headers of the same name are ignored. If true, then + all headers of the same name are appended together (this is useful + for gathering up all CC headers into one, for example.) + */ +extern char *MimeHeaders_get(MimeHeaders *hdrs, + const char *header_name, + bool strip_p, + bool all_p); + +/* Given a header of the form of the MIME "Content-" headers, extracts a + named parameter from it, if it exists. For example, + MimeHeaders_get_parameter("text/plain; charset=us-ascii", "charset") + would return "us-ascii". + + Returns NULL if there is no match, or if there is an allocation failure. + + RFC2231 - MIME Parameter Value and Encoded Word Extensions: Character Sets, + Languages, and Continuations + + RFC2231 has added the character sets, languages, and continuations mechanism. + charset, and language information may also be returned to the caller. + Note that charset and language should be free()'d while + the return value (parameter) has to be PR_FREE'd. + + For example, + MimeHeaders_get_parameter("text/plain; name*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A", "name") + MimeHeaders_get_parameter("text/plain; name*0*=us-ascii'en-us'This%20is%20; CRLFLWSPname*1*=%2A%2A%2Afun%2A%2A%2A", "name") + would return "This is ***fun***" and *charset = "us-ascii", *language = "en-us" + */ +extern char *MimeHeaders_get_parameter (const char *header_value, + const char *parm_name, + char **charset, + char **language); + +extern MimeHeaders *MimeHeaders_copy (MimeHeaders *srcHeaders); + +extern void MimeHeaders_free (MimeHeaders *hdrs); + +typedef enum { + MimeHeadersAll, /* Show all headers */ + MimeHeadersSome, /* Show all "interesting" headers */ + MimeHeadersSomeNoRef, /* Same, but suppress the `References' header + (for when we're printing this message.) */ + MimeHeadersMicro, /* Show a one-line header summary */ + MimeHeadersMicroPlus, /* Same, but show the full recipient list as + well (To, CC, etc.) */ + MimeHeadersCitation, /* A one-line summary geared toward use in a + reply citation ("So-and-so wrote:") */ + MimeHeadersOnly, /* Just parse and output headers...nothing else! */ + MimeHeadersNone /* Skip showing any headers */ +} MimeHeadersState; + + +/* The signature for various callbacks in the MimeDisplayOptions structure. + */ +typedef char *(*MimeHTMLGeneratorFunction) (const char *data, void *closure, + MimeHeaders *headers); + +class MimeDisplayOptions +{ +public: + MimeDisplayOptions(); + virtual ~MimeDisplayOptions(); + mozITXTToHTMLConv *conv; // For text conversion... + nsCOMPtr<nsIPrefBranch> m_prefBranch; /* prefBranch-service */ + nsMimeOutputType format_out; // The format out type + nsCString charsetForCachedInputDecoder; + nsCOMPtr<nsIUnicodeDecoder> m_inputCharsetToUnicodeDecoder; + nsCOMPtr<nsIUnicodeEncoder> m_unicodeToUTF8Encoder; + + const char *url; /* Base URL for the document. This string should + be freed by the caller, after the parser + completes (possibly at the same time as the + MimeDisplayOptions itself.) */ + + MimeHeadersState headers; /* How headers should be displayed. */ + bool fancy_headers_p; /* Whether to do clever formatting of headers + using tables, instead of spaces. */ + + bool output_vcard_buttons_p; /* Whether to output the buttons */ + /* on vcards. */ + + bool variable_width_plaintext_p; /* Whether text/plain messages should + be in variable width, or fixed. */ + bool wrap_long_lines_p; /* Whether to wrap long lines in text/plain + messages. */ + + bool rot13_p; /* Whether text/plain parts should be rotated + Set by "?rot13=true" */ + char *part_to_load; /* The particular part of the multipart which + we are extracting. Set by "?part=3.2.4" */ + + bool no_output_p; /* Will never write output when this is true. + (When false, output or not may depend on other things.) + This needs to be in the options, because the method + MimeObject_parse_begin is controlling the property "output_p" + (on the MimeObject) so we need a way for other functions to + override it and tell that we do not want output. */ + + bool write_html_p; /* Whether the output should be HTML, or raw. */ + + bool decrypt_p; /* Whether all traces of xlateion should be + eradicated -- this is only meaningful when + write_html_p is false; we set this when + attaching a message for forwarding, since + forwarding someone else a message that wasn't + xlated for them doesn't work. We have to + dexlate it before sending it. + */ + + /* Whether this MIME part is a child of another part (and not top level). */ + bool is_child = false; + + uint32_t whattodo ; /* from the prefs, we'll get if the user wants to do glyph or structure substitutions and set this member variable. */ + + char *default_charset; /* If this is non-NULL, then it is the charset to + assume when no other one is specified via a + `charset' parameter. + */ + bool override_charset; /* If this is true, then we will assume that + all data is in the default_charset, regardless + of what the `charset' parameter of that part + says. (This is to cope with the fact that, in + the real world, many messages are mislabelled + with the wrong charset.) + */ + bool force_user_charset; /* this is the new strategy to deal with incorrectly + labeled attachments */ + + /* ======================================================================= + Stream-related callbacks; for these functions, the `closure' argument + is what is found in `options->stream_closure'. (One possible exception + is for output_fn; see "output_closure" below.) + */ + void *stream_closure; + + /* For setting up the display stream, so that the MIME parser can inform + the caller of the type of the data it will be getting. */ + int (*output_init_fn) (const char *type, + const char *charset, + const char *name, + const char *x_mac_type, + const char *x_mac_creator, + void *stream_closure); + + /* How the MIME parser feeds its output (HTML or raw) back to the caller. */ + MimeConverterOutputCallback output_fn; + + /* Closure to pass to the above output_fn. If NULL, then the + stream_closure is used. */ + void *output_closure; + + /* A hook for the caller to perform charset-conversion before HTML is + returned. Each set of characters which originated in a mail message + (body or headers) will be run through this filter before being converted + into HTML. (This should return bytes which may appear in an HTML file, + ie, we must be able to scan through the string to search for "<" and + turn it in to "<", and so on.) + + `input' is a non-NULL-terminated string of a single line from the message. + `input_length' is how long it is. + `input_charset' is a string representing the charset of this string (as + specified by MIME headers.) + `output_charset' is the charset to which conversion is desired. + `output_ret' is where a newly-malloced string is returned. It may be + NULL if no translation is needed. + `output_size_ret' is how long the returned string is (it need not be + NULL-terminated.). + */ + int (*charset_conversion_fn) (const char *input_line, + int32_t input_length, const char *input_charset, + const char *output_charset, + char **output_ret, int32_t *output_size_ret, + void *stream_closure, nsIUnicodeDecoder *decoder, nsIUnicodeEncoder *encoder); + + /* If true, perform both charset-conversion and decoding of + MIME-2 header fields (using RFC-1522 encoding.) + */ + bool rfc1522_conversion_p; + + /* A hook for the caller to turn a file name into a content-type. */ + char *(*file_type_fn) (const char *filename, void *stream_closure); + + /* A hook by which the user may be prompted for a password by the security + library. (This is really of type `SECKEYGetPasswordKey'; see sec.h.) */ + void *(*passwd_prompt_fn)(void *arg1, void *arg2); + + /* ======================================================================= + Various callbacks; for all of these functions, the `closure' argument + is what is found in `html_closure'. + */ + void *html_closure; + + /* For emitting some HTML before the start of the outermost message + (this is called before any HTML is written to layout.) */ + MimeHTMLGeneratorFunction generate_header_html_fn; + + /* For emitting some HTML after the outermost header block, but before + the body of the first message. */ + MimeHTMLGeneratorFunction generate_post_header_html_fn; + + /* For emitting some HTML at the very end (this is called after libmime + has written everything it's going to write.) */ + MimeHTMLGeneratorFunction generate_footer_html_fn; + + /* For turning a message ID into a loadable URL. */ + MimeHTMLGeneratorFunction generate_reference_url_fn; + + /* For turning a mail address into a mailto URL. */ + MimeHTMLGeneratorFunction generate_mailto_url_fn; + + /* For turning a newsgroup name into a news URL. */ + MimeHTMLGeneratorFunction generate_news_url_fn; + + /* ======================================================================= + Callbacks to handle the backend-specific inlined image display + (internal-external-reconnect junk.) For `image_begin', the `closure' + argument is what is found in `stream_closure'; but for all of the + others, the `closure' argument is the data that `image_begin' returned. + */ + + /* Begins processing an embedded image; the URL and content_type are of the + image itself. */ + void *(*image_begin) (const char *image_url, const char *content_type, + void *stream_closure); + + /* Stop processing an image. */ + void (*image_end) (void *image_closure, int status); + + /* Dump some raw image data down the stream. */ + int (*image_write_buffer) (const char *buf, int32_t size, void *image_closure); + + /* What HTML should be dumped out for this image. */ + char *(*make_image_html) (void *image_closure); + + + /* ======================================================================= + Other random opaque state. + */ + MimeParseStateObject *state; /* Some state used by libmime internals; + initialize this to 0 and leave it alone. + */ + + +#ifdef MIME_DRAFTS + /* ======================================================================= + Mail Draft hooks -- 09-19-1996 + */ + bool decompose_file_p; /* are we decomposing a mime msg + into separate files */ + bool done_parsing_outer_headers; /* are we done parsing the outer message + headers; this is really useful when + we have multiple Message/RFC822 + headers */ + bool is_multipart_msg; /* are we decomposing a multipart + message */ + + int decompose_init_count; /* used for non multipart message only + */ + + bool signed_p; /* to tell draft this is a signed + message */ + + bool caller_need_root_headers; /* set it to true to receive the message main + headers through the callback + decompose_headers_info_fn */ + + /* Callback to gather the outer most headers so we could use the + information to initialize the addressing/subject/newsgroups fields + for the composition window. */ + int (*decompose_headers_info_fn) (void *closure, + MimeHeaders *headers); + + /* Callbacks to create temporary files for drafts attachments. */ + int (*decompose_file_init_fn) (void *stream_closure, + MimeHeaders *headers ); + + MimeConverterOutputCallback decompose_file_output_fn; + + int (*decompose_file_close_fn) (void *stream_closure); +#endif /* MIME_DRAFTS */ + + int32_t attachment_icon_layer_id; /* Hackhackhack. This is zero if we have + not yet emitted the attachment layer + stuff. If we have, then this is the + id number for that layer, which is a + unique random number every time, to keep + evil people from writing javascript code + to hack it. */ + + bool missing_parts; /* Whether or not this message is going to contain + missing parts (from IMAP Mime Parts On Demand) */ + + bool show_attachment_inline_p; /* Whether or not we should display attachment inline (whatever say + the content-disposition) */ + + bool quote_attachment_inline_p; /* Whether or not we should include inlined attachments in + quotes of replies) */ + + int32_t html_as_p; /* How we should display HTML, which allows us to know if we should display all body parts */ + + /** + * Should StartBody/EndBody events be generated for nested MimeMessages. If + * false (the default value), the events are only generated for the outermost + * MimeMessage. + */ + bool notify_nested_bodies; + + /** + * When true, compels mime parts to only write the actual body + * payload and not display-gunk like links to attachments. This was + * primarily introduced for the benefit of the javascript emitter. + */ + bool write_pure_bodies; + + /** + * When true, only processes metadata (i.e. size) for streamed attachments. + * Mime emitters that expect any attachment data (including inline text and + * image attachments) should leave this as false (the default value). At + * the moment, only the JS mime emitter uses this. + */ + bool metadata_only; +}; + +#endif /* _MODLMIME_H_ */ diff --git a/mailnews/mime/src/modmimee.h b/mailnews/mime/src/modmimee.h new file mode 100644 index 000000000..123fbcdb1 --- /dev/null +++ b/mailnews/mime/src/modmimee.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + /* -*- Mode: C; tab-width: 4 -*- + mimeenc.c --- MIME encoders and decoders, version 2 (see mimei.h) + Copyright (c) 1996 Netscape Communications Corporation, all rights reserved. + Created: Jamie Zawinski <jwz@netscape.com>, 15-May-96. + */ + +#ifndef _MIMEENC_H_ +#define _MIMEENC_H_ + +#include "nsError.h" +#include "nscore.h" // for nullptr + +typedef int (*MimeConverterOutputCallback) + (const char *buf, int32_t size, void *closure); + +/* This file defines interfaces to generic implementations of Base64, + Quoted-Printable, and UU decoders; and of Base64 and Quoted-Printable + encoders. + */ + + +/* Opaque objects used by the encoder/decoder to store state. */ +typedef struct MimeDecoderData MimeDecoderData; + +struct MimeObject; + + +/* functions for creating that opaque data. + */ +MimeDecoderData *MimeB64DecoderInit(MimeConverterOutputCallback output_fn, + void *closure); + +MimeDecoderData *MimeQPDecoderInit (MimeConverterOutputCallback output_fn, + void *closure, MimeObject *object = nullptr); + +MimeDecoderData *MimeUUDecoderInit (MimeConverterOutputCallback output_fn, + void *closure); +MimeDecoderData *MimeYDecoderInit (MimeConverterOutputCallback output_fn, + void *closure); + +/* Push data through the encoder/decoder, causing the above-provided write_fn + to be called with encoded/decoded data. */ +int MimeDecoderWrite (MimeDecoderData *data, const char *buffer, int32_t size, + int32_t *outSize); + +/* When you're done encoding/decoding, call this to free the data. If + abort_p is false, then calling this may cause the write_fn to be called + one last time (as the last buffered data is flushed out.) + */ +int MimeDecoderDestroy(MimeDecoderData *data, bool abort_p); + +#endif /* _MODMIMEE_H_ */ diff --git a/mailnews/mime/src/moz.build b/mailnews/mime/src/moz.build new file mode 100644 index 000000000..93bdbad98 --- /dev/null +++ b/mailnews/mime/src/moz.build @@ -0,0 +1,92 @@ +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'mimecont.h', + 'mimecryp.h', + 'mimecth.h', + 'mimehdrs.h', + 'mimei.h', + 'mimeleaf.h', + 'mimemoz2.h', + 'mimemsig.h', + 'mimemult.h', + 'mimeobj.h', + 'mimepbuf.h', + 'mimetext.h', + 'modlmime.h', + 'modmimee.h', + 'nsMimeStringResources.h', + 'nsStreamConverter.h', +] + +SOURCES += [ + 'comi18n.cpp', + 'mimebuf.cpp', + 'mimecms.cpp', + 'mimecom.cpp', + 'mimecont.cpp', + 'mimecryp.cpp', + 'mimecth.cpp', + 'mimedrft.cpp', + 'mimeebod.cpp', + 'mimeenc.cpp', + 'mimeeobj.cpp', + 'mimehdrs.cpp', + 'MimeHeaderParser.cpp', + 'mimei.cpp', + 'mimeiimg.cpp', + 'mimeleaf.cpp', + 'mimemalt.cpp', + 'mimemapl.cpp', + 'mimemcms.cpp', + 'mimemdig.cpp', + 'mimemmix.cpp', + 'mimemoz2.cpp', + 'mimempar.cpp', + 'mimemrel.cpp', + 'mimemsg.cpp', + 'mimemsig.cpp', + 'mimemult.cpp', + 'mimeobj.cpp', + 'mimepbuf.cpp', + 'mimesun.cpp', + 'mimetenr.cpp', + 'mimetext.cpp', + 'mimeTextHTMLParsed.cpp', + 'mimethpl.cpp', + 'mimethsa.cpp', + 'mimethtm.cpp', + 'mimetpfl.cpp', + 'mimetpla.cpp', + 'mimetric.cpp', + 'mimeunty.cpp', + 'nsCMS.cpp', + 'nsCMSSecureMessage.cpp', + 'nsMimeObjectClassAccess.cpp', + 'nsSimpleMimeConverterStub.cpp', + 'nsStreamConverter.cpp', +] + +LOCAL_INCLUDES += [ + '/security/certverifier', + '/security/manager/ssl', + '/security/pkix/include', +] + +EXTRA_COMPONENTS += [ + 'mimeJSComponents.js', + 'msgMime.manifest', +] + +EXTRA_JS_MODULES += [ + 'extraMimeParsers.jsm', + 'jsmime.jsm', + 'mimeParser.jsm' +] + +FINAL_LIBRARY = 'mail' + +DEFINES['ENABLE_SMIME'] = True diff --git a/mailnews/mime/src/msgMime.manifest b/mailnews/mime/src/msgMime.manifest new file mode 100644 index 000000000..9ce79bf74 --- /dev/null +++ b/mailnews/mime/src/msgMime.manifest @@ -0,0 +1,9 @@ +component {d1258011-f391-44fd-992e-c6f4b461a42f} mimeJSComponents.js +component {96bd8769-2d0e-4440-963d-22b97fb3ba77} mimeJSComponents.js +component {93f8c049-80ed-4dda-9000-94ad8daba44c} mimeJSComponents.js +component {c560806a-425f-4f0f-bf69-397c58c599a7} mimeJSComponents.js +contract @mozilla.org/messenger/mimeheaders;1 {d1258011-f391-44fd-992e-c6f4b461a42f} +contract @mozilla.org/messenger/mimeconverter;1 {93f8c049-80ed-4dda-9000-94ad8daba44c} +contract @mozilla.org/messenger/headerparser;1 {96bd8769-2d0e-4440-963d-22b97fb3ba77} +contract @mozilla.org/messenger/structuredheaders;1 {c560806a-425f-4f0f-bf69-397c58c599a7} +category custom-mime-encoder A-extra resource:///modules/extraMimeParsers.jsm diff --git a/mailnews/mime/src/nsCMS.cpp b/mailnews/mime/src/nsCMS.cpp new file mode 100644 index 000000000..dd9d31c28 --- /dev/null +++ b/mailnews/mime/src/nsCMS.cpp @@ -0,0 +1,1048 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCMS.h" + +#include "CertVerifier.h" +#include "CryptoTask.h" +#include "ScopedNSSTypes.h" +#include "cms.h" +#include "mozilla/Logging.h" +#include "mozilla/RefPtr.h" +#include "nsArrayUtils.h" +#include "nsIArray.h" +#include "nsICMSMessageErrors.h" +#include "nsICryptoHash.h" +#include "nsISupports.h" +#include "nsIX509CertDB.h" +#include "nsNSSCertificate.h" +#include "nsNSSComponent.h" +#include "nsNSSHelper.h" +#include "nsServiceManagerUtils.h" +#include "pkix/Result.h" +#include "pkix/pkixtypes.h" +#include "smime.h" +#include "mozilla/StaticMutex.h" + +using namespace mozilla; +using namespace mozilla::psm; +using namespace mozilla::pkix; + +#ifdef PR_LOGGING +extern mozilla::LazyLogModule gPIPNSSLog; +#endif + +NS_IMPL_ISUPPORTS(nsCMSMessage, nsICMSMessage, nsICMSMessage2) + +nsCMSMessage::nsCMSMessage() +{ + m_cmsMsg = nullptr; +} +nsCMSMessage::nsCMSMessage(NSSCMSMessage *aCMSMsg) +{ + m_cmsMsg = aCMSMsg; +} + +nsCMSMessage::~nsCMSMessage() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +nsresult nsCMSMessage::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +void nsCMSMessage::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +void nsCMSMessage::destructorSafeDestroyNSSReference() +{ + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + } +} + +NS_IMETHODIMP nsCMSMessage::VerifySignature() +{ + return CommonVerifySignature(nullptr, 0, 0); +} + +NSSCMSSignerInfo* nsCMSMessage::GetTopLevelSignerInfo() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return nullptr; + + if (!m_cmsMsg) + return nullptr; + + if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) + return nullptr; + + NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0); + if (!cinfo) + return nullptr; + + NSSCMSSignedData *sigd = (NSSCMSSignedData*)NSS_CMSContentInfo_GetContent(cinfo); + if (!sigd) + return nullptr; + + PR_ASSERT(NSS_CMSSignedData_SignerInfoCount(sigd) > 0); + return NSS_CMSSignedData_GetSignerInfo(sigd, 0); +} + +NS_IMETHODIMP nsCMSMessage::GetSignerEmailAddress(char * * aEmail) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerEmailAddress\n")); + NS_ENSURE_ARG(aEmail); + + NSSCMSSignerInfo *si = GetTopLevelSignerInfo(); + if (!si) + return NS_ERROR_FAILURE; + + *aEmail = NSS_CMSSignerInfo_GetSignerEmailAddress(si); + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetSignerCommonName(char ** aName) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCommonName\n")); + NS_ENSURE_ARG(aName); + + NSSCMSSignerInfo *si = GetTopLevelSignerInfo(); + if (!si) + return NS_ERROR_FAILURE; + + *aName = NSS_CMSSignerInfo_GetSignerCommonName(si); + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::ContentIsEncrypted(bool *isEncrypted) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsEncrypted\n")); + NS_ENSURE_ARG(isEncrypted); + + if (!m_cmsMsg) + return NS_ERROR_FAILURE; + + *isEncrypted = NSS_CMSMessage_IsEncrypted(m_cmsMsg); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::ContentIsSigned(bool *isSigned) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsSigned\n")); + NS_ENSURE_ARG(isSigned); + + if (!m_cmsMsg) + return NS_ERROR_FAILURE; + + *isSigned = NSS_CMSMessage_IsSigned(m_cmsMsg); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetSignerCert(nsIX509Cert **scert) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + NSSCMSSignerInfo *si = GetTopLevelSignerInfo(); + if (!si) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIX509Cert> cert; + if (si->cert) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCert got signer cert\n")); + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + certdb->ConstructX509(reinterpret_cast<const char *>(si->cert->derCert.data), + si->cert->derCert.len, + getter_AddRefs(cert)); + } + else { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCert no signer cert, do we have a cert list? %s\n", + (si->certList ? "yes" : "no") )); + + *scert = nullptr; + } + + cert.forget(scert); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetEncryptionCert(nsIX509Cert **ecert) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsCMSMessage::VerifyDetachedSignature(unsigned char* aDigestData, + uint32_t aDigestDataLen, + int16_t aDigestType) +{ + if (!aDigestData || !aDigestDataLen) + return NS_ERROR_FAILURE; + + return CommonVerifySignature(aDigestData, aDigestDataLen, aDigestType); +} + +nsresult +nsCMSMessage::CommonVerifySignature(unsigned char* aDigestData, + uint32_t aDigestDataLen, + int16_t aDigestType) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature, content level count %d\n", NSS_CMSMessage_ContentLevelCount(m_cmsMsg))); + NSSCMSContentInfo *cinfo = nullptr; + NSSCMSSignedData *sigd = nullptr; + NSSCMSSignerInfo *si; + int32_t nsigners; + RefPtr<SharedCertVerifier> certVerifier; + nsresult rv = NS_ERROR_FAILURE; + + if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - not signed\n")); + return NS_ERROR_CMS_VERIFY_NOT_SIGNED; + } + + cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0); + if (cinfo) { + switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) { + case SEC_OID_PKCS7_SIGNED_DATA: + sigd = reinterpret_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo)); + break; + + case SEC_OID_PKCS7_ENVELOPED_DATA: + case SEC_OID_PKCS7_ENCRYPTED_DATA: + case SEC_OID_PKCS7_DIGESTED_DATA: + default: { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unexpected ContentTypeTag\n")); + rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; + goto loser; + } + } + } + + if (!sigd) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - no content info\n")); + rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; + goto loser; + } + + if (aDigestData && aDigestDataLen) + { + SECOidTag oidTag; + SECItem digest; + digest.data = aDigestData; + digest.len = aDigestDataLen; + + if (NSS_CMSSignedData_HasDigests(sigd)) { + SECAlgorithmID **existingAlgs = NSS_CMSSignedData_GetDigestAlgs(sigd); + if (existingAlgs) { + while (*existingAlgs) { + SECAlgorithmID *alg = *existingAlgs; + SECOidTag algOIDTag = SECOID_FindOIDTag(&alg->algorithm); + NSS_CMSSignedData_SetDigestValue(sigd, algOIDTag, NULL); + ++existingAlgs; + } + } + } + + if (!GetIntHashToOidHash(aDigestType, oidTag)) { + rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST; + goto loser; + } + + if (NSS_CMSSignedData_SetDigestValue(sigd, oidTag, &digest)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - bad digest\n")); + rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST; + goto loser; + } + } + + // Import certs. Note that import failure is not a signature verification failure. // + if (NSS_CMSSignedData_ImportCerts(sigd, CERT_GetDefaultCertDB(), certUsageEmailRecipient, true) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - can not import certs\n")); + } + + nsigners = NSS_CMSSignedData_SignerInfoCount(sigd); + PR_ASSERT(nsigners > 0); + NS_ENSURE_TRUE(nsigners > 0, NS_ERROR_UNEXPECTED); + si = NSS_CMSSignedData_GetSignerInfo(sigd, 0); + + // See bug 324474. We want to make sure the signing cert is + // still valid at the current time. + + certVerifier = GetDefaultCertVerifier(); + NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED); + + { + UniqueCERTCertList builtChain; + mozilla::pkix::Result result = + certVerifier->VerifyCert(si->cert, + certificateUsageEmailSigner, + Now(), + nullptr /*XXX pinarg*/, + nullptr /*hostname*/, + builtChain); + if (result != mozilla::pkix::Success) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - signing cert not trusted now\n")); + rv = NS_ERROR_CMS_VERIFY_UNTRUSTED; + goto loser; + } + } + + // We verify the first signer info, only // + // XXX: NSS_CMSSignedData_VerifySignerInfo calls CERT_VerifyCert, which + // requires NSS's certificate verification configuration to be done in + // order to work well (e.g. honoring OCSP preferences and proxy settings + // for OCSP requests), but Gecko stopped doing that configuration. Something + // similar to what was done for Gecko bug 1028643 needs to be done here too. + if (NSS_CMSSignedData_VerifySignerInfo(sigd, 0, CERT_GetDefaultCertDB(), certUsageEmailSigner) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unable to verify signature\n")); + + if (NSSCMSVS_SigningCertNotFound == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - signing cert not found\n")); + rv = NS_ERROR_CMS_VERIFY_NOCERT; + } + else if(NSSCMSVS_SigningCertNotTrusted == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - signing cert not trusted at signing time\n")); + rv = NS_ERROR_CMS_VERIFY_UNTRUSTED; + } + else if(NSSCMSVS_Unverified == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - can not verify\n")); + rv = NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED; + } + else if(NSSCMSVS_ProcessingError == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - processing error\n")); + rv = NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; + } + else if(NSSCMSVS_BadSignature == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - bad signature\n")); + rv = NS_ERROR_CMS_VERIFY_BAD_SIGNATURE; + } + else if(NSSCMSVS_DigestMismatch == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - digest mismatch\n")); + rv = NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH; + } + else if(NSSCMSVS_SignatureAlgorithmUnknown == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - algo unknown\n")); + rv = NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO; + } + else if(NSSCMSVS_SignatureAlgorithmUnsupported == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - algo not supported\n")); + rv = NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO; + } + else if(NSSCMSVS_MalformedSignature == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - malformed signature\n")); + rv = NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE; + } + + goto loser; + } + + // Save the profile. Note that save import failure is not a signature verification failure. // + if (NSS_SMIMESignerInfo_SaveSMIMEProfile(si) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unable to save smime profile\n")); + } + + rv = NS_OK; +loser: + return rv; +} + +NS_IMETHODIMP nsCMSMessage::AsyncVerifySignature( + nsISMimeVerificationListener *aListener) +{ + return CommonAsyncVerifySignature(aListener, nullptr, 0, 0); +} + +NS_IMETHODIMP nsCMSMessage::AsyncVerifyDetachedSignature( + nsISMimeVerificationListener *aListener, + unsigned char* aDigestData, + uint32_t aDigestDataLen, + int16_t aDigestType) +{ + if (!aDigestData || !aDigestDataLen) + return NS_ERROR_FAILURE; + + return CommonAsyncVerifySignature(aListener, aDigestData, aDigestDataLen, aDigestType); +} + +class SMimeVerificationTask final : public CryptoTask +{ +public: + SMimeVerificationTask(nsICMSMessage *aMessage, + nsISMimeVerificationListener *aListener, + unsigned char *aDigestData, + uint32_t aDigestDataLen, + int16_t aDigestType) + { + MOZ_ASSERT(NS_IsMainThread()); + mMessage = aMessage; + mListener = aListener; + mDigestData.Assign(reinterpret_cast<char *>(aDigestData), aDigestDataLen); + mDigestType = aDigestType; + } + +private: + virtual void ReleaseNSSResources() override {} + virtual nsresult CalculateResult() override + { + MOZ_ASSERT(!NS_IsMainThread()); + + mozilla::StaticMutexAutoLock lock(sMutex); + nsresult rv; + if (!mDigestData.IsEmpty()) { + rv = mMessage->VerifyDetachedSignature( + reinterpret_cast<uint8_t*>(const_cast<char *>(mDigestData.get())), + mDigestData.Length(), mDigestType); + } else { + rv = mMessage->VerifySignature(); + } + + return rv; + } + virtual void CallCallback(nsresult rv) override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsICMSMessage2> m2 = do_QueryInterface(mMessage); + mListener->Notify(m2, rv); + } + + nsCOMPtr<nsICMSMessage> mMessage; + nsCOMPtr<nsISMimeVerificationListener> mListener; + nsCString mDigestData; + int16_t mDigestType; + + static mozilla::StaticMutex sMutex; +}; + +mozilla::StaticMutex SMimeVerificationTask::sMutex; + +nsresult nsCMSMessage::CommonAsyncVerifySignature(nsISMimeVerificationListener *aListener, + unsigned char* aDigestData, uint32_t aDigestDataLen, + int16_t aDigestType) +{ + RefPtr<CryptoTask> task = new SMimeVerificationTask(this, aListener, aDigestData, aDigestDataLen, aDigestType); + return task->Dispatch("SMimeVerify"); +} + +class nsZeroTerminatedCertArray : public nsNSSShutDownObject +{ +public: + nsZeroTerminatedCertArray() + :mCerts(nullptr), mPoolp(nullptr), mSize(0) + { + } + + ~nsZeroTerminatedCertArray() + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); + } + + void virtualDestroyNSSReference() + { + destructorSafeDestroyNSSReference(); + } + + void destructorSafeDestroyNSSReference() + { + if (mCerts) + { + for (uint32_t i=0; i < mSize; i++) { + if (mCerts[i]) { + CERT_DestroyCertificate(mCerts[i]); + } + } + } + + if (mPoolp) + PORT_FreeArena(mPoolp, false); + } + + bool allocate(uint32_t count) + { + // only allow allocation once + if (mPoolp) + return false; + + mSize = count; + + if (!mSize) + return false; + + mPoolp = PORT_NewArena(1024); + if (!mPoolp) + return false; + + mCerts = (CERTCertificate**)PORT_ArenaZAlloc( + mPoolp, (count+1)*sizeof(CERTCertificate*)); + + if (!mCerts) + return false; + + // null array, including zero termination + for (uint32_t i = 0; i < count+1; i++) { + mCerts[i] = nullptr; + } + + return true; + } + + void set(uint32_t i, CERTCertificate *c) + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return; + + if (i >= mSize) + return; + + if (mCerts[i]) { + CERT_DestroyCertificate(mCerts[i]); + } + + mCerts[i] = CERT_DupCertificate(c); + } + + CERTCertificate *get(uint32_t i) + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return nullptr; + + if (i >= mSize) + return nullptr; + + return CERT_DupCertificate(mCerts[i]); + } + + CERTCertificate **getRawArray() + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return nullptr; + + return mCerts; + } + +private: + CERTCertificate **mCerts; + PLArenaPool *mPoolp; + uint32_t mSize; +}; + +NS_IMETHODIMP nsCMSMessage::CreateEncrypted(nsIArray * aRecipientCerts) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted\n")); + NSSCMSContentInfo *cinfo; + NSSCMSEnvelopedData *envd; + NSSCMSRecipientInfo *recipientInfo; + nsZeroTerminatedCertArray recipientCerts; + SECOidTag bulkAlgTag; + int keySize; + uint32_t i; + nsresult rv = NS_ERROR_FAILURE; + + // Check the recipient certificates // + uint32_t recipientCertCount; + aRecipientCerts->GetLength(&recipientCertCount); + PR_ASSERT(recipientCertCount > 0); + + if (!recipientCerts.allocate(recipientCertCount)) { + goto loser; + } + + for (i=0; i<recipientCertCount; i++) { + nsCOMPtr<nsIX509Cert> x509cert = do_QueryElementAt(aRecipientCerts, i); + + if (!x509cert) + return NS_ERROR_FAILURE; + + UniqueCERTCertificate c(x509cert->GetCert()); + recipientCerts.set(i, c.get()); + } + + // Find a bulk key algorithm // + if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipientCerts.getRawArray(), &bulkAlgTag, + &keySize) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't find bulk alg for recipients\n")); + rv = NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG; + goto loser; + } + + m_cmsMsg = NSS_CMSMessage_Create(nullptr); + if (!m_cmsMsg) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create new cms message\n")); + rv = NS_ERROR_OUT_OF_MEMORY; + goto loser; + } + + if ((envd = NSS_CMSEnvelopedData_Create(m_cmsMsg, bulkAlgTag, keySize)) == nullptr) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create enveloped data\n")); + goto loser; + } + + cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg); + if (NSS_CMSContentInfo_SetContent_EnvelopedData(m_cmsMsg, cinfo, envd) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create content enveloped data\n")); + goto loser; + } + + cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd); + if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, false) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't set content data\n")); + goto loser; + } + + // Create and attach recipient information // + for (i=0; i < recipientCertCount; i++) { + UniqueCERTCertificate rc(recipientCerts.get(i)); + if ((recipientInfo = NSS_CMSRecipientInfo_Create(m_cmsMsg, rc.get())) == nullptr) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create recipient info\n")); + goto loser; + } + if (NSS_CMSEnvelopedData_AddRecipient(envd, recipientInfo) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't add recipient info\n")); + goto loser; + } + } + + return NS_OK; +loser: + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + m_cmsMsg = nullptr; + } + + return rv; +} + +bool nsCMSMessage::IsAllowedHash(const int16_t aCryptoHashInt) +{ + switch (aCryptoHashInt) { + case nsICryptoHash::SHA1: + case nsICryptoHash::SHA256: + case nsICryptoHash::SHA384: + case nsICryptoHash::SHA512: + return true; + case nsICryptoHash::MD2: + case nsICryptoHash::MD5: + default: + return false; + } +} + +bool nsCMSMessage::GetIntHashToOidHash(const int16_t aCryptoHashInt, SECOidTag &aOidTag) +{ + bool rv = true; + + switch (aCryptoHashInt) { + case nsICryptoHash::MD2: + aOidTag = SEC_OID_MD2; + break; + case nsICryptoHash::MD5: + aOidTag = SEC_OID_MD5; + break; + case nsICryptoHash::SHA1: + aOidTag = SEC_OID_SHA1; + break; + case nsICryptoHash::SHA256: + aOidTag = SEC_OID_SHA256; + break; + case nsICryptoHash::SHA384: + aOidTag = SEC_OID_SHA384; + break; + case nsICryptoHash::SHA512: + aOidTag = SEC_OID_SHA512; + break; + default: + rv = false; + } + + return rv; +} + +NS_IMETHODIMP +nsCMSMessage::CreateSigned(nsIX509Cert* aSigningCert, nsIX509Cert* aEncryptCert, + unsigned char* aDigestData, uint32_t aDigestDataLen, + int16_t aDigestType) +{ + NS_ENSURE_ARG(aSigningCert); + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned\n")); + NSSCMSContentInfo *cinfo; + NSSCMSSignedData *sigd; + NSSCMSSignerInfo *signerinfo; + UniqueCERTCertificate scert(aSigningCert->GetCert()); + UniqueCERTCertificate ecert; + nsresult rv = NS_ERROR_FAILURE; + + if (!scert) { + return NS_ERROR_FAILURE; + } + + if (aEncryptCert) { + ecert = UniqueCERTCertificate(aEncryptCert->GetCert()); + } + + SECOidTag digestType; + if (!IsAllowedHash(aDigestType) || + !GetIntHashToOidHash(aDigestType, digestType)) { + return NS_ERROR_INVALID_ARG; + } + + /* + * create the message object + */ + m_cmsMsg = NSS_CMSMessage_Create(nullptr); /* create a message on its own pool */ + if (!m_cmsMsg) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create new message\n")); + rv = NS_ERROR_OUT_OF_MEMORY; + goto loser; + } + + /* + * build chain of objects: message->signedData->data + */ + if ((sigd = NSS_CMSSignedData_Create(m_cmsMsg)) == nullptr) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create signed data\n")); + goto loser; + } + cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg); + if (NSS_CMSContentInfo_SetContent_SignedData(m_cmsMsg, cinfo, sigd) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set content signed data\n")); + goto loser; + } + + cinfo = NSS_CMSSignedData_GetContentInfo(sigd); + + /* we're always passing data in and detaching optionally */ + if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, true) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set content data\n")); + goto loser; + } + + /* + * create & attach signer information + */ + signerinfo = NSS_CMSSignerInfo_Create(m_cmsMsg, scert.get(), digestType); + if (!signerinfo) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create signer info\n")); + goto loser; + } + + /* we want the cert chain included for this one */ + if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, + certUsageEmailSigner) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't include signer cert chain\n")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add signing time\n")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add smime caps\n")); + goto loser; + } + + if (ecert) { + if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, ecert.get(), + CERT_GetDefaultCertDB()) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add smime enc key prefs\n")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, ecert.get(), + CERT_GetDefaultCertDB()) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add MS smime enc key prefs\n")); + goto loser; + } + + // If signing and encryption cert are identical, don't add it twice. + bool addEncryptionCert = + (ecert && (!scert || !CERT_CompareCerts(ecert.get(), scert.get()))); + + if (addEncryptionCert && + NSS_CMSSignedData_AddCertificate(sigd, ecert.get()) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add own encryption certificate\n")); + goto loser; + } + } + + if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add signer info\n")); + goto loser; + } + + // Finally, add the pre-computed digest if passed in + if (aDigestData) { + SECItem digest; + + digest.data = aDigestData; + digest.len = aDigestDataLen; + + if (NSS_CMSSignedData_SetDigestValue(sigd, digestType, &digest) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set digest value\n")); + goto loser; + } + } + + return NS_OK; +loser: + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + m_cmsMsg = nullptr; + } + return rv; +} + +NS_IMPL_ISUPPORTS(nsCMSDecoder, nsICMSDecoder) + +nsCMSDecoder::nsCMSDecoder() +: m_dcx(nullptr) +{ +} + +nsCMSDecoder::~nsCMSDecoder() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +nsresult nsCMSDecoder::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +void nsCMSDecoder::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +void nsCMSDecoder::destructorSafeDestroyNSSReference() +{ + if (m_dcx) { + NSS_CMSDecoder_Cancel(m_dcx); + m_dcx = nullptr; + } +} + +/* void start (in NSSCMSContentCallback cb, in voidPtr arg); */ +NS_IMETHODIMP nsCMSDecoder::Start(NSSCMSContentCallback cb, void * arg) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Start\n")); + m_ctx = new PipUIContext(); + + m_dcx = NSS_CMSDecoder_Start(0, cb, arg, 0, m_ctx, 0, 0); + if (!m_dcx) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Start - can't start decoder\n")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void update (in string bug, in long len); */ +NS_IMETHODIMP nsCMSDecoder::Update(const char *buf, int32_t len) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Update\n")); + NSS_CMSDecoder_Update(m_dcx, (char *)buf, len); + return NS_OK; +} + +/* void finish (); */ +NS_IMETHODIMP nsCMSDecoder::Finish(nsICMSMessage ** aCMSMsg) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Finish\n")); + NSSCMSMessage *cmsMsg; + cmsMsg = NSS_CMSDecoder_Finish(m_dcx); + m_dcx = nullptr; + if (cmsMsg) { + nsCMSMessage *obj = new nsCMSMessage(cmsMsg); + // The NSS object cmsMsg still carries a reference to the context + // we gave it on construction. + // Make sure the context will live long enough. + obj->referenceContext(m_ctx); + *aCMSMsg = obj; + NS_ADDREF(*aCMSMsg); + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsCMSEncoder, nsICMSEncoder) + +nsCMSEncoder::nsCMSEncoder() +: m_ecx(nullptr) +{ +} + +nsCMSEncoder::~nsCMSEncoder() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +nsresult nsCMSEncoder::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +void nsCMSEncoder::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +void nsCMSEncoder::destructorSafeDestroyNSSReference() +{ + if (m_ecx) + NSS_CMSEncoder_Cancel(m_ecx); +} + +/* void start (); */ +NS_IMETHODIMP nsCMSEncoder::Start(nsICMSMessage *aMsg, NSSCMSContentCallback cb, void * arg) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Start\n")); + nsCMSMessage *cmsMsg = static_cast<nsCMSMessage*>(aMsg); + m_ctx = new PipUIContext(); + + m_ecx = NSS_CMSEncoder_Start(cmsMsg->getCMS(), cb, arg, 0, 0, 0, m_ctx, 0, 0, 0, 0); + if (!m_ecx) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Start - can't start encoder\n")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void update (in string aBuf, in long aLen); */ +NS_IMETHODIMP nsCMSEncoder::Update(const char *aBuf, int32_t aLen) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Update\n")); + if (!m_ecx || NSS_CMSEncoder_Update(m_ecx, aBuf, aLen) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Update - can't update encoder\n")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void finish (); */ +NS_IMETHODIMP nsCMSEncoder::Finish() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + nsresult rv = NS_OK; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Finish\n")); + if (!m_ecx || NSS_CMSEncoder_Finish(m_ecx) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Finish - can't finish encoder\n")); + rv = NS_ERROR_FAILURE; + } + m_ecx = nullptr; + return rv; +} + +/* void encode (in nsICMSMessage aMsg); */ +NS_IMETHODIMP nsCMSEncoder::Encode(nsICMSMessage *aMsg) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Encode\n")); + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/mailnews/mime/src/nsCMS.h b/mailnews/mime/src/nsCMS.h new file mode 100644 index 000000000..393344277 --- /dev/null +++ b/mailnews/mime/src/nsCMS.h @@ -0,0 +1,108 @@ +/* -*- 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/. */ + +#ifndef __NS_CMS_H__ +#define __NS_CMS_H__ + +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsXPIDLString.h" +#include "nsIInterfaceRequestor.h" +#include "nsICMSMessage.h" +#include "nsICMSMessage2.h" +#include "nsIX509Cert.h" +#include "nsICMSEncoder.h" +#include "nsICMSDecoder.h" +#include "sechash.h" +#include "cms.h" +#include "nsNSSShutDown.h" + +#define NS_CMSMESSAGE_CID \ + { 0xa4557478, 0xae16, 0x11d5, { 0xba,0x4b,0x00,0x10,0x83,0x03,0xb1,0x17 } } + +class nsCMSMessage : public nsICMSMessage, + public nsICMSMessage2, + public nsNSSShutDownObject +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICMSMESSAGE + NS_DECL_NSICMSMESSAGE2 + + nsCMSMessage(); + explicit nsCMSMessage(NSSCMSMessage* aCMSMsg); + nsresult Init(); + + void referenceContext(nsIInterfaceRequestor* aContext) {m_ctx = aContext;} + NSSCMSMessage* getCMS() {return m_cmsMsg;} +private: + virtual ~nsCMSMessage(); + nsCOMPtr<nsIInterfaceRequestor> m_ctx; + NSSCMSMessage * m_cmsMsg; + NSSCMSSignerInfo* GetTopLevelSignerInfo(); + nsresult CommonVerifySignature(unsigned char* aDigestData, uint32_t aDigestDataLen, + int16_t aDigestType); + + nsresult CommonAsyncVerifySignature(nsISMimeVerificationListener *aListener, + unsigned char* aDigestData, uint32_t aDigestDataLen, + int16_t aDigestType); + bool GetIntHashToOidHash(const int16_t aCryptoHashInt, SECOidTag &aOidTag); + bool IsAllowedHash(const int16_t aCryptoHashInt); + + virtual void virtualDestroyNSSReference() override; + void destructorSafeDestroyNSSReference(); + +}; + +// =============================================== +// nsCMSDecoder - implementation of nsICMSDecoder +// =============================================== + +#define NS_CMSDECODER_CID \ + { 0x9dcef3a4, 0xa3bc, 0x11d5, { 0xba, 0x47, 0x00, 0x10, 0x83, 0x03, 0xb1, 0x17 } } + +class nsCMSDecoder : public nsICMSDecoder, + public nsNSSShutDownObject +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICMSDECODER + + nsCMSDecoder(); + nsresult Init(); + +private: + virtual ~nsCMSDecoder(); + nsCOMPtr<nsIInterfaceRequestor> m_ctx; + NSSCMSDecoderContext *m_dcx; + virtual void virtualDestroyNSSReference() override; + void destructorSafeDestroyNSSReference(); +}; + +// =============================================== +// nsCMSEncoder - implementation of nsICMSEncoder +// =============================================== + +#define NS_CMSENCODER_CID \ + { 0xa15789aa, 0x8903, 0x462b, { 0x81, 0xe9, 0x4a, 0xa2, 0xcf, 0xf4, 0xd5, 0xcb } } +class nsCMSEncoder : public nsICMSEncoder, + public nsNSSShutDownObject +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICMSENCODER + + nsCMSEncoder(); + nsresult Init(); + +private: + virtual ~nsCMSEncoder(); + nsCOMPtr<nsIInterfaceRequestor> m_ctx; + NSSCMSEncoderContext *m_ecx; + virtual void virtualDestroyNSSReference() override; + void destructorSafeDestroyNSSReference(); +}; + +#endif diff --git a/mailnews/mime/src/nsCMSSecureMessage.cpp b/mailnews/mime/src/nsCMSSecureMessage.cpp new file mode 100644 index 000000000..0b7a99d7d --- /dev/null +++ b/mailnews/mime/src/nsCMSSecureMessage.cpp @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXPIDLString.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsIInterfaceRequestor.h" +#include "nsCRT.h" +#include "nsIX509CertDB.h" + +#include "nsICMSSecureMessage.h" + +#include "nsCMSSecureMessage.h" +#include "nsIX509Cert.h" +#include "nsNSSHelper.h" +#include "nsNSSCertificate.h" +#include "nsNSSShutDown.h" + +#include <string.h> +#include "plbase64.h" +#include "cert.h" +#include "cms.h" + +#include "nsIServiceManager.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" + +#include "mozilla/Logging.h" +#ifdef PR_LOGGING +extern mozilla::LazyLogModule gPIPNSSLog; +#endif + +using namespace mozilla; + +// Standard ISupports implementation +// NOTE: Should these be the thread-safe versions? + +/***** + * nsCMSSecureMessage + *****/ + +// Standard ISupports implementation +NS_IMPL_ISUPPORTS(nsCMSSecureMessage, nsICMSSecureMessage) + +// nsCMSSecureMessage constructor +nsCMSSecureMessage::nsCMSSecureMessage() +{ + // initialize superclass +} + +// nsCMSMessage destructor +nsCMSSecureMessage::~nsCMSSecureMessage() +{ +} + +nsresult nsCMSSecureMessage::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +/* string getCertByPrefID (in string certID); */ +NS_IMETHODIMP nsCMSSecureMessage:: +GetCertByPrefID(const char *certID, char **_retval) +{ + nsNSSShutDownPreventionLock locker; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::GetCertByPrefID\n")); + nsresult rv = NS_OK; + CERTCertificate *cert = 0; + nsXPIDLCString nickname; + nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext(); + + *_retval = 0; + + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + goto done; + } + + rv = prefs->GetCharPref(certID, + getter_Copies(nickname)); + if (NS_FAILED(rv)) goto done; + + /* Find a good cert in the user's database */ + cert = CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(), const_cast<char*>(nickname.get()), + certUsageEmailRecipient, true, ctx); + + if (!cert) { + /* Success, but no value */ + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::GetCertByPrefID - can't find user cert\n")); + goto done; + } + + /* Convert the DER to a BASE64 String */ + encode(cert->derCert.data, cert->derCert.len, _retval); + +done: + if (cert) CERT_DestroyCertificate(cert); + return rv; +} + + +// nsCMSSecureMessage::DecodeCert +nsresult nsCMSSecureMessage:: +DecodeCert(const char *value, nsIX509Cert ** _retval) +{ + nsNSSShutDownPreventionLock locker; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::DecodeCert\n")); + nsresult rv = NS_OK; + int32_t length; + unsigned char *data = 0; + + *_retval = 0; + + if (!value) { return NS_ERROR_FAILURE; } + + rv = decode(value, &data, &length); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::DecodeCert - can't decode cert\n")); + return rv; + } + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + if (!certdb) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIX509Cert> cert; + certdb->ConstructX509(reinterpret_cast<char *>(data), length, getter_AddRefs(cert)); + + if (cert) { + *_retval = cert; + NS_ADDREF(*_retval); + } + else { + rv = NS_ERROR_FAILURE; + } + + free((char*)data); + return rv; +} + +// nsCMSSecureMessage::SendMessage +nsresult nsCMSSecureMessage:: +SendMessage(const char *msg, const char *base64Cert, char ** _retval) +{ + nsNSSShutDownPreventionLock locker; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage\n")); + nsresult rv = NS_OK; + CERTCertificate *cert = 0; + NSSCMSMessage *cmsMsg = 0; + unsigned char *certDER = 0; + int32_t derLen; + NSSCMSEnvelopedData *env; + NSSCMSContentInfo *cinfo; + NSSCMSRecipientInfo *rcpt; + SECItem output; + PLArenaPool *arena = PORT_NewArena(1024); + SECStatus s; + nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext(); + + /* Step 0. Create a CMS Message */ + cmsMsg = NSS_CMSMessage_Create(nullptr); + if (!cmsMsg) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create NSSCMSMessage\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 1. Import the certificate into NSS */ + rv = decode(base64Cert, &certDER, &derLen); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't decode / import cert into NSS\n")); + goto done; + } + + cert = CERT_DecodeCertFromPackage((char *)certDER, derLen); + if (!cert) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't decode cert from package\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 2. Get a signature cert */ + + /* Step 3. Build inner (signature) content */ + + /* Step 4. Build outer (enveloped) content */ + env = NSS_CMSEnvelopedData_Create(cmsMsg, SEC_OID_DES_EDE3_CBC, 0); + if (!env) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create envelope data\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + cinfo = NSS_CMSEnvelopedData_GetContentInfo(env); + s = NSS_CMSContentInfo_SetContent_Data(cmsMsg, cinfo, 0, false); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't set content data\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + rcpt = NSS_CMSRecipientInfo_Create(cmsMsg, cert); + if (!rcpt) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create recipient info\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + s = NSS_CMSEnvelopedData_AddRecipient(env, rcpt); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't add recipient\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 5. Add content to message */ + cinfo = NSS_CMSMessage_GetContentInfo(cmsMsg); + s = NSS_CMSContentInfo_SetContent_EnvelopedData(cmsMsg, cinfo, env); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't set content enveloped data\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 6. Encode */ + NSSCMSEncoderContext *ecx; + + output.data = 0; output.len = 0; + ecx = NSS_CMSEncoder_Start(cmsMsg, 0, 0, &output, arena, + 0, ctx, 0, 0, 0, 0); + if (!ecx) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't start cms encoder\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + s = NSS_CMSEncoder_Update(ecx, msg, strlen(msg)); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't update encoder\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + s = NSS_CMSEncoder_Finish(ecx); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't finish encoder\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 7. Base64 encode and return the result */ + rv = encode(output.data, output.len, _retval); + +done: + if (certDER) free((char *)certDER); + if (cert) CERT_DestroyCertificate(cert); + if (cmsMsg) NSS_CMSMessage_Destroy(cmsMsg); + if (arena) PORT_FreeArena(arena, false); /* false? */ + + return rv; +} + +/* + * nsCMSSecureMessage::ReceiveMessage + */ +nsresult nsCMSSecureMessage:: +ReceiveMessage(const char *msg, char **_retval) +{ + nsNSSShutDownPreventionLock locker; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage\n")); + nsresult rv = NS_OK; + NSSCMSDecoderContext *dcx; + unsigned char *der = 0; + int32_t derLen; + NSSCMSMessage *cmsMsg = 0; + SECItem *content; + nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext(); + + /* Step 1. Decode the base64 wrapper */ + rv = decode(msg, &der, &derLen); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't base64 decode\n")); + goto done; + } + + dcx = NSS_CMSDecoder_Start(0, 0, 0, /* pw */ 0, ctx, /* key */ 0, 0); + if (!dcx) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't start decoder\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + (void)NSS_CMSDecoder_Update(dcx, (char *)der, derLen); + cmsMsg = NSS_CMSDecoder_Finish(dcx); + if (!cmsMsg) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't finish decoder\n")); + rv = NS_ERROR_FAILURE; + /* Memory leak on dcx?? */ + goto done; + } + + content = NSS_CMSMessage_GetContent(cmsMsg); + if (!content) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't get content\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Copy the data */ + *_retval = (char*)malloc(content->len+1); + memcpy(*_retval, content->data, content->len); + (*_retval)[content->len] = 0; + +done: + if (der) free(der); + if (cmsMsg) NSS_CMSMessage_Destroy(cmsMsg); + + return rv; +} + +nsresult nsCMSSecureMessage:: +encode(const unsigned char *data, int32_t dataLen, char **_retval) +{ + nsresult rv = NS_OK; + + *_retval = PL_Base64Encode((const char *)data, dataLen, nullptr); + if (!*_retval) { rv = NS_ERROR_OUT_OF_MEMORY; goto loser; } + +loser: + return rv; +} + +nsresult nsCMSSecureMessage:: +decode(const char *data, unsigned char **result, int32_t * _retval) +{ + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::decode\n")); + nsresult rv = NS_OK; + uint32_t len = strlen(data); + int adjust = 0; + + /* Compute length adjustment */ + if (data[len-1] == '=') { + adjust++; + if (data[len-2] == '=') adjust++; + } + + *result = (unsigned char *)PL_Base64Decode(data, len, nullptr); + if (!*result) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::decode - error decoding base64\n")); + rv = NS_ERROR_ILLEGAL_VALUE; + goto loser; + } + + *_retval = (len*3)/4 - adjust; + +loser: + return rv; +} diff --git a/mailnews/mime/src/nsCMSSecureMessage.h b/mailnews/mime/src/nsCMSSecureMessage.h new file mode 100644 index 000000000..a36124ab0 --- /dev/null +++ b/mailnews/mime/src/nsCMSSecureMessage.h @@ -0,0 +1,37 @@ +/* -*- 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/. */ + +#ifndef _NSCMSSECUREMESSAGE_H_ +#define _NSCMSSECUREMESSAGE_H_ + +#include "nsICMSSecureMessage.h" + +#include "cms.h" + +// =============================================== +// nsCMSManager - implementation of nsICMSManager +// =============================================== + +#define NS_CMSSECUREMESSAGE_CID \ + { 0x5fb907e0, 0x1dd2, 0x11b2, { 0xa7, 0xc0, 0xf1, 0x4c, 0x41, 0x6a, 0x62, 0xa1 } } + +class nsCMSSecureMessage +: public nsICMSSecureMessage +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICMSSECUREMESSAGE + + nsCMSSecureMessage(); + nsresult Init(); + +private: + virtual ~nsCMSSecureMessage(); + NS_METHOD encode(const unsigned char *data, int32_t dataLen, char **_retval); + NS_METHOD decode(const char *data, unsigned char **result, int32_t * _retval); +}; + + +#endif /* _NSCMSMESSAGE_H_ */ diff --git a/mailnews/mime/src/nsMimeObjectClassAccess.cpp b/mailnews/mime/src/nsMimeObjectClassAccess.cpp new file mode 100644 index 000000000..f1286dcc1 --- /dev/null +++ b/mailnews/mime/src/nsMimeObjectClassAccess.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <stdio.h> +#include "mimecom.h" +#include "nscore.h" +#include "nsMimeObjectClassAccess.h" + +/* + * The following macros actually implement addref, release and + * query interface for our component. + */ +NS_IMPL_ISUPPORTS(nsMimeObjectClassAccess, nsIMimeObjectClassAccess) + +/* + * nsMimeObjectClassAccess definitions.... + */ + +/* + * Inherited methods for nsMimeObjectClassAccess + */ +nsMimeObjectClassAccess::nsMimeObjectClassAccess() +{ +} + +nsMimeObjectClassAccess::~nsMimeObjectClassAccess() +{ +} + +nsresult +nsMimeObjectClassAccess::MimeObjectWrite(void *mimeObject, + char *data, + int32_t length, + bool user_visible_p) +{ + int rc = XPCOM_MimeObject_write(mimeObject, data, length, user_visible_p); + NS_ENSURE_FALSE(rc < 0, NS_ERROR_FAILURE); + + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeInlineTextClass(void **ptr) +{ + *ptr = XPCOM_GetmimeInlineTextClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeLeafClass(void **ptr) +{ + *ptr = XPCOM_GetmimeLeafClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeObjectClass(void **ptr) +{ + *ptr = XPCOM_GetmimeObjectClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeContainerClass(void **ptr) +{ + *ptr = XPCOM_GetmimeContainerClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeMultipartClass(void **ptr) +{ + *ptr = XPCOM_GetmimeMultipartClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeMultipartSignedClass(void **ptr) +{ + *ptr = XPCOM_GetmimeMultipartSignedClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeEncryptedClass(void **ptr) +{ + *ptr = XPCOM_GetmimeEncryptedClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::MimeCreate(char * content_type, void * hdrs, void * opts, void **ptr) +{ + *ptr = XPCOM_Mime_create(content_type, hdrs, opts); + return NS_OK; +} diff --git a/mailnews/mime/src/nsMimeObjectClassAccess.h b/mailnews/mime/src/nsMimeObjectClassAccess.h new file mode 100644 index 000000000..d4a189aac --- /dev/null +++ b/mailnews/mime/src/nsMimeObjectClassAccess.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface is implemented by libmime. This interface is used by + * a Content-Type handler "Plug In" (i.e. vCard) for accessing various + * internal information about the object class system of libmime. When + * libmime progresses to a C++ object class, this would probably change. + */ +#ifndef nsMimeObjectClassAccess_h_ +#define nsMimeObjectClassAccess_h_ + +#include "mozilla/Attributes.h" +#include "nsISupports.h" +#include "nsIMimeObjectClassAccess.h" + +class nsMimeObjectClassAccess : public nsIMimeObjectClassAccess { +public: + nsMimeObjectClassAccess(); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_ISUPPORTS + + // These methods are all implemented by libmime to be used by + // content type handler plugins for processing stream data. + + // This is the write call for outputting processed stream data. + NS_IMETHOD MimeObjectWrite(void *mimeObject, + char *data, + int32_t length, + bool user_visible_p) override; + + // The following group of calls expose the pointers for the object + // system within libmime. + NS_IMETHOD GetmimeInlineTextClass(void **ptr) override; + NS_IMETHOD GetmimeLeafClass(void **ptr) override; + NS_IMETHOD GetmimeObjectClass(void **ptr) override; + NS_IMETHOD GetmimeContainerClass(void **ptr) override; + NS_IMETHOD GetmimeMultipartClass(void **ptr) override; + NS_IMETHOD GetmimeMultipartSignedClass(void **ptr) override; + NS_IMETHOD GetmimeEncryptedClass(void **ptr) override; + + NS_IMETHOD MimeCreate(char *content_type, void * hdrs, + void * opts, void**ptr) override; + +private: + virtual ~nsMimeObjectClassAccess(); +}; + +#endif /* nsMimeObjectClassAccess_h_ */ diff --git a/mailnews/mime/src/nsMimeStringResources.h b/mailnews/mime/src/nsMimeStringResources.h new file mode 100644 index 000000000..4177c7863 --- /dev/null +++ b/mailnews/mime/src/nsMimeStringResources.h @@ -0,0 +1,40 @@ +/* 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/. */ + +#ifndef _NAME_OF_THIS_HEADER_FILE__ +#define _NAME_OF_THIS_HEADER_FILE__ + +/* Note that the negative values are not actually strings: they are error + * codes masquerading as strings. Do not pass them to MimeGetStringByID() + * expecting to get anything back for your trouble. + */ +#define MIME_OUT_OF_MEMORY -1000 +#define MIME_UNABLE_TO_OPEN_TMP_FILE -1001 +#define MIME_ERROR_WRITING_FILE -1002 +#define MIME_MHTML_SUBJECT 1000 +#define MIME_MHTML_RESENT_COMMENTS 1001 +#define MIME_MHTML_RESENT_DATE 1002 +#define MIME_MHTML_RESENT_SENDER 1003 +#define MIME_MHTML_RESENT_FROM 1004 +#define MIME_MHTML_RESENT_TO 1005 +#define MIME_MHTML_RESENT_CC 1006 +#define MIME_MHTML_DATE 1007 +#define MIME_MHTML_SENDER 1008 +#define MIME_MHTML_FROM 1009 +#define MIME_MHTML_REPLY_TO 1010 +#define MIME_MHTML_ORGANIZATION 1011 +#define MIME_MHTML_TO 1012 +#define MIME_MHTML_CC 1013 +#define MIME_MHTML_NEWSGROUPS 1014 +#define MIME_MHTML_FOLLOWUP_TO 1015 +#define MIME_MHTML_REFERENCES 1016 +#define MIME_MHTML_MESSAGE_ID 1021 +#define MIME_MHTML_BCC 1023 +#define MIME_MSG_LINK_TO_DOCUMENT 1026 +#define MIME_MSG_DOCUMENT_INFO 1027 +#define MIME_MSG_ATTACHMENT 1028 +#define MIME_MSG_DEFAULT_ATTACHMENT_NAME 1040 +#define MIME_FORWARDED_MESSAGE_HTML_USER_WROTE 1041 + +#endif /* _NAME_OF_THIS_HEADER_FILE__ */ diff --git a/mailnews/mime/src/nsSimpleMimeConverterStub.cpp b/mailnews/mime/src/nsSimpleMimeConverterStub.cpp new file mode 100644 index 000000000..ca608b54c --- /dev/null +++ b/mailnews/mime/src/nsSimpleMimeConverterStub.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimecth.h" +#include "mimeobj.h" +#include "mimetext.h" +#include "mimemoz2.h" +#include "mimecom.h" +#include "nsStringGlue.h" +#include "nsComponentManagerUtils.h" +#include "nsICategoryManager.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsISimpleMimeConverter.h" +#include "nsServiceManagerUtils.h" +#include "nsSimpleMimeConverterStub.h" + +typedef struct MimeSimpleStub MimeSimpleStub; +typedef struct MimeSimpleStubClass MimeSimpleStubClass; + +struct MimeSimpleStubClass { + MimeInlineTextClass text; +}; + +struct MimeSimpleStub { + MimeInlineText text; + nsCString *buffer; + nsCOMPtr<nsISimpleMimeConverter> innerScriptable; +}; + +#define MimeSimpleStubClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +MimeDefClass(MimeSimpleStub, MimeSimpleStubClass, mimeSimpleStubClass, NULL); + +static int +BeginGather(MimeObject *obj) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + int status = ((MimeObjectClass *)XPCOM_GetmimeLeafClass())->parse_begin(obj); + + if (status < 0) + return status; + + if (!obj->output_p || + !obj->options || + !obj->options->write_html_p) { + return 0; + } + + ssobj->buffer->Truncate(); + return 0; +} + +static int +GatherLine(const char *line, int32_t length, MimeObject *obj) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + + if (!obj->output_p || + !obj->options || + !obj->options->output_fn) { + return 0; + } + + if (!obj->options->write_html_p) + return MimeObject_write(obj, line, length, true); + + ssobj->buffer->Append(line); + return 0; +} + +static int +EndGather(MimeObject *obj, bool abort_p) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + + if (obj->closed_p) + return 0; + + int status = ((MimeObjectClass *)MIME_GetmimeInlineTextClass())->parse_eof(obj, abort_p); + if (status < 0) + return status; + + if (ssobj->buffer->IsEmpty()) + return 0; + + mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure); + nsIChannel *channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + ssobj->innerScriptable->SetUri(uri); + } + nsCString asHTML; + nsresult rv = ssobj->innerScriptable->ConvertToHTML(nsDependentCString(obj->content_type), + *ssobj->buffer, + asHTML); + if (NS_FAILED(rv)) { + NS_ASSERTION(NS_SUCCEEDED(rv), "converter failure"); + return -1; + } + + // MimeObject_write wants a non-const string for some reason, but it doesn't mutate it + status = MimeObject_write(obj, asHTML.get(), + asHTML.Length(), true); + if (status < 0) + return status; + return 0; +} + +static int +Initialize(MimeObject *obj) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + + nsresult rv; + nsCOMPtr<nsICategoryManager> catman = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return -1; + + nsAutoCString contentType; // lowercase + ToLowerCase(nsDependentCString(obj->content_type), contentType); + + nsCString value; + rv = catman->GetCategoryEntry(NS_SIMPLEMIMECONVERTERS_CATEGORY, + contentType.get(), getter_Copies(value)); + if (NS_FAILED(rv) || value.IsEmpty()) + return -1; + + ssobj->innerScriptable = do_CreateInstance(value.get(), &rv); + if (NS_FAILED(rv) || !ssobj->innerScriptable) + return -1; + ssobj->buffer = new nsCString(); + ((MimeObjectClass *)XPCOM_GetmimeLeafClass())->initialize(obj); + + return 0; +} + +static void +Finalize(MimeObject *obj) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + ssobj->innerScriptable = nullptr; + delete ssobj->buffer; +} + +static int +MimeSimpleStubClassInitialize(MimeSimpleStubClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *)clazz; + oclass->parse_begin = BeginGather; + oclass->parse_line = GatherLine; + oclass->parse_eof = EndGather; + oclass->initialize = Initialize; + oclass->finalize = Finalize; + return 0; +} + +class nsSimpleMimeConverterStub : public nsIMimeContentTypeHandler +{ +public: + explicit nsSimpleMimeConverterStub(const char *aContentType) : mContentType(aContentType) { } + + NS_DECL_ISUPPORTS + + NS_IMETHOD GetContentType(char **contentType) override + { + *contentType = ToNewCString(mContentType); + return *contentType ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + NS_IMETHOD CreateContentTypeHandlerClass(const char *contentType, + contentTypeHandlerInitStruct *initString, + MimeObjectClass **objClass) override; +private: + virtual ~nsSimpleMimeConverterStub() { } + nsCString mContentType; +}; + +NS_IMPL_ISUPPORTS(nsSimpleMimeConverterStub, nsIMimeContentTypeHandler) + +NS_IMETHODIMP +nsSimpleMimeConverterStub::CreateContentTypeHandlerClass(const char *contentType, + contentTypeHandlerInitStruct *initStruct, + MimeObjectClass **objClass) +{ + NS_ENSURE_ARG_POINTER(objClass); + + *objClass = (MimeObjectClass *)&mimeSimpleStubClass; + (*objClass)->superclass = (MimeObjectClass *)XPCOM_GetmimeInlineTextClass(); + NS_ENSURE_TRUE((*objClass)->superclass, NS_ERROR_UNEXPECTED); + + initStruct->force_inline_display = true; + return NS_OK;; +} + +nsresult +MIME_NewSimpleMimeConverterStub(const char *aContentType, + nsIMimeContentTypeHandler **aResult) +{ + RefPtr<nsSimpleMimeConverterStub> inst = new nsSimpleMimeConverterStub(aContentType); + NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY); + + return CallQueryInterface(inst.get(), aResult); +} diff --git a/mailnews/mime/src/nsSimpleMimeConverterStub.h b/mailnews/mime/src/nsSimpleMimeConverterStub.h new file mode 100644 index 000000000..bdc12e5e3 --- /dev/null +++ b/mailnews/mime/src/nsSimpleMimeConverterStub.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef NS_SIMPLE_MIME_CONVERTER_STUB_H_ +#define NS_SIMPLE_MIME_CONVERTER_STUB_H_ + +nsresult +MIME_NewSimpleMimeConverterStub(const char *aContentType, + nsIMimeContentTypeHandler **aResult); + +#endif /* NS_SIMPLE_MIME_CONVERTER_STUB_H_ */ diff --git a/mailnews/mime/src/nsStreamConverter.cpp b/mailnews/mime/src/nsStreamConverter.cpp new file mode 100644 index 000000000..0d1781498 --- /dev/null +++ b/mailnews/mime/src/nsStreamConverter.cpp @@ -0,0 +1,1157 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include <stdio.h> +#include "mimecom.h" +#include "modmimee.h" +#include "nscore.h" +#include "nsStreamConverter.h" +#include "prmem.h" +#include "prprf.h" +#include "prlog.h" +#include "plstr.h" +#include "mimemoz2.h" +#include "nsMimeTypes.h" +#include "nsIComponentManager.h" +#include "nsIURL.h" +#include "nsStringGlue.h" +#include "nsUnicharUtils.h" +#include "nsIServiceManager.h" +#include "nsMemory.h" +#include "nsIPipe.h" +#include "nsMimeStringResources.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsNetUtil.h" +#include "nsIMsgQuote.h" +#include "nsIScriptSecurityManager.h" +#include "nsNetUtil.h" +#include "mozITXTToHTMLConv.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgWindow.h" +#include "nsICategoryManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsMsgUtils.h" +#include "mozilla/ArrayUtils.h" + +#define PREF_MAIL_DISPLAY_GLYPH "mail.display_glyph" +#define PREF_MAIL_DISPLAY_STRUCT "mail.display_struct" + +//////////////////////////////////////////////////////////////// +// Bridge routines for new stream converter XP-COM interface +//////////////////////////////////////////////////////////////// + +extern "C" void * +mime_bridge_create_draft_stream(nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out); + +extern "C" void * +bridge_create_stream(nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out, + uint32_t whattodo, + nsIChannel *aChannel) +{ + if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (format_out == nsMimeOutput::nsMimeMessageEditorTemplate) ) + return mime_bridge_create_draft_stream(newEmitter, newPluginObj2, uri, format_out); + else + return mime_bridge_create_display_stream(newEmitter, newPluginObj2, uri, format_out, whattodo, + aChannel); +} + +void +bridge_destroy_stream(void *newStream) +{ + nsMIMESession *stream = (nsMIMESession *)newStream; + if (!stream) + return; + + PR_FREEIF(stream); +} + +void +bridge_set_output_type(void *bridgeStream, nsMimeOutputType aType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + + if (session) + { + // BAD ASSUMPTION!!!! NEED TO CHECK aType + mime_stream_data *msd = (mime_stream_data *)session->data_object; + if (msd) + msd->format_out = aType; // output format type + } +} + +nsresult +bridge_new_new_uri(void *bridgeStream, nsIURI *aURI, int32_t aOutputType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + const char **fixup_pointer = nullptr; + + if (session) + { + if (session->data_object) + { + bool *override_charset = nullptr; + char **default_charset = nullptr; + char **url_name = nullptr; + + if ( (aOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (aOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)session->data_object; + if (mdd->options) + { + default_charset = &(mdd->options->default_charset); + override_charset = &(mdd->options->override_charset); + url_name = &(mdd->url_name); + } + } + else + { + mime_stream_data *msd = (mime_stream_data *)session->data_object; + + if (msd->options) + { + default_charset = &(msd->options->default_charset); + override_charset = &(msd->options->override_charset); + url_name = &(msd->url_name); + fixup_pointer = &(msd->options->url); + } + } + + if ( (default_charset) && (override_charset) && (url_name) ) + { + // + // set the default charset to be the folder charset if we have one associated with + // this url... + nsCOMPtr<nsIMsgI18NUrl> i18nUrl (do_QueryInterface(aURI)); + if (i18nUrl) + { + nsCString charset; + + // check to see if we have a charset override...and if we do, set that field appropriately too... + nsresult rv = i18nUrl->GetCharsetOverRide(getter_Copies(charset)); + if (NS_SUCCEEDED(rv) && !charset.IsEmpty() ) { + *override_charset = true; + *default_charset = ToNewCString(charset); + } + else + { + i18nUrl->GetFolderCharset(getter_Copies(charset)); + if (!charset.IsEmpty()) + *default_charset = ToNewCString(charset); + } + + // if there is no manual override and a folder charset exists + // then check if we have a folder level override + if (!(*override_charset) && *default_charset && **default_charset) + { + bool folderCharsetOverride; + rv = i18nUrl->GetFolderCharsetOverride(&folderCharsetOverride); + if (NS_SUCCEEDED(rv) && folderCharsetOverride) + *override_charset = true; + + // notify the default to msgWindow (for the menu check mark) + // do not set the default in case of nsMimeMessageDraftOrTemplate + // or nsMimeMessageEditorTemplate because it is already set + // when the message is displayed and doing it again may overwrite + // the correct MIME charset parsed from the message header + if (aOutputType != nsMimeOutput::nsMimeMessageDraftOrTemplate && + aOutputType != nsMimeOutput::nsMimeMessageEditorTemplate) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(aURI)); + if (msgurl) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + msgWindow->SetMailCharacterSet(nsDependentCString(*default_charset)); + msgWindow->SetCharsetOverride(*override_charset); + } + } + } + + // if the pref says always override and no manual override then set the folder charset to override + if (!*override_charset) { + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + bool force_override; + rv = pPrefBranch->GetBoolPref("mailnews.force_charset_override", &force_override); + if (NS_SUCCEEDED(rv) && force_override) + { + *override_charset = true; + } + } + } + } + } + nsAutoCString urlString; + if (NS_SUCCEEDED(aURI->GetSpec(urlString))) + { + if (!urlString.IsEmpty()) + { + NS_Free(*url_name); + *url_name = ToNewCString(urlString); + if (!(*url_name)) + return NS_ERROR_OUT_OF_MEMORY; + + // rhp: Ugh, this is ugly...but it works. + if (fixup_pointer) + *fixup_pointer = (const char *)*url_name; + } + } + } + } + } + + return NS_OK; +} + +static int +mime_headers_callback ( void *closure, MimeHeaders *headers ) +{ + // We get away with this because this doesn't get called on draft operations. + mime_stream_data *msd = (mime_stream_data *)closure; + + NS_ASSERTION(msd && headers, "null mime stream data or headers"); + if ( !msd || ! headers ) + return 0; + + NS_ASSERTION(!msd->headers, "non-null mime stream data headers"); + msd->headers = MimeHeaders_copy ( headers ); + return 0; +} + +nsresult +bridge_set_mime_stream_converter_listener(void *bridgeStream, nsIMimeStreamConverterListener* listener, + nsMimeOutputType aOutputType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + + if ( (session) && (session->data_object) ) + { + if ( (aOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (aOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)session->data_object; + if (mdd->options) + { + if (listener) + { + mdd->options->caller_need_root_headers = true; + mdd->options->decompose_headers_info_fn = mime_headers_callback; + } + else + { + mdd->options->caller_need_root_headers = false; + mdd->options->decompose_headers_info_fn = nullptr; + } + } + } + else + { + mime_stream_data *msd = (mime_stream_data *)session->data_object; + + if (msd->options) + { + if (listener) + { + msd->options->caller_need_root_headers = true; + msd->options->decompose_headers_info_fn = mime_headers_callback; + } + else + { + msd->options->caller_need_root_headers = false; + msd->options->decompose_headers_info_fn = nullptr; + } + } + } + } + + return NS_OK; +} + +// find a query element in a url and return a pointer to its data +// (query must be in the form "query=") +static const char * +FindQueryElementData(const char * aUrl, const char * aQuery) +{ + if (aUrl && aQuery) + { + size_t queryLen = 0; // we don't call strlen until we need to + aUrl = PL_strcasestr(aUrl, aQuery); + while (aUrl) + { + if (!queryLen) + queryLen = strlen(aQuery); + if (*(aUrl-1) == '&' || *(aUrl-1) == '?') + return aUrl + queryLen; + aUrl = PL_strcasestr(aUrl + queryLen, aQuery); + } + } + return nullptr; +} + +// case-sensitive test for string prefixing. If |string| is prefixed +// by |prefix| then a pointer to the next character in |string| following +// the prefix is returned. If it is not a prefix then |nullptr| is returned. +static const char * +SkipPrefix(const char *aString, const char *aPrefix) +{ + while (*aPrefix) + if (*aPrefix++ != *aString++) + return nullptr; + return aString; +} + +// +// Utility routines needed by this interface +// +nsresult +nsStreamConverter::DetermineOutputFormat(const char *aUrl, nsMimeOutputType *aNewType) +{ + // sanity checking + NS_ENSURE_ARG_POINTER(aNewType); + if (!aUrl || !*aUrl) + { + // default to html for the entire document + *aNewType = nsMimeOutput::nsMimeMessageQuoting; + mOutputFormat = "text/html"; + return NS_OK; + } + + // shorten the url that we test for the query strings by skipping directly + // to the part where the query strings begin. + const char *queryPart = PL_strchr(aUrl, '?'); + + // First, did someone pass in a desired output format. They will be able to + // pass in any content type (i.e. image/gif, text/html, etc...but the "/" will + // have to be represented via the "%2F" value + const char *format = FindQueryElementData(queryPart, "outformat="); + if (format) + { + //NOTE: I've done a file contents search of every file (*.*) in the mozilla + // directory tree and there is not a single location where the string "outformat" + // is added to any URL. It appears that this code has been orphaned off by a change + // elsewhere and is no longer required. It will be removed in the future unless + // someone complains. + MOZ_ASSERT(false, "Is this code actually being used?"); + + while (*format == ' ') + ++format; + + if (*format) + { + mOverrideFormat = "raw"; + + // set mOutputFormat to the supplied format, ensure that we replace any + // %2F strings with the slash character + const char *nextField = PL_strpbrk(format, "&; "); + mOutputFormat.Assign(format, nextField ? nextField - format : -1); + MsgReplaceSubstring(mOutputFormat, "%2F", "/"); + MsgReplaceSubstring(mOutputFormat, "%2f", "/"); + + // Don't muck with this data! + *aNewType = nsMimeOutput::nsMimeMessageRaw; + return NS_OK; + } + } + + // is this is a part that should just come out raw + const char *part = FindQueryElementData(queryPart, "part="); + if (part && !mToType.Equals("application/vnd.mozilla.xul+xml")) + { + // default for parts + mOutputFormat = "raw"; + *aNewType = nsMimeOutput::nsMimeMessageRaw; + + // if we are being asked to fetch a part....it should have a + // content type appended to it...if it does, we want to remember + // that as mOutputFormat + const char * typeField = FindQueryElementData(queryPart, "type="); + if (typeField && !strncmp(typeField, "application/x-message-display", sizeof("application/x-message-display") - 1)) + { + const char *secondTypeField = FindQueryElementData(typeField, "type="); + if (secondTypeField) + typeField = secondTypeField; + } + if (typeField) + { + // store the real content type...mOutputFormat gets deleted later on... + // and make sure we only get our own value. + char *nextField = PL_strchr(typeField, '&'); + mRealContentType.Assign(typeField, nextField ? nextField - typeField : -1); + if (mRealContentType.Equals("message/rfc822")) + { + mRealContentType = "application/x-message-display"; + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + } + else if (mRealContentType.Equals("application/x-message-display")) + { + mRealContentType = ""; + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + } + } + + return NS_OK; + } + + const char *emitter = FindQueryElementData(queryPart, "emitter="); + if (emitter) + { + const char *remainder = SkipPrefix(emitter, "js"); + if (remainder && (!*remainder || *remainder == '&')) + mOverrideFormat = "application/x-js-mime-message"; + } + + // if using the header query + const char *header = FindQueryElementData(queryPart, "header="); + if (header) + { + struct HeaderType { + const char * headerType; + const char * outputFormat; + nsMimeOutputType mimeOutputType; + }; + + // place most commonly used options at the top + static const struct HeaderType rgTypes[] = + { + { "filter", "text/html", nsMimeOutput::nsMimeMessageFilterSniffer }, + { "quotebody", "text/html", nsMimeOutput::nsMimeMessageBodyQuoting }, + { "print", "text/html", nsMimeOutput::nsMimeMessagePrintOutput }, + { "only", "text/xml", nsMimeOutput::nsMimeMessageHeaderDisplay }, + { "none", "text/html", nsMimeOutput::nsMimeMessageBodyDisplay }, + { "quote", "text/html", nsMimeOutput::nsMimeMessageQuoting }, + { "saveas", "text/html", nsMimeOutput::nsMimeMessageSaveAs }, + { "src", "text/plain", nsMimeOutput::nsMimeMessageSource }, + { "attach", "raw", nsMimeOutput::nsMimeMessageAttach } + }; + + // find the requested header in table, ensure that we don't match on a prefix + // by checking that the following character is either null or the next query element + const char * remainder; + for (uint32_t n = 0; n < MOZ_ARRAY_LENGTH(rgTypes); ++n) + { + remainder = SkipPrefix(header, rgTypes[n].headerType); + if (remainder && (*remainder == '\0' || *remainder == '&')) + { + mOutputFormat = rgTypes[n].outputFormat; + *aNewType = rgTypes[n].mimeOutputType; + return NS_OK; + } + } + } + + // default to html for just the body + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + + return NS_OK; +} + +nsresult +nsStreamConverter::InternalCleanup(void) +{ + if (mBridgeStream) + { + bridge_destroy_stream(mBridgeStream); + mBridgeStream = nullptr; + } + + return NS_OK; +} + +/* + * Inherited methods for nsMimeConverter + */ +nsStreamConverter::nsStreamConverter() +{ + // Init member variables... + mWrapperOutput = false; + mBridgeStream = nullptr; + mOutputFormat = "text/html"; + mAlreadyKnowOutputType = false; + mForwardInline = false; + mForwardInlineFilter = false; + mOverrideComposeFormat = false; + + mPendingRequest = nullptr; + mPendingContext = nullptr; +} + +nsStreamConverter::~nsStreamConverter() +{ + InternalCleanup(); +} + +NS_IMPL_ISUPPORTS(nsStreamConverter, nsIStreamListener, nsIRequestObserver, + nsIStreamConverter, nsIMimeStreamConverter) + +/////////////////////////////////////////////////////////////// +// nsStreamConverter definitions.... +/////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsStreamConverter::Init(nsIURI *aURI, nsIStreamListener * aOutListener, nsIChannel *aChannel) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsresult rv = NS_OK; + mOutListener = aOutListener; + + // mscott --> we need to look at the url and figure out what the correct output type is... + nsMimeOutputType newType = mOutputType; + if (!mAlreadyKnowOutputType) + { + nsAutoCString urlSpec; + rv = aURI->GetSpec(urlSpec); + DetermineOutputFormat(urlSpec.get(), &newType); + mAlreadyKnowOutputType = true; + mOutputType = newType; + } + + switch (newType) + { + case nsMimeOutput::nsMimeMessageSplitDisplay: // the wrapper HTML output to produce the split header/body display + mWrapperOutput = true; + mOutputFormat = "text/html"; + break; + case nsMimeOutput::nsMimeMessageHeaderDisplay: // the split header/body display + mOutputFormat = "text/xml"; + break; + case nsMimeOutput::nsMimeMessageBodyDisplay: // the split header/body display + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted output + case nsMimeOutput::nsMimeMessageSaveAs: // Save as operation + case nsMimeOutput::nsMimeMessageBodyQuoting: // only HTML body quoted output + case nsMimeOutput::nsMimeMessagePrintOutput: // all Printing output + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageAttach: + case nsMimeOutput::nsMimeMessageDecrypt: + case nsMimeOutput::nsMimeMessageRaw: // the raw RFC822 data and attachments + mOutputFormat = "raw"; + break; + + case nsMimeOutput::nsMimeMessageSource: // the raw RFC822 data (view source) and attachments + mOutputFormat = "text/plain"; + mOverrideFormat = "raw"; + break; + + case nsMimeOutput::nsMimeMessageDraftOrTemplate: // Loading drafts & templates + mOutputFormat = "message/draft"; + break; + + case nsMimeOutput::nsMimeMessageEditorTemplate: // Loading templates into editor + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageFilterSniffer: // output all displayable part as raw + mOutputFormat = "text/html"; + break; + + default: + NS_ERROR("this means I made a mistake in my assumptions"); + } + + + // the following output channel stream is used to fake the content type for people who later + // call into us.. + nsCString contentTypeToUse; + GetContentType(getter_Copies(contentTypeToUse)); + // mscott --> my theory is that we don't need this fake outgoing channel. Let's use the + // original channel and just set our content type ontop of the original channel... + + aChannel->SetContentType(contentTypeToUse); + + //rv = NS_NewInputStreamChannel(getter_AddRefs(mOutgoingChannel), aURI, nullptr, contentTypeToUse, -1); + //if (NS_FAILED(rv)) + // return rv; + + // Set system principal for this document, which will be dynamically generated + + // We will first find an appropriate emitter in the repository that supports + // the requested output format...note, the special exceptions are nsMimeMessageDraftOrTemplate + // or nsMimeMessageEditorTemplate where we don't need any emitters + // + + if ( (newType != nsMimeOutput::nsMimeMessageDraftOrTemplate) && + (newType != nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + nsAutoCString categoryName ("@mozilla.org/messenger/mimeemitter;1?type="); + if (!mOverrideFormat.IsEmpty()) + categoryName += mOverrideFormat; + else + categoryName += mOutputFormat; + + nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCString contractID; + catman->GetCategoryEntry("mime-emitter", categoryName.get(), getter_Copies(contractID)); + if (!contractID.IsEmpty()) + categoryName = contractID; + } + + mEmitter = do_CreateInstance(categoryName.get(), &rv); + + if ((NS_FAILED(rv)) || (!mEmitter)) + { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // initialize our emitter + if (mEmitter) + { + // Now we want to create a pipe which we'll use for converting the data. + nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1"); + rv = pipe->Init(true, true, 4096, 8); + NS_ENSURE_SUCCESS(rv, rv); + + // These always succeed because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(mInputStream))); + MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(mOutputStream))); + + mEmitter->Initialize(aURI, aChannel, newType); + mEmitter->SetPipe(mInputStream, mOutputStream); + mEmitter->SetOutputListener(aOutListener); + } + + uint32_t whattodo = mozITXTToHTMLConv::kURLs; + bool enable_emoticons = true; + bool enable_structs = true; + + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + rv = pPrefBranch->GetBoolPref(PREF_MAIL_DISPLAY_GLYPH,&enable_emoticons); + if (NS_FAILED(rv) || enable_emoticons) + { + whattodo = whattodo | mozITXTToHTMLConv::kGlyphSubstitution; + } + rv = pPrefBranch->GetBoolPref(PREF_MAIL_DISPLAY_STRUCT,&enable_structs); + if (NS_FAILED(rv) || enable_structs) + { + whattodo = whattodo | mozITXTToHTMLConv::kStructPhrase; + } + } + + if (mOutputType == nsMimeOutput::nsMimeMessageSource) + return NS_OK; + else + { + mBridgeStream = bridge_create_stream(mEmitter, this, aURI, newType, whattodo, aChannel); + if (!mBridgeStream) + return NS_ERROR_OUT_OF_MEMORY; + else + { + SetStreamURI(aURI); + + //Do we need to setup an Mime Stream Converter Listener? + if (mMimeStreamConverterListener) + bridge_set_mime_stream_converter_listener((nsMIMESession *)mBridgeStream, mMimeStreamConverterListener, mOutputType); + + return NS_OK; + } + } +} + +NS_IMETHODIMP nsStreamConverter::GetContentType(char **aOutputContentType) +{ + if (!aOutputContentType) + return NS_ERROR_NULL_POINTER; + + // since this method passes a string through an IDL file we need to use nsMemory to allocate it + // and not strdup! + // (1) check to see if we have a real content type...use it first... + if (!mRealContentType.IsEmpty()) + *aOutputContentType = ToNewCString(mRealContentType); + else if (mOutputFormat.Equals("raw")) + *aOutputContentType = (char *) nsMemory::Clone(UNKNOWN_CONTENT_TYPE, sizeof(UNKNOWN_CONTENT_TYPE)); + else + *aOutputContentType = ToNewCString(mOutputFormat); + return NS_OK; +} + +// +// This is the type of output operation that is being requested by libmime. The types +// of output are specified by nsIMimeOutputType enum +// +nsresult +nsStreamConverter::SetMimeOutputType(nsMimeOutputType aType) +{ + mAlreadyKnowOutputType = true; + mOutputType = aType; + if (mBridgeStream) + bridge_set_output_type(mBridgeStream, aType); + return NS_OK; +} + +NS_IMETHODIMP nsStreamConverter::GetMimeOutputType(nsMimeOutputType *aOutFormat) +{ + nsresult rv = NS_OK; + if (aOutFormat) + *aOutFormat = mOutputType; + else + rv = NS_ERROR_NULL_POINTER; + + return rv; +} + +// +// This is needed by libmime for MHTML link processing...this is the URI associated +// with this input stream +// +nsresult +nsStreamConverter::SetStreamURI(nsIURI *aURI) +{ + mURI = aURI; + if (mBridgeStream) + return bridge_new_new_uri((nsMIMESession *)mBridgeStream, aURI, mOutputType); + else + return NS_OK; +} + +nsresult +nsStreamConverter::SetMimeHeadersListener(nsIMimeStreamConverterListener *listener, nsMimeOutputType aType) +{ + mMimeStreamConverterListener = listener; + bridge_set_mime_stream_converter_listener((nsMIMESession *)mBridgeStream, listener, aType); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardInline(bool aForwardInline) +{ + mForwardInline = aForwardInline; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardToAddress(nsAString &aAddress) +{ + aAddress = mForwardToAddress; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardToAddress(const nsAString &aAddress) +{ + mForwardToAddress = aAddress; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOverrideComposeFormat(bool *aResult) +{ + if (!aResult) + return NS_ERROR_NULL_POINTER; + *aResult = mOverrideComposeFormat; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOverrideComposeFormat(bool aOverrideComposeFormat) +{ + mOverrideComposeFormat = aOverrideComposeFormat; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardInline(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mForwardInline; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardInlineFilter(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mForwardInlineFilter; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardInlineFilter(bool aForwardInlineFilter) +{ + mForwardInlineFilter = aForwardInlineFilter; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetIdentity(nsIMsgIdentity * *aIdentity) +{ + if (!aIdentity) return NS_ERROR_NULL_POINTER; + /* + We don't have an identity for the local folders account, + we will return null but it is not an error! + */ + *aIdentity = mIdentity; + NS_IF_ADDREF(*aIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetIdentity(nsIMsgIdentity * aIdentity) +{ + mIdentity = aIdentity; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOriginalMsgURI(const char * originalMsgURI) +{ + mOriginalMsgURI = originalMsgURI; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOriginalMsgURI(char ** result) +{ + if (!result) return NS_ERROR_NULL_POINTER; + *result = ToNewCString(mOriginalMsgURI); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOrigMsgHdr(nsIMsgDBHdr *aMsgHdr) +{ + mOrigMsgHdr = aMsgHdr; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOrigMsgHdr(nsIMsgDBHdr * *aMsgHdr) +{ + if (!aMsgHdr) return NS_ERROR_NULL_POINTER; + NS_IF_ADDREF(*aMsgHdr = mOrigMsgHdr); + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Methods for nsIStreamListener... +///////////////////////////////////////////////////////////////////////////// +// +// Notify the client that data is available in the input stream. This +// method is called whenver data is written into the input stream by the +// networking library... +// +nsresult +nsStreamConverter::OnDataAvailable(nsIRequest *request, + nsISupports *ctxt, + nsIInputStream *aIStream, + uint64_t sourceOffset, + uint32_t aLength) +{ + nsresult rc=NS_OK; // should this be an error instead? + uint32_t readLen = aLength; + uint32_t written; + + // If this is the first time through and we are supposed to be + // outputting the wrapper two pane URL, then do it now. + if (mWrapperOutput) + { + char outBuf[1024]; +const char output[] = "\ +<HTML>\ +<FRAMESET ROWS=\"30%%,70%%\">\ +<FRAME NAME=messageHeader SRC=\"%s?header=only\">\ +<FRAME NAME=messageBody SRC=\"%s?header=none\">\ +</FRAMESET>\ +</HTML>"; + + nsAutoCString url; + if (NS_FAILED(mURI->GetSpec(url))) + return NS_ERROR_FAILURE; + + PR_snprintf(outBuf, sizeof(outBuf), output, url.get(), url.get()); + + if (mEmitter) + mEmitter->Write(nsDependentCString(outBuf), &written); + + // rhp: will this stop the stream???? Not sure. + return NS_ERROR_FAILURE; + } + + char *buf = (char *)PR_Malloc(aLength); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */ + + readLen = aLength; + aIStream->Read(buf, aLength, &readLen); + + // We need to filter out any null characters else we will have a lot of trouble + // as we use c string everywhere in mime + char * readPtr; + char * endPtr = buf + readLen; + + // First let see if the stream contains null characters + for (readPtr = buf; readPtr < endPtr && *readPtr; readPtr ++) + ; + + // Did we find a null character? Then, we need to cleanup the stream + if (readPtr < endPtr) + { + char * writePtr = readPtr; + for (readPtr ++; readPtr < endPtr; readPtr ++) + { + if (!*readPtr) + continue; + + *writePtr = *readPtr; + writePtr ++; + } + readLen = writePtr - buf; + } + + if (mOutputType == nsMimeOutput::nsMimeMessageSource) + { + rc = NS_OK; + if (mEmitter) + { + rc = mEmitter->Write(Substring(buf, buf+readLen), &written); + } + } + else if (mBridgeStream) + { + nsMIMESession *tSession = (nsMIMESession *) mBridgeStream; + // XXX Casting int to nsresult + rc = static_cast<nsresult>( + tSession->put_block((nsMIMESession *)mBridgeStream, buf, readLen)); + } + + PR_FREEIF(buf); + return rc; +} + +///////////////////////////////////////////////////////////////////////////// +// Methods for nsIRequestObserver +///////////////////////////////////////////////////////////////////////////// +// +// Notify the observer that the URL has started to load. This method is +// called only once, at the beginning of a URL load. +// +nsresult +nsStreamConverter::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ +#ifdef DEBUG_rhp + printf("nsStreamConverter::OnStartRequest()\n"); +#endif + +#ifdef DEBUG_mscott + mConvertContentTime = PR_IntervalNow(); +#endif + + // here's a little bit of hackery.... + // since the mime converter is now between the channel + // and the + if (request) + { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (channel) + { + nsCString contentType; + GetContentType(getter_Copies(contentType)); + + channel->SetContentType(contentType); + } + } + + // forward the start request to any listeners + if (mOutListener) + { + if (mOutputType == nsMimeOutput::nsMimeMessageRaw) + { + //we need to delay the on start request until we have figure out the real content type + mPendingRequest = request; + mPendingContext = ctxt; + } + else + mOutListener->OnStartRequest(request, ctxt); + } + + return NS_OK; +} + +// +// Notify the observer that the URL has finished loading. This method is +// called once when the networking library has finished processing the +// +nsresult +nsStreamConverter::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) +{ + // Make sure we fire any pending OnStartRequest before we do OnStop. + FirePendingStartRequest(); +#ifdef DEBUG_rhp + printf("nsStreamConverter::OnStopRequest()\n"); +#endif + + // + // Now complete the stream! + // + if (mBridgeStream) + { + nsMIMESession *tSession = (nsMIMESession *) mBridgeStream; + + if (mMimeStreamConverterListener) + { + + MimeHeaders **workHeaders = nullptr; + + if ( (mOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (mOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)tSession->data_object; + if (mdd) + workHeaders = &(mdd->headers); + } + else + { + mime_stream_data *msd = (mime_stream_data *)tSession->data_object; + if (msd) + workHeaders = &(msd->headers); + } + + if (workHeaders) + { + nsresult rv; + nsCOMPtr<nsIMimeHeaders> mimeHeaders = do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + { + if (*workHeaders) + mimeHeaders->Initialize(Substring((*workHeaders)->all_headers, + (*workHeaders)->all_headers_fp)); + mMimeStreamConverterListener->OnHeadersReady(mimeHeaders); + } + else + mMimeStreamConverterListener->OnHeadersReady(nullptr); + } + + mMimeStreamConverterListener = nullptr; // release our reference + } + + tSession->complete((nsMIMESession *)mBridgeStream); + } + + // + // Now complete the emitter and do necessary cleanup! + // + if (mEmitter) + { + mEmitter->Complete(); + } + + // First close the output stream... + if (mOutputStream) + mOutputStream->Close(); + + // Make sure to do necessary cleanup! + InternalCleanup(); + +#if 0 + // print out the mime timing information BEFORE we flush to layout + // otherwise we'll be including layout information. + printf("Time Spent in mime: %d ms\n", PR_IntervalToMilliseconds(PR_IntervalNow() - mConvertContentTime)); +#endif + + // forward on top request to any listeners + if (mOutListener) + mOutListener->OnStopRequest(request, ctxt, status); + + + mAlreadyKnowOutputType = false; + + // since we are done converting data, lets close all the objects we own... + // this helps us fix some circular ref counting problems we are running into... + Close(); + + // Time to return... + return NS_OK; +} + +nsresult nsStreamConverter::Close() +{ + mOutgoingChannel = nullptr; + mEmitter = nullptr; + mOutListener = nullptr; + return NS_OK; +} + +// nsIStreamConverter implementation + +// No syncronous conversion at this time. +NS_IMETHODIMP nsStreamConverter::Convert(nsIInputStream *aFromStream, + const char *aFromType, + const char *aToType, + nsISupports *aCtxt, + nsIInputStream **_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +// Stream converter service calls this to initialize the actual stream converter (us). +NS_IMETHODIMP nsStreamConverter::AsyncConvertData(const char *aFromType, + const char *aToType, + nsIStreamListener *aListener, + nsISupports *aCtxt) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgQuote> aMsgQuote = do_QueryInterface(aCtxt, &rv); + nsCOMPtr<nsIChannel> aChannel; + + if (aMsgQuote) + { + nsCOMPtr<nsIMimeStreamConverterListener> quoteListener; + rv = aMsgQuote->GetQuoteListener(getter_AddRefs(quoteListener)); + if (quoteListener) + SetMimeHeadersListener(quoteListener, nsMimeOutput::nsMimeMessageQuoting); + rv = aMsgQuote->GetQuoteChannel(getter_AddRefs(aChannel)); + } + else + { + aChannel = do_QueryInterface(aCtxt, &rv); + } + + mFromType = aFromType; + mToType = aToType; + + NS_ASSERTION(aChannel && NS_SUCCEEDED(rv), "mailnews mime converter has to have the channel passed in..."); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIURI> aUri; + aChannel->GetURI(getter_AddRefs(aUri)); + return Init(aUri, aListener, aChannel); +} + +NS_IMETHODIMP nsStreamConverter::FirePendingStartRequest() +{ + if (mPendingRequest && mOutListener) + { + mOutListener->OnStartRequest(mPendingRequest, mPendingContext); + mPendingRequest = nullptr; + mPendingContext = nullptr; + } + return NS_OK; +} diff --git a/mailnews/mime/src/nsStreamConverter.h b/mailnews/mime/src/nsStreamConverter.h new file mode 100644 index 000000000..0bd11d1d9 --- /dev/null +++ b/mailnews/mime/src/nsStreamConverter.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsStreamConverter_h_ +#define nsStreamConverter_h_ + +#include "nsIStreamConverter.h" +#include "nsIMimeStreamConverter.h" +#include "nsIMimeEmitter.h" +#include "nsIURI.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIChannel.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" + +#define MIME_FORWARD_HTML_PREFIX "<HTML><BODY><BR><BR>" + +class nsStreamConverter : public nsIStreamConverter, public nsIMimeStreamConverter { +public: + nsStreamConverter(); + + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIMimeStreamConverter support + NS_DECL_NSIMIMESTREAMCONVERTER + // nsIStreamConverter methods + NS_DECL_NSISTREAMCONVERTER + // nsIStreamListener methods + NS_DECL_NSISTREAMLISTENER + + // nsIRequestObserver methods + NS_DECL_NSIREQUESTOBSERVER + + //////////////////////////////////////////////////////////////////////////// + // nsStreamConverter specific methods: + //////////////////////////////////////////////////////////////////////////// + NS_IMETHOD Init(nsIURI *aURI, nsIStreamListener * aOutListener, nsIChannel *aChannel); + NS_IMETHOD GetContentType(char **aOutputContentType); + NS_IMETHOD InternalCleanup(void); + NS_IMETHOD DetermineOutputFormat(const char *url, nsMimeOutputType *newType); + NS_IMETHOD FirePendingStartRequest(void); + +private: + virtual ~nsStreamConverter(); + nsresult Close(); + + // the input and output streams form a pipe...they need to be passed around together.. + nsCOMPtr<nsIAsyncOutputStream> mOutputStream; // output stream + nsCOMPtr<nsIAsyncInputStream> mInputStream; + + nsCOMPtr<nsIStreamListener> mOutListener; // output stream listener + nsCOMPtr<nsIChannel> mOutgoingChannel; + + nsCOMPtr<nsIMimeEmitter> mEmitter; // emitter being used... + nsCOMPtr<nsIURI> mURI; // URI being processed + nsMimeOutputType mOutputType; // the output type we should use for the operation + bool mAlreadyKnowOutputType; + + void *mBridgeStream; // internal libmime data stream + + // Type of output, entire message, header only, body only + nsCString mOutputFormat; + nsCString mRealContentType; // if we know the content type for real, this will be set (used by attachments) + + nsCString mOverrideFormat; // this is a possible override for emitter creation + bool mWrapperOutput; // Should we output the frame split message display + + nsCOMPtr<nsIMimeStreamConverterListener> mMimeStreamConverterListener; + bool mForwardInline; + bool mForwardInlineFilter; + bool mOverrideComposeFormat; + nsString mForwardToAddress; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCString mOriginalMsgURI; + nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr; + + nsCString mFromType; + nsCString mToType; +#ifdef DEBUG_mscott + PRTime mConvertContentTime; +#endif + nsIRequest * mPendingRequest; // used when we need to delay to fire onStartRequest + nsISupports * mPendingContext; // used when we need to delay to fire onStartRequest +}; + +#endif /* nsStreamConverter_h_ */ |