diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/mime/src | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
95 files changed, 25416 insertions, 0 deletions
diff --git a/comm/mailnews/mime/src/MimeHeaderParser.cpp b/comm/mailnews/mime/src/MimeHeaderParser.cpp new file mode 100644 index 0000000000..cc489e2ec5 --- /dev/null +++ b/comm/mailnews/mime/src/MimeHeaderParser.cpp @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 2; 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/DebugOnly.h" +#include "nsMemory.h" +#include "nsCOMPtr.h" +#include "nsIMsgHeaderParser.h" +#include "mozilla/Components.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( + components::HeaderParser::Service()); + + nsCOMPtr<msgIAddressObject> address; + headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(address)); + nsTArray<RefPtr<msgIAddressObject>> addresses; + addresses.AppendElement(address); + headerParser->MakeMimeHeader(addresses, full); +} + +void MakeDisplayAddress(const nsAString& aName, const nsAString& aEmail, + nsAString& full) { + nsCOMPtr<nsIMsgHeaderParser> headerParser( + components::HeaderParser::Service()); + + 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( + components::HeaderParser::Service()); + + 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( + components::HeaderParser::Service()); + NS_ENSURE_TRUE(headerParser, retval); + nsTArray<RefPtr<msgIAddressObject>> addresses; + nsresult rv = headerParser->ParseDecodedHeader(aHeader, false, addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Javascript jsmime returned an error!"); + if (NS_SUCCEEDED(rv) && addresses.Length() > 0) { + retval.SetCapacity(addresses.Length()); + for (auto& addr : addresses) { + retval.AppendElement(addr); + } + } + return retval; +} + +nsCOMArray<msgIAddressObject> EncodedHeader(const nsACString& aHeader, + const char* aCharset) { + nsCOMArray<msgIAddressObject> retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr<nsIMsgHeaderParser> headerParser( + components::HeaderParser::Service()); + NS_ENSURE_TRUE(headerParser, retval); + nsTArray<RefPtr<msgIAddressObject>> addresses; + nsresult rv = + headerParser->ParseEncodedHeader(aHeader, aCharset, false, addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!"); + if (NS_SUCCEEDED(rv) && addresses.Length() > 0) { + retval.SetCapacity(addresses.Length()); + for (auto& addr : addresses) { + retval.AppendElement(addr); + } + } + return retval; +} + +nsCOMArray<msgIAddressObject> EncodedHeaderW(const nsAString& aHeader) { + nsCOMArray<msgIAddressObject> retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr<nsIMsgHeaderParser> headerParser( + components::HeaderParser::Service()); + NS_ENSURE_TRUE(headerParser, retval); + nsTArray<RefPtr<msgIAddressObject>> addresses; + nsresult rv = headerParser->ParseEncodedHeaderW(aHeader, addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!"); + if (NS_SUCCEEDED(rv) && addresses.Length() > 0) { + retval.SetCapacity(addresses.Length()); + for (auto& addr : addresses) { + retval.AppendElement(addr); + } + } + 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/comm/mailnews/mime/src/MimeJSComponents.jsm b/comm/mailnews/mime/src/MimeJSComponents.jsm new file mode 100644 index 0000000000..35acba7c26 --- /dev/null +++ b/comm/mailnews/mime/src/MimeJSComponents.jsm @@ -0,0 +1,547 @@ +/* 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/. */ + +var EXPORTED_SYMBOLS = [ + "MimeHeaders", + "MimeWritableStructuredHeaders", + "MimeAddressParser", + "MimeConverter", +]; + +var { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm"); +var { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.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: ChromeUtils.generateQI(["nsIUTF8StringEnumerator"]), + [Symbol.iterator]() { + return this._iterator; + }, + _setNext() { + if (this._next !== undefined) { + return; + } + this._next = this._iterator.next(); + }, + hasMore() { + this._setNext(); + return !this._next.done; + }, + getNext() { + this._setNext(); + let result = this._next; + this._next = undefined; + if (result.done) { + throw Components.Exception("", Cr.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 + ); + } + 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(aHeaderName) { + let name = aHeaderName.toLowerCase(); + return this._headers.get(name); + }, + + hasHeader(aHeaderName) { + return this._headers.has(aHeaderName.toLowerCase()); + }, + + getUnstructuredHeader(aHeaderName) { + let result = this.getHeader(aHeaderName); + if (result === undefined || typeof result == "string") { + return result; + } + throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE); + }, + + getAddressingHeader(aHeaderName, aPreserveGroups) { + let addrs = this.getHeader(aHeaderName); + if (addrs === undefined) { + addrs = []; + } else if (!Array.isArray(addrs)) { + throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE); + } + return fixArray(addrs, aPreserveGroups); + }, + + getRawHeader(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(sanitizeDate) { + if (this._headers.size == 0) { + return ""; + } + let handler = new HeaderHandler(); + let emitter = jsmime.headeremitter.makeStreamingEmitter(handler, { + useASCII: true, + sanitizeDate, + }); + 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", + QueryInterface: ChromeUtils.generateQI([ + "nsIMimeHeaders", + "msgIStructuredHeaders", + ]), + + initialize(allHeaders) { + this._headers = MimeParser.extractHeaders(allHeaders); + }, + + extractHeader(header, getAll) { + if (!this._headers) { + throw Components.Exception("", Cr.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"); + } + return values[0]; + }, + + get allHeaders() { + return this._headers.rawHeaderText; + }, +}; + +function MimeWritableStructuredHeaders() { + this._headers = new Map(); +} +MimeWritableStructuredHeaders.prototype = { + __proto__: MimeStructuredHeaders.prototype, + QueryInterface: ChromeUtils.generateQI([ + "msgIStructuredHeaders", + "msgIWritableStructuredHeaders", + ]), + + setHeader(aHeaderName, aValue) { + this._headers.set(aHeaderName.toLowerCase(), aValue); + }, + + deleteHeader(aHeaderName) { + this._headers.delete(aHeaderName.toLowerCase()); + }, + + addAllHeaders(aHeaders) { + for (let header of aHeaders.headerNames) { + this.setHeader(header, aHeaders.getHeader(header)); + } + }, + + setUnstructuredHeader(aHeaderName, aValue) { + this.setHeader(aHeaderName, aValue); + }, + + setAddressingHeader(aHeaderName, aAddresses) { + this.setHeader(aHeaderName, fixXpconnectAddresses(aAddresses)); + }, + + setRawHeader(aHeaderName, aValue) { + 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.trim()); + } + }, +}; + +// These are prototypes for nsIMsgHeaderParser implementation +var Mailbox = { + toString() { + return this.name ? this.name + " <" + this.email + ">" : this.email; + }, +}; + +var EmailGroup = { + toString() { + 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") { + // eslint-disable-next-line no-control-regex + 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 = { + QueryInterface: ChromeUtils.generateQI(["nsIMsgHeaderParser"]), + + parseEncodedHeader(aHeader, aCharset, aPreserveGroups) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField( + aHeader, + MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_ALL_I18N, + aCharset + ); + return fixArray(value, aPreserveGroups); + }, + parseEncodedHeaderW(aHeader) { + 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); + }, + parseDecodedHeader(aHeader, aPreserveGroups) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, MimeParser.HEADER_ADDRESS); + return fixArray(value, aPreserveGroups); + }, + + makeMimeHeader(addresses) { + 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(aHeader) { + let addresses = this.parseDecodedHeader(aHeader, false); + return addresses.length > 0 ? addresses[0].name || addresses[0].email : ""; + }, + + removeDuplicateAddresses(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(aName, aEmail) { + let object = Object.create(Mailbox); + object.name = aName; + object.email = aEmail ? aEmail.trim() : aEmail; + return object; + }, + + makeGroupObject(aName, aMembers) { + let object = Object.create(EmailGroup); + object.name = aName; + object.group = aMembers; + return object; + }, + + makeFromDisplayAddress(aDisplay) { + if (aDisplay.includes(";") && !/:.*;/.test(aDisplay)) { + // Using semicolons as mailbox separators in against the standard, but + // used in the wild by some clients. + // Looks like this isn't using group syntax, so let's assume it's a + // non-standards compliant input string, and fix it. + // Replace semicolons with commas, unless the semicolon is inside a quote. + // The regexp uses tricky lookahead, see bug 1059988 comment #70 for details. + aDisplay = aDisplay.replace(/;(?=(?:(?:[^"]*"){2})*[^"]*$)/g, ","); + } + + // The basic idea is to split on every comma, so long as there is a + // preceding @ or <> pair. + let output = []; + while (aDisplay.length > 0) { + let lt = aDisplay.indexOf("<"); + let gt = aDisplay.indexOf(">"); + let at = aDisplay.indexOf("@"); + let start = 0; + // An address doesn't always contain both <> and @, the goal is to find + // the first comma after <> or @. + if (lt != -1 && gt > lt) { + start = gt; + } + if (at != -1) { + start = Math.min(start, at); + } + let comma = aDisplay.indexOf(",", start); + let addr; + if (comma > 0) { + addr = aDisplay.substr(0, comma); + aDisplay = aDisplay.substr(comma + 1); + + // Make sure we don't have any "empty addresses" (multiple commas). + comma = 0; + while (/[,\s]/.test(aDisplay.charAt(comma))) { + comma++; + } + aDisplay = aDisplay.substr(comma); + } else { + addr = aDisplay; + aDisplay = ""; + } + addr = addr.trimLeft(); + if (addr) { + output.push(this._makeSingleAddress(addr)); + } + } + return output; + }, + + /** + * Construct a single email address from an |name <local@domain>| token. + * + * @param {string} aInput - a string to be parsed to a mailbox object. + * @returns {msgIAddressObject} the mailbox parsed from the input. + */ + _makeSingleAddress(aInput) { + // If the whole string is within quotes, unquote it first. + aInput = aInput.trim().replace(/^"(.*)"$/, "$1"); + + if (/<.*>/.test(aInput)) { + // We don't want to look for the address within quotes, so first remove + // all quoted strings containing angle chars. + let cleanedInput = aInput.replace(/".*[<>]+.*"/g, ""); + + // Extract the address from within the quotes. + let addrMatch = cleanedInput.match(/<([^><]*)>/); + + let addr = addrMatch ? addrMatch[1] : ""; + let addrIdx = aInput.indexOf("<" + addr + ">"); + return this.makeMailboxObject(aInput.slice(0, addrIdx).trim(), addr); + } + return this.makeMailboxObject("", aInput); + }, + + extractHeaderAddressMailboxes(aLine) { + return this.parseDecodedHeader(aLine) + .map(addr => addr.email) + .join(", "); + }, + + makeMimeAddress(aName, aEmail) { + let object = this.makeMailboxObject(aName, aEmail); + return this.makeMimeHeader([object]); + }, +}; + +function MimeConverter() {} +MimeConverter.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIMimeConverter"]), + + encodeMimePartIIStr_UTF8(aHeader, aStructured, aFieldNameLen, aLineLength) { + // 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(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(...aArgs) { + return this.decodeMimeHeader(...aArgs); + }, +}; diff --git a/comm/mailnews/mime/src/comi18n.cpp b/comm/mailnews/mime/src/comi18n.cpp new file mode 100644 index 0000000000..90bfd17abf --- /dev/null +++ b/comm/mailnews/mime/src/comi18n.cpp @@ -0,0 +1,49 @@ +/* -*- 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 "comi18n.h" +#include "nsMsgUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIMimeConverter.h" +#include "mozilla/Preferences.h" +#include "mozilla/Encoding.h" +#include "mozilla/EncodingDetector.h" + +using namespace mozilla; + +//////////////////////////////////////////////////////////////////////////////// +// 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("@mozilla.org/messenger/mimeconverter;1", &rv); + if (NS_FAILED(rv)) { + result.Truncate(); + return; + } + mimeConverter->DecodeMimeHeaderToUTF8(nsDependentCString(header), + default_charset, override_charset, + eatContinuations, result); +} + +nsresult MIME_detect_charset(const char* aBuf, int32_t aLength, + nsACString& aCharset) { + mozilla::UniquePtr<mozilla::EncodingDetector> detector = + mozilla::EncodingDetector::Create(); + mozilla::Span<const uint8_t> src = + mozilla::AsBytes(mozilla::Span(aBuf, aLength)); + Unused << detector->Feed(src, true); + auto encoding = detector->Guess(nullptr, true); + encoding->Name(aCharset); + return NS_OK; +} + +} /* end of extern "C" */ +// END PUBLIC INTERFACE diff --git a/comm/mailnews/mime/src/comi18n.h b/comm/mailnews/mime/src/comi18n.h new file mode 100644 index 0000000000..0c9f7cbaf7 --- /dev/null +++ b/comm/mailnews/mime/src/comi18n.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/. */ +#ifndef _COMI18N_LOADED_H_ +#define _COMI18N_LOADED_H_ + +#include "msgCore.h" +#include "mozilla/Encoding.h" + +#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, + nsACString& aCharset); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif // _COMI18N_LOADED_H_ diff --git a/comm/mailnews/mime/src/components.conf b/comm/mailnews/mime/src/components.conf new file mode 100644 index 0000000000..cfd19671ca --- /dev/null +++ b/comm/mailnews/mime/src/components.conf @@ -0,0 +1,87 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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/. + +Classes = [ + { + "cid": "{d1258011-f391-44fd-992e-c6f4b461a42f}", + "contract_ids": ["@mozilla.org/messenger/mimeheaders;1"], + "jsm": "resource:///modules/MimeJSComponents.jsm", + "constructor": "MimeHeaders", + }, + { + "cid": "{c560806a-425f-4f0f-bf69-397c58c599a7}", + "contract_ids": ["@mozilla.org/messenger/structuredheaders;1"], + "jsm": "resource:///modules/MimeJSComponents.jsm", + "constructor": "MimeWritableStructuredHeaders", + }, + { + "cid": "{96bd8769-2d0e-4440-963d-22b97fb3ba77}", + "contract_ids": ["@mozilla.org/messenger/headerparser;1"], + "jsm": "resource:///modules/MimeJSComponents.jsm", + "constructor": "MimeAddressParser", + "name": "HeaderParser", + "interfaces": ["nsIMsgHeaderParser"], + }, + { + "cid": "{93f8c049-80ed-4dda-9000-94ad8daba44c}", + "contract_ids": ["@mozilla.org/messenger/mimeconverter;1"], + "jsm": "resource:///modules/MimeJSComponents.jsm", + "constructor": "MimeConverter", + "name": "MimeConverter", + "interfaces": ["nsIMimeConverter"], + }, + { + "cid": "{403b0540-b7c3-11d2-b35e-525400e2d63a}", + "contract_ids": ["@mozilla.org/messenger/mimeobject;1"], + "type": "nsMimeObjectClassAccess", + "headers": ["/comm/mailnews/mime/src/nsMimeObjectClassAccess.h"], + }, + { + "cid": "{faf4f9a6-60ad-11d3-989a-001083010e9b}", + "contract_ids": [ + "@mozilla.org/streamconv;1?from=message/rfc822&to=application/xhtml+xml", + "@mozilla.org/streamconv;1?from=message/rfc822&to=text/html", + "@mozilla.org/streamconv;1?from=message/rfc822&to=*/*", + ], + "type": "nsStreamConverter", + "headers": ["/comm/mailnews/mime/src/nsStreamConverter.h"], + }, + { + "cid": "{f0a8af16-dcce-11d2-a411-00805f613c79}", + "contract_ids": ["@mozilla.org/messenger/mimeemitter;1?type=text/html"], + "type": "nsMimeHtmlDisplayEmitter", + "init_method": "Init", + "headers": ["/comm/mailnews/mime/emitters/nsMimeHtmlEmitter.h"], + "categories": { + "mime-emitter": "@mozilla.org/messenger/mimeemitter;1?type=text/html" + }, + }, + { + "cid": "{977e418f-e392-11d2-a2ac-00a024a7d144}", + "contract_ids": ["@mozilla.org/messenger/mimeemitter;1?type=text/xml"], + "type": "nsMimeXmlEmitter", + "headers": ["/comm/mailnews/mime/emitters/nsMimeXmlEmitter.h"], + "categories": { + "mime-emitter": "@mozilla.org/messenger/mimeemitter;1?type=text/xml" + }, + }, + { + "cid": "{e8892265-7653-46c5-a290-307f3404d0f3}", + "contract_ids": ["@mozilla.org/messenger/mimeemitter;1?type=text/plain"], + "type": "nsMimePlainEmitter", + "headers": ["/comm/mailnews/mime/emitters/nsMimePlainEmitter.h"], + "categories": { + "mime-emitter": "@mozilla.org/messenger/mimeemitter;1?type=text/plain" + }, + }, + { + "cid": "{f0a8af16-dcff-11d2-a411-00805f613c79}", + "contract_ids": ["@mozilla.org/messenger/mimeemitter;1?type=raw"], + "type": "nsMimeRawEmitter", + "headers": ["/comm/mailnews/mime/emitters/nsMimeRawEmitter.h"], + "categories": {"mime-emitter": "@mozilla.org/messenger/mimeemitter;1?type=raw"}, + }, +] diff --git a/comm/mailnews/mime/src/extraMimeParsers.jsm b/comm/mailnews/mime/src/extraMimeParsers.jsm new file mode 100644 index 0000000000..b20bd543a1 --- /dev/null +++ b/comm/mailnews/mime/src/extraMimeParsers.jsm @@ -0,0 +1,31 @@ +/* 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/. */ + +/* globals jsmime */ + +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/comm/mailnews/mime/src/jsmime.jsm b/comm/mailnews/mime/src/jsmime.jsm new file mode 100644 index 0000000000..407c2aacd1 --- /dev/null +++ b/comm/mailnews/mime/src/jsmime.jsm @@ -0,0 +1,72 @@ +/* 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: + +/** + * 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"]; + +function bytesToString(buffer) { + var string = ""; + for (var i = 0; i < buffer.length; i++) { + string += String.fromCharCode(buffer[i]); + } + return string; +} + +// Our UTF-7 decoder. +function UTF7TextDecoder(options = {}, manager) { + this.manager = manager; + this.collectInput = ""; +} +UTF7TextDecoder.prototype = { + // Since the constructor checked, this will only be called for UTF-7. + decode(input, options = {}) { + let more = "stream" in options ? options.stream : false; + // There are cases where this is called without input. + if (!input) { + return ""; + } + this.collectInput += bytesToString(input); + if (more) { + return ""; + } + return this.manager.utf7ToUnicode(this.collectInput); + }, +}; + +/* exported MimeTextDecoder */ +function MimeTextDecoder(charset, options) { + let manager = Cc["@mozilla.org/charset-converter-manager;1"].createInstance( + Ci.nsICharsetConverterManager + ); + // The following will throw if the charset is unknown. + let newCharset = manager.getCharsetAlias(charset); + if (newCharset.toLowerCase() == "utf-7") { + return new UTF7TextDecoder(options, manager); + } + return new TextDecoder(newCharset, options); +} + +// The following code loads custom MIME encoders. +var CATEGORY_NAME = "custom-mime-encoder"; +Services.obs.addObserver(function (subject, topic, data) { + subject = subject.QueryInterface(Ci.nsISupportsCString).data; + if (data == CATEGORY_NAME) { + let url = Services.catMan.getCategoryEntry(CATEGORY_NAME, subject); + Services.scriptloader.loadSubScript(url, {}, "UTF-8"); + } +}, "xpcom-category-entry-added"); + +for (let { data } of Services.catMan.enumerateCategory(CATEGORY_NAME)) { + let url = Services.catMan.getCategoryEntry(CATEGORY_NAME, data); + Services.scriptloader.loadSubScript(url, {}, "UTF-8"); +} diff --git a/comm/mailnews/mime/src/mime.def b/comm/mailnews/mime/src/mime.def new file mode 100644 index 0000000000..6b1c36bf9e --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimeParser.jsm b/comm/mailnews/mime/src/mimeParser.jsm new file mode 100644 index 0000000000..95256ba41c --- /dev/null +++ b/comm/mailnews/mime/src/mimeParser.jsm @@ -0,0 +1,546 @@ +/* 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: + +var EXPORTED_SYMBOLS = ["MimeParser"]; + +var { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm"); +var { MailStringUtils } = ChromeUtils.import( + "resource:///modules/MailStringUtils.jsm" +); + +// Emitter helpers, for internal functions later on. +var ExtractMimeMsgEmitter = { + getAttachmentName(part) { + if (!part || !part.hasOwnProperty("headers")) { + return ""; + } + + if (part.headers.hasOwnProperty("content-disposition")) { + let filename = MimeParser.getParameter( + part.headers["content-disposition"][0], + "filename" + ); + if (filename) { + return filename; + } + } + + if (part.headers.hasOwnProperty("content-type")) { + let name = MimeParser.getParameter( + part.headers["content-type"][0], + "name" + ); + if (name) { + return name; + } + } + + return ""; + }, + + // All parts of content-disposition = "attachment" are returned as attachments. + // For content-disposition = "inline", all parts except those with content-type + // text/plain, text/html and text/enriched are returned as attachments. + isAttachment(part) { + if (!part) { + return false; + } + + let contentType = part.contentType || "text/plain"; + if (contentType.search(/^multipart\//i) === 0) { + return false; + } + + let contentDisposition = ""; + if ( + Array.isArray(part.headers["content-disposition"]) && + part.headers["content-disposition"].length > 0 + ) { + contentDisposition = part.headers["content-disposition"][0]; + } + + if ( + contentDisposition.search(/^attachment/i) === 0 || + contentType.search(/^text\/plain|^text\/html|^text\/enriched/i) === -1 + ) { + return true; + } + + return false; + }, + + /** JSMime API */ + startMessage() { + this.mimeTree = { + partName: "", + contentType: "message/rfc822", + parts: [], + size: 0, + headers: {}, + attachments: [], + // No support for encryption. + isEncrypted: false, + }; + // partsPath is a hierarchical stack of parts from the root to the + // current part. + this.partsPath = [this.mimeTree]; + this.options = this.options || {}; + }, + + endMessage() { + // Prepare the mimeMsg object, which is the final output of the emitter. + this.mimeMsg = null; + if (this.mimeTree.parts.length == 0) { + return; + } + + // Check if only a specific mime part has been requested. + if (this.options.getMimePart) { + if (this.mimeTree.parts[0].partName == this.options.getMimePart) { + this.mimeMsg = this.mimeTree.parts[0]; + } + return; + } + + this.mimeTree.attachments.sort((a, b) => a.partName > b.partName); + this.mimeMsg = this.mimeTree; + }, + + startPart(partNum, headerMap) { + let contentType = headerMap.contentType?.type + ? headerMap.contentType.type + : "text/plain"; + + let headers = {}; + for (let [headerName, headerValue] of headerMap._rawHeaders) { + // MsgHdrToMimeMessage always returns an array, even for single values. + let valueArray = Array.isArray(headerValue) ? headerValue : [headerValue]; + // Return a binary string, to mimic MsgHdrToMimeMessage. + headers[headerName] = valueArray.map(value => { + return MailStringUtils.stringToByteString(value); + }); + } + + // Get the most recent part from the hierarchical parts stack, which is the + // parent of the new part to by added. + let parentPart = this.partsPath[this.partsPath.length - 1]; + + // Add a leading 1 to the partNum and convert the "$" sub-message deliminator. + let partName = "1" + (partNum ? "." : "") + partNum.replaceAll("$", ".1"); + + // MsgHdrToMimeMessage differentiates between the message headers and the + // headers of the first part. jsmime.js however returns all headers of + // the message in the first multipart/* part: Merge all headers into the + // parent part and only keep content-* headers. + if (parentPart.contentType.startsWith("message/")) { + for (let [k, v] of Object.entries(headers)) { + if (!parentPart.headers[k]) { + parentPart.headers[k] = v; + } + } + headers = Object.fromEntries( + Object.entries(headers).filter(h => h[0].startsWith("content-")) + ); + } + + // Add default content-type header. + if (!headers.hasOwnProperty("content-type")) { + headers["content-type"] = ["text/plain"]; + } + + let newPart = { + partName, + body: "", + headers, + contentType, + size: 0, + parts: [], + // No support for encryption. + isEncrypted: false, + }; + + // Add nested new part. + parentPart.parts.push(newPart); + // Push the newly added part into the hierarchical parts stack. + this.partsPath.push(newPart); + }, + + endPart(partNum) { + let deleteBody = false; + // Get the most recent part from the hierarchical parts stack. + let currentPart = this.partsPath[this.partsPath.length - 1]; + + // Add size. + let size = currentPart.body.length; + currentPart.size += size; + let partSize = currentPart.size; + + if (this.isAttachment(currentPart)) { + currentPart.name = this.getAttachmentName(currentPart); + this.mimeTree.attachments.push({ ...currentPart }); + deleteBody = !this.options.getMimePart; + } + + if (deleteBody || currentPart.body == "") { + delete currentPart.body; + } + + // Remove content-disposition and content-transfer-encoding headers. + currentPart.headers = Object.fromEntries( + Object.entries(currentPart.headers).filter( + h => + !["content-disposition", "content-transfer-encoding"].includes(h[0]) + ) + ); + + // Set the parent of this part to be the new current part. + this.partsPath.pop(); + + // Add the size of this part to its parent as well. + currentPart = this.partsPath[this.partsPath.length - 1]; + currentPart.size += partSize; + }, + + /** + * The data parameter is either a string or a Uint8Array. + */ + deliverPartData(partNum, data) { + // Get the most recent part from the hierarchical parts stack. + let currentPart = this.partsPath[this.partsPath.length - 1]; + + if (typeof data === "string") { + currentPart.body += data; + } else { + currentPart.body += MailStringUtils.uint8ArrayToByteString(data); + } + }, +}; + +var ExtractHeadersEmitter = { + startPart(partNum, headers) { + if (partNum == "") { + this.headers = headers; + } + }, +}; + +var ExtractHeadersAndBodyEmitter = { + body: "", + startPart: ExtractHeadersEmitter.startPart, + deliverPartData(partNum, data) { + if (partNum == "") { + this.body += data; + } + }, +}; + +// Sets appropriate default options for chrome-privileged environments +function setDefaultParserOptions(opts) { + if (!("onerror" in opts)) { + opts.onerror = Cu.reportError; + } +} + +var MimeParser = { + /*** + * Determine an arbitrary "parameter" part of a mail header. + * + * @param {string} headerStr - The string containing all parts of the header. + * @param {string} parameter - The parameter we are looking for. + * + * + * 'multipart/signed; protocol="xyz"', 'protocol' --> returns "xyz" + * + * @return {string} String containing the value of the parameter; or "". + */ + + getParameter(headerStr, parameter) { + parameter = parameter.toLowerCase(); + headerStr = headerStr.replace(/[\r\n]+[ \t]+/g, ""); + + let hdrMap = jsmime.headerparser.parseParameterHeader( + ";" + headerStr, + true, + true + ); + + for (let [key, value] of hdrMap.entries()) { + if (parameter == key.toLowerCase()) { + return value; + } + } + + return ""; + }, + + /** + * 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(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, 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); + }, + + /** + * 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(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(); + }, + + /** + * 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(emitter, opts) { + var StreamListener = { + onStartRequest(aRequest) { + try { + if ("onStartRequest" in emitter) { + emitter.onStartRequest(aRequest); + } + } finally { + this._parser.resetParser(); + } + }, + onStopRequest(aRequest, aStatus) { + this._parser.deliverEOF(); + if ("onStopRequest" in emitter) { + emitter.onStopRequest(aRequest, aStatus); + } + }, + onDataAvailable(aRequest, 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: ChromeUtils.generateQI([ + "nsIStreamListener", + "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(emitter, opts) { + setDefaultParserOptions(opts); + return new jsmime.MimeParser(emitter, opts); + }, + + /** + * Returns a mimeMsg object for the given input. The returned object tries to + * be compatible with the return value of MsgHdrToMimeMessage. Differences: + * - no support for encryption + * - returned attachments include the body and not the URL + * - returned attachments match either allInlineAttachments or + * allUserAttachments (decodeSubMessages = false) + * - does not eat TABs in headers, if they follow a CRLF + * + * The input is any type of input that would be accepted by parseSync. + * + * @param input A string of text to parse. + */ + extractMimeMsg(input, options) { + var emitter = Object.create(ExtractMimeMsgEmitter); + // Set default options. + emitter.options = { + getMimePart: "", + decodeSubMessages: true, + }; + // Override default options. + for (let option of Object.keys(options)) { + emitter.options[option] = options[option]; + } + + MimeParser.parseSync(input, emitter, { + // jsmime does not use the "1." prefix for the partName. + // jsmime uses "$." as sub-message deliminator. + pruneat: emitter.options.getMimePart + .split(".") + .slice(1) + .join(".") + .replaceAll(".1.", "$."), + decodeSubMessages: emitter.options.decodeSubMessages, + bodyformat: "decode", + stripcontinuations: true, + strformat: "unicode", + }); + return emitter.mimeMsg; + }, + + /** + * 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(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(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(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 new Error("Illegal type of header field"); + } + }, +}; diff --git a/comm/mailnews/mime/src/mimeTextHTMLParsed.cpp b/comm/mailnews/mime/src/mimeTextHTMLParsed.cpp new file mode 100644 index 0000000000..225b63f1ba --- /dev/null +++ b/comm/mailnews/mime/src/mimeTextHTMLParsed.cpp @@ -0,0 +1,171 @@ +/* -*- 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/. */ + +/* 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 "nsContentUtils.h" +#include "mozilla/dom/DOMParser.h" +#include "mozilla/dom/Document.h" +#include "nsGenericHTMLElement.h" +#include "mozilla/Preferences.h" +#include "nsIParserUtils.h" +#include "nsIDocumentEncoder.h" +#include "mozilla/ErrorResult.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. + mozilla::ErrorResult rv2; + RefPtr<mozilla::dom::DOMParser> parser = + mozilla::dom::DOMParser::CreateWithoutGlobal(rv2); + nsCOMPtr<mozilla::dom::Document> document = parser->ParseFromString( + rawHTML, mozilla::dom::SupportedType::Text_html, rv2); + if (rv2.Failed()) return -1; + + // Remove meta http-equiv="refresh". + RefPtr<nsContentList> metas = document->GetElementsByTagName(u"meta"_ns); + uint32_t length = metas->Length(true); + for (uint32_t i = length; i > 0; i--) { + RefPtr<nsGenericHTMLElement> node = + nsGenericHTMLElement::FromNodeOrNull(metas->Item(i - 1)); + nsAutoString header; + node->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header); + nsContentUtils::ASCIIToLower(header); + if (nsGkAtoms::refresh->Equals(header)) { + node->Remove(); + } + } + + // Serialize it back to HTML source again. + nsCOMPtr<nsIDocumentEncoder> encoder = do_createDocumentEncoder("text/html"); + NS_ENSURE_TRUE(encoder, -1); + uint32_t aFlags = nsIDocumentEncoder::OutputRaw | + nsIDocumentEncoder::OutputDisallowLineBreaking; + rv = encoder->Init(document, u"text/html"_ns, aFlags); + NS_ENSURE_SUCCESS(rv, -1); + rv = encoder->EncodeToString(parsed); + NS_ENSURE_SUCCESS(rv, -1); + + bool stripConditionalCSS = mozilla::Preferences::GetBool( + "mail.html_sanitize.drop_conditional_css", true); + + nsCString resultCStr; + if (stripConditionalCSS) { + nsString cssCondStripped; + nsCOMPtr<nsIParserUtils> parserUtils = + do_GetService(NS_PARSERUTILS_CONTRACTID); + parserUtils->RemoveConditionalCSS(parsed, cssCondStripped); + parsed.Truncate(); + resultCStr = NS_ConvertUTF16toUTF8(cssCondStripped); + } else { + resultCStr = NS_ConvertUTF16toUTF8(parsed); + } + + // Write it out. + + // XXX: adding the doc source resultCStr to what we have here is not nice: + // We already have the stuff up to and including <body> written. + // So we are dumping <head> content into <body>. Tagsoup ohoy! + + 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/comm/mailnews/mime/src/mimeTextHTMLParsed.h b/comm/mailnews/mime/src/mimeTextHTMLParsed.h new file mode 100644 index 0000000000..99a84d0dd8 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimebuf.cpp b/comm/mailnews/mime/src/mimebuf.cpp new file mode 100644 index 0000000000..d1db4d67d5 --- /dev/null +++ b/comm/mailnews/mime/src/mimebuf.cpp @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made + * by IBM described herein are Copyright (c) International Business Machines + * Corporation, 2000. Modifications to Mozilla code or documentation identified + * per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +/* + * 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 && (*buffer_fpP < (uint32_t)*buffer_sizeP) && + (*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. */ + 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/comm/mailnews/mime/src/mimebuf.h b/comm/mailnews/mime/src/mimebuf.h new file mode 100644 index 0000000000..3cd52fac2c --- /dev/null +++ b/comm/mailnews/mime/src/mimebuf.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/. + * 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/comm/mailnews/mime/src/mimecms.cpp b/comm/mailnews/mime/src/mimecms.cpp new file mode 100644 index 0000000000..e9d2d33ae5 --- /dev/null +++ b/comm/mailnews/mime/src/mimecms.cpp @@ -0,0 +1,772 @@ +/* -*- 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 "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 "nsIMsgSMIMEHeaderSink.h" +#include "nsCOMPtr.h" +#include "nsIX509Cert.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "nsIMailChannel.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, + const char* aMsgDate, + nsIMsgSMIMEHeaderSink* aHeaderSink, + int32_t aMimeNestingLevel, + const nsCString& aMsgNeckoURL, + const nsCString& aOriginMimePartNumber); + + 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 mOriginMimePartNumber; + + nsCString mFromAddr; + nsCString mFromName; + nsCString mSenderAddr; + nsCString mSenderName; + nsCString mMsgDate; +}; + +class SignedStatusRunnable : public mozilla::Runnable { + public: + SignedStatusRunnable( + const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink>& aSink, + int32_t aNestingLevel, int32_t aSignatureStatus, nsIX509Cert* aSignerCert, + const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + protected: + nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> m_sink; + int32_t m_nestingLevel; + int32_t m_signatureStatus; + nsCOMPtr<nsIX509Cert> m_signerCert; + nsCString m_msgNeckoURL; + nsCString m_originMimePartNumber; +}; + +SignedStatusRunnable::SignedStatusRunnable( + const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink>& aSink, + int32_t aNestingLevel, int32_t aSignatureStatus, nsIX509Cert* aSignerCert, + const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber) + : mozilla::Runnable("SignedStatusRunnable"), + mResult(NS_ERROR_UNEXPECTED), + m_sink(aSink), + m_nestingLevel(aNestingLevel), + m_signatureStatus(aSignatureStatus), + m_signerCert(aSignerCert), + m_msgNeckoURL(aMsgNeckoURL), + m_originMimePartNumber(aOriginMimePartNumber) {} + +NS_IMETHODIMP SignedStatusRunnable::Run() { + mResult = + m_sink->SignedStatus(m_nestingLevel, m_signatureStatus, m_signerCert, + m_msgNeckoURL, m_originMimePartNumber); + return NS_OK; +} + +nsresult ProxySignedStatus( + const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink>& aSink, + int32_t aNestingLevel, int32_t aSignatureStatus, nsIX509Cert* aSignerCert, + const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber) { + RefPtr<SignedStatusRunnable> signedStatus = new SignedStatusRunnable( + aSink, aNestingLevel, aSignatureStatus, aSignerCert, aMsgNeckoURL, + aOriginMimePartNumber); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxySignedStatus"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(signedStatus)); + NS_ENSURE_SUCCESS(rv, rv); + return signedStatus->mResult; +} + +NS_IMPL_ISUPPORTS(nsSMimeVerificationListener, nsISMimeVerificationListener) + +nsSMimeVerificationListener::nsSMimeVerificationListener( + const char* aFromAddr, const char* aFromName, const char* aSenderAddr, + const char* aSenderName, const char* aMsgDate, + nsIMsgSMIMEHeaderSink* aHeaderSink, int32_t aMimeNestingLevel, + const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber) + : mMsgNeckoURL(aMsgNeckoURL), mOriginMimePartNumber(aOriginMimePartNumber) { + mHeaderSink = new nsMainThreadPtrHolder<nsIMsgSMIMEHeaderSink>( + "nsSMimeVerificationListener::mHeaderSink", aHeaderSink); + mSinkIsNull = !aHeaderSink; + mMimeNestingLevel = aMimeNestingLevel; + + mFromAddr = aFromAddr; + mFromName = aFromName; + mSenderAddr = aSenderAddr; + mSenderName = aSenderName; + mMsgDate = aMsgDate; +} + +NS_IMETHODIMP nsSMimeVerificationListener::Notify( + nsICMSMessage* 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<nsIX509Cert> signerCert; + aVerifiedMessage->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( + aVerifiedMessage, 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 { + PRTime sigTime; + if (NS_FAILED(aVerifiedMessage->GetSigningTime(&sigTime))) { + // Signing time attribute is optional in CMS messages. + signature_status = nsICMSMessageErrors::SUCCESS; + } else { + // If it's present, check for a rough match with the message date. + PRTime msgTime; + if (PR_ParseTimeString(mMsgDate.get(), false, &msgTime) != PR_SUCCESS) { + signature_status = nsICMSMessageErrors::VERIFY_TIME_MISMATCH; + } else { + PRTime delta; + + if (sigTime > msgTime) { + delta = sigTime - msgTime; + } else { + delta = msgTime - sigTime; + } + + if (delta / PR_USEC_PER_SEC > 60 * 60 * 1) { + signature_status = nsICMSMessageErrors::VERIFY_TIME_MISMATCH; + } else { + signature_status = nsICMSMessageErrors::SUCCESS; + } + } + } + } + } + + if (NS_IsMainThread()) { + mHeaderSink->SignedStatus(mMimeNestingLevel, signature_status, signerCert, + mMsgNeckoURL, mOriginMimePartNumber); + } else { + ProxySignedStatus(mHeaderSink, mMimeNestingLevel, signature_status, + signerCert, mMsgNeckoURL, mOriginMimePartNumber); + } + + 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) { + char* addr = mime_part_address(walker); + if (!strcmp(addr, walker->options->part_to_load)) { + aAlreadyFoundTop = true; + aTopShownObject = walker; + } + PR_FREEIF(addr); + } + 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; + 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")) { + nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(channel); + if (mailChannel) { + mailChannel->GetSmimeHeaderSink( + getter_AddRefs(data->smimeHeaderSink)); + } + } + } + } // 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, nsCString& msg_date) { + 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); + + msg_date.Adopt(MimeHeaders_get(msg_headers, HEADER_DATE, false, true)); +} + +void MimeCMSRequestAsyncSignatureVerification( + nsICMSMessage* aCMSMsg, const char* aFromAddr, const char* aFromName, + const char* aSenderAddr, const char* aSenderName, const char* aMsgDate, + nsIMsgSMIMEHeaderSink* aHeaderSink, int32_t aMimeNestingLevel, + const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber, + const nsTArray<uint8_t>& aDigestData, int16_t aDigestType) { + RefPtr<nsSMimeVerificationListener> listener = + new nsSMimeVerificationListener( + aFromAddr, aFromName, aSenderAddr, aSenderName, aMsgDate, aHeaderSink, + aMimeNestingLevel, aMsgNeckoURL, aOriginMimePartNumber); + + long verifyFlags = 0; + if (mozilla::Preferences::GetBool( + "mail.smime.accept_insecure_sha1_message_signatures", false)) { + verifyFlags |= nsICMSVerifyFlags::VERIFY_ALLOW_WEAK_SHA1; + } + + if (aDigestData.IsEmpty()) + aCMSMsg->AsyncVerifySignature(verifyFlags, listener); + else + aCMSMsg->AsyncVerifyDetachedSignature(verifyFlags, listener, aDigestData, + aDigestType); +} + +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; + + // maxWantedNesting 1: only want outermost nesting level + if (aRelativeNestLevel > 1) return 0; + + if (data->decoding_failed) status = nsICMSMessageErrors::GENERAL_ERROR; + + nsAutoCString partnum; + partnum.Adopt(mime_part_address(data->self)); + + 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, partnum); + } + if (data->any_parent_is_encrypted_p) { + data->smimeHeaderSink->EncryptionStatus( + aRelativeNestLevel, nsICMSMessageErrors::GENERAL_ERROR, nullptr, + data->url, partnum); + } + 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; + nsCString msg_date; + + MimeCMSGetFromSender(data->self, from_addr, from_name, sender_addr, + sender_name, msg_date); + + MimeCMSRequestAsyncSignatureVerification( + data->content_info, from_addr.get(), from_name.get(), + sender_addr.get(), sender_name.get(), msg_date.get(), + data->smimeHeaderSink, aRelativeNestLevel, data->url, partnum, {}, 0); + } + } + + if (data->ci_is_encrypted) { + data->smimeHeaderSink->EncryptionStatus(aRelativeNestLevel, status, + certOfInterest, data->url, partnum); + } + + 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/comm/mailnews/mime/src/mimecms.h b/comm/mailnews/mime/src/mimecms.h new file mode 100644 index 0000000000..bb4746dfd7 --- /dev/null +++ b/comm/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; // for function arguments in mimecms.h + +/* 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/comm/mailnews/mime/src/mimecom.cpp b/comm/mailnews/mime/src/mimecom.cpp new file mode 100644 index 0000000000..17438302bb --- /dev/null +++ b/comm/mailnews/mime/src/mimecom.cpp @@ -0,0 +1,53 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimecom.h b/comm/mailnews/mime/src/mimecom.h new file mode 100644 index 0000000000..d170a70829 --- /dev/null +++ b/comm/mailnews/mime/src/mimecom.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/. */ + +/* + * 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/comm/mailnews/mime/src/mimecont.cpp b/comm/mailnews/mime/src/mimecont.cpp new file mode 100644 index 0000000000..8b139e1fbf --- /dev/null +++ b/comm/mailnews/mime/src/mimecont.cpp @@ -0,0 +1,209 @@ +/* -*- 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 "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 we're being destroyed, prior to deleting any data, mark + * flush data in all children and mark them as closed, to avoid + * flushing during subsequent mime_free of the children. + * This also helps if this (parent) object is already marked as + * closed, but a child is not yet marked as closed. + */ + ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(object, true); + if (cont->children) { + int i; + for (i = 0; i < cont->nchildren; i++) { + MimeObject* kid = cont->children[i]; + if (kid && !kid->closed_p) { + kid->clazz->parse_eof(kid, true); + } + } + } + + 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/comm/mailnews/mime/src/mimecont.h b/comm/mailnews/mime/src/mimecont.h new file mode 100644 index 0000000000..71120c8c6e --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimecryp.cpp b/comm/mailnews/mime/src/mimecryp.cpp new file mode 100644 index 0000000000..b985c53fbe --- /dev/null +++ b/comm/mailnews/mime/src/mimecryp.cpp @@ -0,0 +1,507 @@ +/* -*- 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/. */ + +#ifdef MOZ_LOGGING +# include "mozilla/Logging.h" +#endif +#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 "modmimee.h" // for MimeConverterOutputCallback + +using namespace mozilla; + +static mozilla::LazyLogModule gMimeCryptLog("MIMECRYPT"); + +#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 only 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 && 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); +} + +#ifdef MOZ_LOGGING +static int DebugOut(const char* buf, int32_t size, void* closure) { + MOZ_LOG(gMimeCryptLog, LogLevel::Debug, + ("MimeEncrypted_emit_buffered_child: (partial) decrypted body\n%.*s", + size, buf)); + return 0; +} +#endif + +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"); + +#ifdef MOZ_LOGGING + if (enc->hdrs && enc->hdrs->all_headers) { + MOZ_LOG(gMimeCryptLog, LogLevel::Debug, + ("MimeEncrypted_emit_buffered_child: decrypted headers:\n%.*s", + enc->hdrs->all_headers_fp, enc->hdrs->all_headers)); + } + + if (enc->part_buffer) { + status = MimePartBufferRead(enc->part_buffer, DebugOut, 0); + if (status < 0) return status; + } +#endif + + /* 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) { + /* 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/comm/mailnews/mime/src/mimecryp.h b/comm/mailnews/mime/src/mimecryp.h new file mode 100644 index 0000000000..dd8c846350 --- /dev/null +++ b/comm/mailnews/mime/src/mimecryp.h @@ -0,0 +1,139 @@ +/* -*- 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/comm/mailnews/mime/src/mimecth.cpp b/comm/mailnews/mime/src/mimecth.cpp new file mode 100644 index 0000000000..0a091b77fb --- /dev/null +++ b/comm/mailnews/mime/src/mimecth.cpp @@ -0,0 +1,33 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimecth.h b/comm/mailnews/mime/src/mimecth.h new file mode 100644 index 0000000000..ed65163452 --- /dev/null +++ b/comm/mailnews/mime/src/mimecth.h @@ -0,0 +1,131 @@ +/* -*- 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/comm/mailnews/mime/src/mimedrft.cpp b/comm/mailnews/mime/src/mimedrft.cpp new file mode 100644 index 0000000000..07d05cf19b --- /dev/null +++ b/comm/mailnews/mime/src/mimedrft.cpp @@ -0,0 +1,2135 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made + * by IBM described herein are Copyright (c) International Business Machines + * Corporation, 2000. Modifications to Mozilla code or documentation identified + * per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +#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 "nsNetUtil.h" +#include "comi18n.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgCompFields.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 "modmimee.h" // for MimeConverterOutputCallback +#include "mozilla/dom/Promise.h" +#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); + +#define NS_MSGCOMPOSESERVICE_CID \ + { /* 588595FE-1ADA-11d3-A715-0060B0EB39B5 */ \ + 0x588595fe, 0x1ada, 0x11d3, { \ + 0xa7, 0x15, 0x0, 0x60, 0xb0, 0xeb, 0x39, 0xb5 \ + } \ + } +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), + autodetectCharset(false) {} +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// +// 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 nsACString& 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( + "@mozilla.org/messengercompose/attachment;1", &rv); + if (NS_SUCCEEDED(rv) && attachment) { + nsAutoString nameStr; + rv = nsMsgI18NConvertToUnicode("UTF-8"_ns, curAttachment->m_realName, + 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; + nsCString cloudPartHeaderData; + + provider.Adopt( + MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(), + "provider", nullptr, nullptr)); + cloudUrl.Adopt(MimeHeaders_get_parameter( + curAttachment->m_cloudPartInfo.get(), "url", nullptr, nullptr)); + cloudPartHeaderData.Adopt( + MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(), + "data", nullptr, nullptr)); + + attachment->SetSendViaCloud(true); + attachment->SetCloudFileAccountKey(provider); + attachment->SetContentLocation(cloudUrl); + attachment->SetCloudPartHeaderData(cloudPartHeaderData); + } + 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("@mozilla.org/messengercompose/composeparams;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + pMsgComposeParams->SetType(composeType); + pMsgComposeParams->SetFormat(format); + pMsgComposeParams->SetIdentity(identity); + pMsgComposeParams->SetComposeFields(compFields); + if (!originalMsgURI.IsEmpty()) + 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 nsACString& 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 nsACString& 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("@mozilla.org/messengercompose/compose;1", &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); + + RefPtr<mozilla::dom::Promise> promise; + rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr, + nullptr, nullptr, getter_AddRefs(promise)); + 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, + const nsTArray<nsString>& otherHeaders, char* charset, + nsIMsgCompFields** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + *_retval = nullptr; + + nsCOMPtr<nsIMsgCompFields> cFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(cFields, NS_ERROR_OUT_OF_MEMORY); + + nsAutoCString val; + nsAutoString outString; + + if (from) { + nsMsgI18NConvertRawBytesToUTF16( + nsDependentCString(from), + charset ? nsDependentCString(charset) : EmptyCString(), 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) { + nsMsgI18NConvertRawBytesToUTF16( + nsDependentCString(reply_to), + charset ? nsDependentCString(charset) : EmptyCString(), outString); + cFields->SetReplyTo(outString); + } + + if (to) { + nsMsgI18NConvertRawBytesToUTF16( + nsDependentCString(to), + charset ? nsDependentCString(charset) : EmptyCString(), outString); + cFields->SetTo(outString); + } + + if (cc) { + nsMsgI18NConvertRawBytesToUTF16( + nsDependentCString(cc), + charset ? nsDependentCString(charset) : EmptyCString(), outString); + cFields->SetCc(outString); + } + + if (bcc) { + nsMsgI18NConvertRawBytesToUTF16( + nsDependentCString(bcc), + charset ? nsDependentCString(charset) : EmptyCString(), 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); + } + + nsTArray<nsString> cFieldsOtherHeaders; + cFields->GetOtherHeaders(cFieldsOtherHeaders); + for (auto otherHeader : otherHeaders) { + if (!otherHeader.IsEmpty()) { + MIME_DecodeMimeHeader(NS_ConvertUTF16toUTF8(otherHeader).get(), charset, + false, true, val); + cFieldsOtherHeaders.AppendElement(NS_ConvertUTF8toUTF16(val)); + } else { + cFieldsOtherHeaders.AppendElement(u""_ns); + } + } + cFields->SetOtherHeaders(cFieldsOtherHeaders); + cFields.forget(_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.LowerCaseFindASCII("text/html") == kNotFound && + mdd->messageBody->m_type.LowerCaseFindASCII("text/plain") == kNotFound && + !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/vcard") || + 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.LowerCaseFindASCII(MESSAGE_RFC822) != kNotFound) + // 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()) { + if (htmlEdit) { + nsCString escaped; + nsAppendEscapedHTML(utf8Value, escaped); + NS_MsgSACat(body, escaped.get()); + } else { + NS_MsgSACat(body, utf8Value.get()); + } + } 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 convert_plaintext_body_to_html(char** body) { + // We need to convert the plain/text to HTML in order to escape any HTML + // markup. + nsCString escapedBody; + nsAppendEscapedHTML(nsDependentCString(*body), escapedBody); + + nsCString newBody; + char* q = escapedBody.BeginWriting(); + char* p; + int prevQuoteLevel = 0; + bool isFlowed = false; + bool haveSig = false; + + // First detect whether this appears to be flowed or not. + p = q; + while (*p) { + // At worst we read the null byte terminator. + if (*p == ' ' && (*(p + 1) == '\r' || *(p + 1) == '\n')) { + // This looks flowed, but don't get fooled by a signature separator: + // --space + if (p - 3 >= q && (*(p - 3) == '\r' || *(p - 3) == '\n') && + *(p - 2) == '-' && *(p - 1) == '-') { + p++; + continue; + } + if (p - 2 == q && *(p - 2) == '-' && *(p - 1) == '-') { + p++; + continue; + } + isFlowed = true; + break; + } + p++; + } + + while (*q) { + p = q; + // Detect quotes. A quote character is a ">" which was escaped to >. + // In non-flowed messages the quote character can be optionally followed by + // a space. Examples: Level 0 + // > Level 0 (with leading space) + // > Level 1 + // > > Level 1 (with leading space, note the two spaces between the quote + // characters) + // >> Level 2 + // > > Level 2 (only when non-flowed, otherwise Level 1 with leading space) + // >>> Level 3 + // > > > Level 3 (with leading space, only when non-flowed, otherwise Level + // 1) + int quoteLevel = 0; + while (strncmp(p, ">", 4) == 0) { + p += 4; + if (!isFlowed && *p == ' ') p++; + quoteLevel++; + } + + // Eat space following quote character, for non-flowed already eaten above. + if (quoteLevel > 0 && isFlowed && *p == ' ') p++; + + // Close any open signatures if we find a quote. Strange, that shouldn't + // happen. + if (quoteLevel > 0 && haveSig) { + newBody.AppendLiteral("</pre>"); + haveSig = false; + } + if (quoteLevel > prevQuoteLevel) { + while (prevQuoteLevel < quoteLevel) { + if (isFlowed) + newBody.AppendLiteral("<blockquote type=\"cite\">"); + else + newBody.AppendLiteral( + "<blockquote type=\"cite\"><pre wrap class=\"moz-quote-pre\">"); + prevQuoteLevel++; + } + } else if (quoteLevel < prevQuoteLevel) { + while (prevQuoteLevel > quoteLevel) { + if (isFlowed) + newBody.AppendLiteral("</blockquote>"); + else + newBody.AppendLiteral("</pre></blockquote>"); + prevQuoteLevel--; + } + } + // Position after the quote. + q = p; + + // Detect signature. + bool forceBR = false; + if (quoteLevel == 0) { + if (strncmp(q, "-- \r", 4) == 0 || strncmp(q, "-- \n", 4) == 0) { + haveSig = true; + forceBR = true; + newBody.AppendLiteral("<pre class=\"moz-signature\">"); + } + } + + bool seenSpace = false; + while (*p && *p != '\r' && *p != '\n') { + seenSpace = (*p == ' '); + p++; + continue; + } + if (!*p) { + // We're at the end of the string. + if (p > q) { + // Copy last bit over. + newBody.Append(q); + } + break; + } + if (*p == '\r' && + *(p + 1) == '\n') { // At worst we read the null byte terminator. + // Skip the CR in CRLF. + *p = 0; // don't copy skipped \r. + p++; + } + *p = 0; + newBody.Append(q); + if (!isFlowed || !seenSpace || forceBR) newBody.AppendLiteral("<br>"); + q = p + 1; + } + + // Close all open quotes. + while (prevQuoteLevel > 0) { + if (isFlowed) + newBody.AppendLiteral("</blockquote>"); + else + newBody.AppendLiteral("</pre></blockquote>"); + prevQuoteLevel--; + } + + // Close any open signatures. + if (haveSig) { + newBody.AppendLiteral("</pre>"); + haveSig = false; + } + + PR_Free(*body); + *body = ToNewCString(newBody); +} + +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; + nsTArray<nsString> readOtherHeaders; + + 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; + // Override the charset only if requested. If the message doesn't have + // one and we're not overriding, we'll detect it later. + if (charsetOverride && mdd->options->default_charset) { + PR_FREEIF(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", 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); + } + } + + // Other headers via pref. + nsCString otherHeaders; + nsTArray<nsCString> otherHeadersArray; + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + pPrefBranch->GetCharPref("mail.compose.other.header", otherHeaders); + if (!otherHeaders.IsEmpty()) { + ToLowerCase(otherHeaders); + ParseString(otherHeaders, ',', otherHeadersArray); + for (auto otherHeader : otherHeadersArray) { + otherHeader.Trim(" "); + nsAutoCString result; + result.Assign( + MimeHeaders_get(mdd->headers, otherHeader.get(), false, false)); + readOtherHeaders.AppendElement(NS_ConvertUTF8toUTF16(result)); + } + } + } + + CreateCompositionFields(from, repl, to, cc, bcc, fcc, grps, foll, org, subj, + refs, priority, news_host, readOtherHeaders, + 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); + + // We always preserve an existing message ID, if present, apart from some + // exceptions. + bool keepID = fields != nullptr; + + // Don't keep ID when forwarding inline. + if (forward_inline) keepID = false; + + // nsMimeOutput::nsMimeMessageEditorTemplate is used for editing a message + // "as new", creating a message from a template or editing a template. + // Only in the latter case we want to preserve the ID. + if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate && + !PL_strstr(mdd->url_name, "&edittempl=true")) + keepID = false; + + if (keepID) 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::Unset; + 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("@mozilla.org/messenger/account-manager;1", &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.LowerCaseFindASCII("text/html") != + kNotFound) + composeFormat = nsIMsgCompFormat::HTML; + else if (mdd->messageBody->m_type.LowerCaseFindASCII("text/plain") != + kNotFound || + 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; + + if (!bodyAsAttachment && mdd->messageBody->m_tmpFile) { + int64_t fileSize; + nsCOMPtr<nsIFile> tempFileCopy; + mdd->messageBody->m_tmpFile->Clone(getter_AddRefs(tempFileCopy)); + mdd->messageBody->m_tmpFile = tempFileCopy; + tempFileCopy = nullptr; + mdd->messageBody->m_tmpFile->GetFileSize(&fileSize); + uint32_t bodyLen = 0; + + // 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. + nsAutoCString bodyCharset; + if (mimeCharset) { + bodyCharset.Adopt(mimeCharset); + } else if (mdd->mailcharset) { + bodyCharset.Assign(mdd->mailcharset); + } + if (bodyCharset.IsEmpty()) { + nsAutoCString detectedCharset; + // We need to detect it. + rv = MIME_detect_charset(body, bodyLen, detectedCharset); + if (NS_SUCCEEDED(rv) && !detectedCharset.IsEmpty()) { + bodyCharset = detectedCharset; + } + } + if (!bodyCharset.IsEmpty()) { + nsAutoString tmpUnicodeBody; + rv = nsMsgI18NConvertToUnicode( + bodyCharset, nsDependentCString(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; + } + } + } + } + + 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. + convert_plaintext_body_to_html(&body); + } + // 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); + } + + MSG_ComposeType msgComposeType = 0; // Keep compilers happy. + if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate) { + if (PL_strstr(mdd->url_name, "?redirect=true") || + PL_strstr(mdd->url_name, "&redirect=true")) + msgComposeType = nsIMsgCompType::Redirect; + else if (PL_strstr(mdd->url_name, "?editasnew=true") || + PL_strstr(mdd->url_name, "&editasnew=true")) + msgComposeType = nsIMsgCompType::EditAsNew; + else if (PL_strstr(mdd->url_name, "?edittempl=true") || + PL_strstr(mdd->url_name, "&edittempl=true")) + msgComposeType = nsIMsgCompType::EditTemplate; + else + msgComposeType = nsIMsgCompType::Template; + } + + if (body && msgComposeType == nsIMsgCompType::EditAsNew) { + // When editing as new, we respect the identities preferred format + // which can be overridden. + if (mdd->identity) { + bool identityComposeHTML; + mdd->identity->GetComposeHtml(&identityComposeHTML); + + if (composeFormat == nsIMsgCompFormat::HTML && + identityComposeHTML == mdd->overrideComposeFormat) { + // We we have HTML: + // If they want HTML and they want to override it (true == true) + // or they don't want HTML and they don't want to override it + // (false == false), then convert. Conversion happens below. + convertToPlainText = true; + composeFormat = nsIMsgCompFormat::PlainText; + } else if (composeFormat == nsIMsgCompFormat::PlainText && + identityComposeHTML != mdd->overrideComposeFormat) { + // We have plain text: + // If they want HTML and they don't want to override it (true != + // false) or they don't want HTML and they want to override it + // (false != true), then convert. + convert_plaintext_body_to_html(&body); + composeFormat = nsIMsgCompFormat::HTML; + } + } + } else if (body && mdd->overrideComposeFormat && + (msgComposeType == nsIMsgCompType::Template || + msgComposeType == nsIMsgCompType::EditTemplate || + !mdd->forwardInline)) // Draft processing. + { + // When using a template and overriding, the user gets the + // "other" format. + if (composeFormat == nsIMsgCompFormat::PlainText) { + convert_plaintext_body_to_html(&body); + composeFormat = nsIMsgCompFormat::HTML; + } else { + // Conversion happens below. + convertToPlainText = true; + composeFormat = nsIMsgCompFormat::PlainText; + } + } + + // 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) { + // Set the draft ID when editing a template so the original is + // overwritten when saving the template again. + // Note that always setting the draft ID here would cause drafts to be + // overwritten when edited "as new", which is undesired. + if (msgComposeType == nsIMsgCompType::EditTemplate) { + fields->SetDraftId(nsDependentCString(mdd->url_name)); + fields->SetTemplateId(nsDependentCString( + mdd->url_name)); // Remember original template ID. + } + + if (convertToPlainText) fields->ConvertBodyToPlainText(); + + 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 { + if (convertToPlainText) fields->ConvertBodyToPlainText(); + fields->SetDraftId(nsDependentCString(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, + EmptyCString(), 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(nsDependentCString(mdd->url_name)); + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Draft, + nsIMsgCompFormat::Default, mdd->identity, + EmptyCString(), mdd->origMsgHdr); + } + } + } + } else { + CreateCompositionFields(from, repl, to, cc, bcc, fcc, grps, foll, org, subj, + refs, priority, news_host, readOtherHeaders, + mdd->mailcharset, getter_AddRefs(fields)); + if (fields) + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::New, + nsIMsgCompFormat::Default, mdd->identity, + EmptyCString(), 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); + 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) { + if (mdd->options->default_charset) + mdd->mailcharset = strdup(mdd->options->default_charset); + else { + mdd->mailcharset = strdup(""); + mdd->autodetectCharset = true; + } + } 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)); + + nsCOMPtr<nsIFile> tmpFile = nullptr; + { + // Let's build a temp file with an extension based on the content-type: + // nsmail.<extension> + + nsAutoCString newAttachName("nsmail"); + nsAutoCString fileExtension; + // 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); + int32_t extLoc = newAttachment->m_realName.RFindChar('.'); + int32_t specLength = newAttachment->m_realName.Length(); + // @see nsExternalHelperAppService::GetTypeFromURI() + if (extLoc != -1 && extLoc != specLength - 1 && + // nothing over 20 chars long can be sanely considered an + // extension.... Dat dere would be just data. + specLength - extLoc < 20) { + fileExtension = Substring(newAttachment->m_realName, extLoc + 1); + } else { + nsCOMPtr<nsIMIMEService> mimeFinder( + do_GetService(NS_MIMESERVICE_CONTRACTID)); + if (mimeFinder) { + mimeFinder->GetPrimaryExtension(contentType, ""_ns, fileExtension); + } + } + + if (fileExtension.IsEmpty()) { + newAttachName.AppendLiteral(".tmp"); + } else { + newAttachName.Append('.'); + newAttachName.Append(fileExtension); + } + + 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 = 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->autodetectCharset) { + nsAutoCString detectedCharset; + nsresult res = NS_OK; + res = MIME_detect_charset(buf, size, detectedCharset); + if (NS_SUCCEEDED(res) && !detectedCharset.IsEmpty()) { + mdd->mailcharset = ToNewCString(detectedCharset); + mdd->autodetectCharset = false; + } + } + + 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, nullptr, getter_AddRefs(aURL)); + 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); + if (mdd->options) delete mdd->options; + PR_Free(mdd); + } + PR_Free(stream); + PR_Free(obj); + + return nullptr; +} diff --git a/comm/mailnews/mime/src/mimeebod.cpp b/comm/mailnews/mime/src/mimeebod.cpp new file mode 100644 index 0000000000..755574b1b0 --- /dev/null +++ b/comm/mailnews/mime/src/mimeebod.cpp @@ -0,0 +1,441 @@ +/* -*- 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 "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; +#ifdef XP_UNIX + } else if (!PL_strcasecmp(at, "local-file") || !PL_strcasecmp(at, "afs")) { + if (!name) return 0; + + 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("/afs/."_ns); + fs->Exists(&exists); + } + if (!exists) return 0; + } + + 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; +#endif + } 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) { + const char* pre = "<P><PRE>"; + const char* suf = "</PRE>"; + int32_t i; + // The end condition requires i to be negative, so it's ok to + // allow the starting value to be negative. + for (i = strlen(s) - 1; i >= 0 && IS_SPACE(s[i]); i--) s[i] = 0; + nsCString s2; + nsAppendEscapedHTML(nsDependentCString(s), s2); + body = (char*)PR_MALLOC(strlen(pre) + s2.Length() + strlen(suf) + 1); + if (!body) { + goto FAIL; + } + PL_strcpy(body, pre); + PL_strcat(body, s2.get()); + 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("/afs/."_ns); + 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/comm/mailnews/mime/src/mimeebod.h b/comm/mailnews/mime/src/mimeebod.h new file mode 100644 index 0000000000..fcb33ee664 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimeenc.cpp b/comm/mailnews/mime/src/mimeenc.cpp new file mode 100644 index 0000000000..1c9df3794b --- /dev/null +++ b/comm/mailnews/mime/src/mimeenc.cpp @@ -0,0 +1,999 @@ +/* -*- 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 "mimei.h" +#include "prmem.h" +#include "mimeobj.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/mailnews/MimeEncoder.h" +#include "modmimee.h" // for MimeConverterOutputCallback + +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) { + if (outSize) *outSize = 0; + + // Without input there is nothing to do. + if (length == 0) return 1; + + /* 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); + if (outSize) *outSize += n; + 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/comm/mailnews/mime/src/mimeeobj.cpp b/comm/mailnews/mime/src/mimeeobj.cpp new file mode 100644 index 0000000000..2bc858b4b4 --- /dev/null +++ b/comm/mailnews/mime/src/mimeeobj.cpp @@ -0,0 +1,215 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimeeobj.h b/comm/mailnews/mime/src/mimeeobj.h new file mode 100644 index 0000000000..069a94f60c --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimefilt.cpp b/comm/mailnews/mime/src/mimefilt.cpp new file mode 100644 index 0000000000..61b1ac6ae8 --- /dev/null +++ b/comm/mailnews/mime/src/mimefilt.cpp @@ -0,0 +1,349 @@ +/* -*- 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/. */ + +/* 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; + size_t s_len = strlen(s); + if (s_len && s[slen - 1] == '\r' || s[slen - 1] == '\n') s[slen - 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/comm/mailnews/mime/src/mimehdrs.cpp b/comm/mailnews/mime/src/mimehdrs.cpp new file mode 100644 index 0000000000..6df9028765 --- /dev/null +++ b/comm/mailnews/mime/src/mimehdrs.cpp @@ -0,0 +1,785 @@ +/* -*- 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 "msgCore.h" +#include "mimei.h" +#include "prmem.h" +#include "prlog.h" +#include "plstr.h" +#include "mimebuf.h" +#include "mimemoz2.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" +#include "mozilla/Unused.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; + nsMsgI18NConvertRawBytesToUTF8( + value, + opt->default_charset ? nsDependentCString(opt->default_charset) + : EmptyCString(), + 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; +} + +static bool find_header_starts(MimeHeaders* hdrs, bool counting) { + const char* end = hdrs->all_headers + hdrs->all_headers_fp; + char* s = hdrs->all_headers; + int i = 0; + + if (counting) { + // For the start pointer + hdrs->heads_size = 1; + } else { + hdrs->heads[i++] = 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) { + if (counting) { + hdrs->heads_size++; + } else { + NS_ASSERTION(i < hdrs->heads_size, + "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (i >= hdrs->heads_size) return false; + hdrs->heads[i++] = s; + } + } + } + if (!counting) { + NS_ASSERTION(i == hdrs->heads_size, "unexpected"); + } + return true; +} + +int MimeHeaders_build_heads_list(MimeHeaders* hdrs) { + 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; + } + } + + find_header_starts(hdrs, true); + + /* Now allocate storage for the pointers to each of those headers. + */ + hdrs->heads = (char**)PR_MALLOC((hdrs->heads_size) * sizeof(char*)); + if (!hdrs->heads) return MIME_OUT_OF_MEMORY; + memset(hdrs->heads, 0, (hdrs->heads_size) * sizeof(char*)); + + /* Now make another pass through the headers, and this time, record the + starting position of each header. + */ + if (!find_header_starts(hdrs, false)) { + return -1; + } + + 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; + size_t headLen = end - head; + + /* Quick hack to skip over BSD Mailbox delimiter. */ + if (i == 0 && head[0] == 'F' && headLen >= 5 && !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 occurrence 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(nsDependentCString(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; + size_t headLen = end - head; + + /* Hack for BSD Mailbox delimiter. */ + if (i == 0 && head[0] == 'F' && headLen >= 5 && + !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 = name.LowerCaseEqualsLiteral("to") || + name.LowerCaseEqualsLiteral("from") || + name.LowerCaseEqualsLiteral("cc") || + name.LowerCaseEqualsLiteral("bcc") || + name.LowerCaseEqualsLiteral("reply-to") || + name.LowerCaseEqualsLiteral("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(nsMsgI18NConvertFromUnicode( + nsDependentCString(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) { + mozilla::Unused << 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/comm/mailnews/mime/src/mimehdrs.h b/comm/mailnews/mime/src/mimehdrs.h new file mode 100644 index 0000000000..028092246a --- /dev/null +++ b/comm/mailnews/mime/src/mimehdrs.h @@ -0,0 +1,85 @@ +/* -*- 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/comm/mailnews/mime/src/mimei.cpp b/comm/mailnews/mime/src/mimei.cpp new file mode 100644 index 0000000000..3001189f8e --- /dev/null +++ b/comm/mailnews/mime/src/mimei.cpp @@ -0,0 +1,1716 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made + * by IBM described herein are Copyright (c) International Business Machines + * Corporation, 2000. Modifications to Mozilla code or documentation identified + * per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +// clang-format off +#include "nsCOMPtr.h" +#include "mimeobj.h" /* MimeObject (abstract) */ +#include "mimecont.h" /* |--- MimeContainer (abstract) */ +#include "mimemult.h" /* | |--- MimeMultipart (abstract) */ +#include "mimemmix.h" /* | | |--- MimeMultipartMixed */ +#include "mimemdig.h" /* | | |--- MimeMultipartDigest */ +#include "mimempar.h" /* | | |--- MimeMultipartParallel */ +#include "mimemalt.h" /* | | |--- MimeMultipartAlternative */ +#include "mimemrel.h" /* | | |--- MimeMultipartRelated */ +#include "mimemapl.h" /* | | |--- MimeMultipartAppleDouble */ +#include "mimesun.h" /* | | |--- MimeSunAttachment */ +#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/ +#ifdef ENABLE_SMIME +#include "mimemcms.h" /* | | |---MimeMultipartSignedCMS */ +#endif +#include "mimecryp.h" /* | |--- MimeEncrypted (abstract) */ +#ifdef ENABLE_SMIME +#include "mimecms.h" /* | | |--- MimeEncryptedPKCS7 */ +#endif +#include "mimemsg.h" /* | |--- MimeMessage */ +#include "mimeunty.h" /* | |--- MimeUntypedText */ +#include "mimeleaf.h" /* |--- MimeLeaf (abstract) */ +#include "mimetext.h" /* | |--- MimeInlineText (abstract) */ +#include "mimetpla.h" /* | | |--- MimeInlineTextPlain */ +#include "mimethpl.h" /* | | | |--- M.I.TextHTMLAsPlaintext */ +#include "mimetpfl.h" /* | | |--- MimeInlineTextPlainFlowed */ +#include "mimethtm.h" /* | | |--- MimeInlineTextHTML */ +#include "mimethsa.h" /* | | | |--- M.I.TextHTMLSanitized */ +#include "mimeTextHTMLParsed.h" /*| | |--- M.I.TextHTMLParsed */ +#include "mimetric.h" /* | | |--- MimeInlineTextRichtext */ +#include "mimetenr.h" /* | | | |--- MimeInlineTextEnriched */ +/* SUPPORTED VIA PLUGIN | | |--- MimeInlineTextVCard */ +#include "mimeiimg.h" /* | |--- MimeInlineImage */ +#include "mimeeobj.h" /* | |--- MimeExternalObject */ +#include "mimeebod.h" /* |--- MimeExternalBody */ + /* If you add classes here,also add them to mimei.h */ +// clang-format on + +#include "prlog.h" +#include "prmem.h" +#include "prenv.h" +#include "plstr.h" +#include "prlink.h" +#include "prprf.h" +#include "mimecth.h" +#include "mimebuf.h" +#include "mimemoz2.h" +#include "nsIMimeContentTypeHandler.h" +#include "nsICategoryManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsXPCOMCID.h" +#include "nsISimpleMimeConverter.h" +#include "nsSimpleMimeConverterStub.h" +#include "nsTArray.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include "nsMsgUtils.h" +#include "nsIPrefBranch.h" +#include "mozilla/Preferences.h" +#include "imgLoader.h" + +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgHdr.h" +#include "nsIMailChannel.h" + +using namespace mozilla; + +// forward declaration +void getMsgHdrForCurrentURL(MimeDisplayOptions* opts, nsIMsgDBHdr** aMsgHdr); + +#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part" +#define EXTERNAL_ATTACHMENT_URL_HEADER "X-Mozilla-External-Attachment-URL" + +/* ========================================================================== + Allocation and destruction + ========================================================================== + */ +static int mime_classinit(MimeObjectClass* clazz); + +/* + * These are the necessary defines/variables for doing + * content type handlers in external plugins. + */ +typedef struct { + char content_type[128]; + bool force_inline_display; +} cthandler_struct; + +nsTArray<cthandler_struct*>* ctHandlerList = nullptr; + +/* + * This will return TRUE if the content_type is found in the + * list, FALSE if it is not found. + */ +bool find_content_type_attribs(const char* content_type, + bool* force_inline_display) { + *force_inline_display = false; + if (!ctHandlerList) return false; + + for (size_t i = 0; i < ctHandlerList->Length(); i++) { + cthandler_struct* ptr = ctHandlerList->ElementAt(i); + if (PL_strcasecmp(content_type, ptr->content_type) == 0) { + *force_inline_display = ptr->force_inline_display; + return true; + } + } + + return false; +} + +void add_content_type_attribs(const char* content_type, + contentTypeHandlerInitStruct* ctHandlerInfo) { + cthandler_struct* ptr = nullptr; + bool force_inline_display; + + if (find_content_type_attribs(content_type, &force_inline_display)) return; + + if (!content_type || !ctHandlerInfo) return; + + if (!ctHandlerList) ctHandlerList = new nsTArray<cthandler_struct*>(); + + if (!ctHandlerList) return; + + ptr = (cthandler_struct*)PR_MALLOC(sizeof(cthandler_struct)); + if (!ptr) return; + + PL_strncpy(ptr->content_type, content_type, sizeof(ptr->content_type)); + ptr->force_inline_display = ctHandlerInfo->force_inline_display; + ctHandlerList->AppendElement(ptr); +} + +/* + * This routine will find all content type handler for a specific content + * type (if it exists) + */ +bool force_inline_display(const char* content_type) { + bool force_inline_disp; + + find_content_type_attribs(content_type, &force_inline_disp); + return force_inline_disp; +} + +/* + * This routine will find all content type handler for a specific content + * type (if it exists) and is defined to the nsRegistry + */ +MimeObjectClass* mime_locate_external_content_handler( + const char* content_type, contentTypeHandlerInitStruct* ctHandlerInfo) { + if (!content_type || !*(content_type)) // null or empty content type + return nullptr; + + MimeObjectClass* newObj = nullptr; + nsresult rv; + + nsAutoCString lookupID("@mozilla.org/mimecth;1?type="); + nsAutoCString contentType; + ToLowerCase(nsDependentCString(content_type), contentType); + lookupID += contentType; + + nsCOMPtr<nsIMimeContentTypeHandler> ctHandler = + do_CreateInstance(lookupID.get(), &rv); + if (NS_FAILED(rv) || !ctHandler) { + nsCOMPtr<nsICategoryManager> catman = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return nullptr; + + nsCString value; + rv = catman->GetCategoryEntry(NS_SIMPLEMIMECONVERTERS_CATEGORY, contentType, + value); + if (NS_FAILED(rv) || value.IsEmpty()) return nullptr; + rv = MIME_NewSimpleMimeConverterStub(contentType.get(), + getter_AddRefs(ctHandler)); + if (NS_FAILED(rv) || !ctHandler) return nullptr; + } + + rv = ctHandler->CreateContentTypeHandlerClass(contentType.get(), + ctHandlerInfo, &newObj); + if (NS_FAILED(rv)) return nullptr; + + add_content_type_attribs(contentType.get(), ctHandlerInfo); + return newObj; +} + +/* This is necessary to expose the MimeObject method outside of this DLL */ +int MIME_MimeObject_write(MimeObject* obj, const char* output, int32_t length, + bool user_visible_p) { + return MimeObject_write(obj, output, length, user_visible_p); +} + +MimeObject* mime_new(MimeObjectClass* clazz, MimeHeaders* hdrs, + const char* override_content_type) { + int size = clazz->instance_size; + MimeObject* object; + int status; + + /* Some assertions to verify that this isn't random junk memory... */ + NS_ASSERTION(clazz->class_name && strlen(clazz->class_name) > 0, + "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + NS_ASSERTION(size > 0 && size < 1000, + "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (!clazz->class_initialized) { + status = mime_classinit(clazz); + if (status < 0) return 0; + } + + NS_ASSERTION(clazz->initialize && clazz->finalize, + "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (hdrs) { + hdrs = MimeHeaders_copy(hdrs); + if (!hdrs) return 0; + } + + object = (MimeObject*)PR_MALLOC(size); + if (!object) return 0; + + memset(object, 0, size); + object->clazz = clazz; + object->headers = hdrs; + object->dontShowAsAttachment = false; + + if (override_content_type && *override_content_type) + object->content_type = strdup(override_content_type); + + status = clazz->initialize(object); + if (status < 0) { + clazz->finalize(object); + PR_Free(object); + return 0; + } + + return object; +} + +void mime_free(MimeObject* object) { +#ifdef DEBUG__ + int i, size = object->clazz->instance_size; + uint32_t* array = (uint32_t*)object; +#endif /* DEBUG */ + + object->clazz->finalize(object); + +#ifdef DEBUG__ + for (i = 0; i < (size / sizeof(*array)); i++) array[i] = (uint32_t)0xDEADBEEF; +#endif /* DEBUG */ + + PR_Free(object); +} + +bool mime_is_allowed_class(const MimeObjectClass* clazz, + int32_t types_of_classes_to_disallow) { + if (types_of_classes_to_disallow == 0) return true; + bool avoid_html = (types_of_classes_to_disallow >= 1); + bool avoid_images = (types_of_classes_to_disallow >= 2); + bool avoid_strange_content = (types_of_classes_to_disallow >= 3); + bool allow_only_vanilla_classes = (types_of_classes_to_disallow == 100); + + if (allow_only_vanilla_classes) + /* A "safe" class is one that is unlikely to have security bugs or to + allow security exploits or one that is essential for the usefulness + of the application, even for paranoid users. + What's included here is more personal judgement than following + strict rules, though, unfortunately. + The function returns true only for known good classes, i.e. is a + "whitelist" in this case. + This idea comes from Georgi Guninski. + */ + return (clazz == (MimeObjectClass*)&mimeInlineTextPlainClass || + clazz == (MimeObjectClass*)&mimeInlineTextPlainFlowedClass || + clazz == (MimeObjectClass*)&mimeInlineTextHTMLSanitizedClass || + clazz == (MimeObjectClass*)&mimeInlineTextHTMLAsPlaintextClass || + /* The latter 2 classes bear some risk, because they use the Gecko + HTML parser, but the user has the option to make an explicit + choice in this case, via html_as. */ + clazz == (MimeObjectClass*)&mimeMultipartMixedClass || + clazz == (MimeObjectClass*)&mimeMultipartAlternativeClass || + clazz == (MimeObjectClass*)&mimeMultipartDigestClass || + clazz == (MimeObjectClass*)&mimeMultipartAppleDoubleClass || + clazz == (MimeObjectClass*)&mimeMessageClass || + clazz == (MimeObjectClass*)&mimeExternalObjectClass || + /* mimeUntypedTextClass? -- does uuencode */ +#ifdef ENABLE_SMIME + clazz == (MimeObjectClass*)&mimeMultipartSignedCMSClass || + clazz == (MimeObjectClass*)&mimeEncryptedCMSClass || +#endif + clazz == 0); + + /* Contrairy to above, the below code is a "blacklist", i.e. it + *excludes* some "bad" classes. */ + return !( + (avoid_html && (clazz == (MimeObjectClass*)&mimeInlineTextHTMLParsedClass + /* Should not happen - we protect against that in + mime_find_class(). Still for safety... */ + )) || + (avoid_images && (clazz == (MimeObjectClass*)&mimeInlineImageClass)) || + (avoid_strange_content && + (clazz == (MimeObjectClass*)&mimeInlineTextEnrichedClass || + clazz == (MimeObjectClass*)&mimeInlineTextRichtextClass || + clazz == (MimeObjectClass*)&mimeSunAttachmentClass || + clazz == (MimeObjectClass*)&mimeExternalBodyClass))); +} + +void getMsgHdrForCurrentURL(MimeDisplayOptions* opts, nsIMsgDBHdr** aMsgHdr) { + *aMsgHdr = nullptr; + + if (!opts) return; + + mime_stream_data* msd = (mime_stream_data*)(opts->stream_closure); + if (!msd) return; + + nsCOMPtr<nsIChannel> channel = + msd->channel; // note the lack of ref counting... + if (channel) { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgMessageUrl> msgURI; + channel->GetURI(getter_AddRefs(uri)); + if (uri) { + msgURI = do_QueryInterface(uri); + if (msgURI) { + msgURI->GetMessageHeader(aMsgHdr); + if (*aMsgHdr) return; + nsCString rdfURI; + msgURI->GetUri(rdfURI); + if (!rdfURI.IsEmpty()) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + GetMsgDBHdrFromURI(rdfURI, getter_AddRefs(msgHdr)); + NS_IF_ADDREF(*aMsgHdr = msgHdr); + } + } + } + } + + return; +} + +MimeObjectClass* mime_find_class(const char* content_type, MimeHeaders* hdrs, + MimeDisplayOptions* opts, bool exact_match_p) { + MimeObjectClass* clazz = 0; + MimeObjectClass* tempClass = 0; + contentTypeHandlerInitStruct ctHandlerInfo; + + // Read some prefs + nsIPrefBranch* prefBranch = GetPrefBranch(opts); + int32_t html_as = 0; // def. see below + int32_t types_of_classes_to_disallow = 0; /* Let only a few libmime classes + process incoming data. This protects from bugs (e.g. buffer overflows) + and from security loopholes (e.g. allowing unchecked HTML in some + obscure classes, although the user has html_as > 0). + This option is mainly for the UI of html_as. + 0 = allow all available classes + 1 = Use hardcoded blacklist to avoid rendering (incoming) HTML + 2 = ... and images + 3 = ... and some other uncommon content types + 4 = show all body parts + 100 = Use hardcoded whitelist to avoid even more bugs(buffer overflows). + This mode will limit the features available (e.g. uncommon + attachment types and inline images) and is for paranoid users. + */ + if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer && + opts->format_out != nsMimeOutput::nsMimeMessageDecrypt && + opts->format_out != nsMimeOutput::nsMimeMessageAttach) + if (prefBranch) { + prefBranch->GetIntPref("mailnews.display.html_as", &html_as); + prefBranch->GetIntPref("mailnews.display.disallow_mime_handlers", + &types_of_classes_to_disallow); + if (types_of_classes_to_disallow > 0 && html_as == 0) + // We have non-sensical prefs. Do some fixup. + html_as = 1; + } + + // First, check to see if the message has been marked as JUNK. If it has, + // then force the message to be rendered as simple, unless this has been + // called by a filtering routine. + bool sanitizeJunkMail = false; + + // it is faster to read the pref first then figure out the msg hdr for the + // current url only if we have to + // XXX instead of reading this pref every time, part of mime should be an + // observer listening to this pref change and updating internal state + // accordingly. But none of the other prefs in this file seem to be doing + // that...=( + if (prefBranch) + prefBranch->GetBoolPref("mail.spam.display.sanitize", &sanitizeJunkMail); + + if (sanitizeJunkMail && + !(opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer)) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr)); + if (msgHdr) { + nsCString junkScoreStr; + (void)msgHdr->GetStringProperty("junkscore", junkScoreStr); + if (html_as == 0 && junkScoreStr.get() && atoi(junkScoreStr.get()) > 50) + html_as = 3; // 3 == Simple HTML + } // if msgHdr + } // if we are supposed to sanitize junk mail + + /* + * What we do first is check for an external content handler plugin. + * This will actually extend the mime handling by calling a routine + * which will allow us to load an external content type handler + * for specific content types. If one is not found, we will drop back + * to the default handler. + */ + if ((tempClass = mime_locate_external_content_handler( + content_type, &ctHandlerInfo)) != nullptr) { +#ifdef MOZ_THUNDERBIRD + // This is a case where we only want to add this property if we are a + // thunderbird build AND we have found an external mime content handler for + // text/calendar This will enable iMIP support in Lightning + if (hdrs && (!PL_strncasecmp(content_type, "text/calendar", 13))) { + char* full_content_type = + MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (full_content_type) { + char* imip_method = MimeHeaders_get_parameter( + full_content_type, "method", nullptr, nullptr); + + mime_stream_data* msd = (mime_stream_data*)(opts->stream_closure); + nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(msd->channel); + if (mailChannel) { + mailChannel->SetImipMethod( + nsCString(imip_method ? imip_method : "nomethod")); + } + + // PR_Free checks for null + PR_Free(imip_method); + PR_Free(full_content_type); + } + } +#endif + + if (types_of_classes_to_disallow > 0 && + (!PL_strncasecmp(content_type, "text/vcard", 10) || + !PL_strncasecmp(content_type, "text/x-vcard", 12))) + /* Use a little hack to prevent some dangerous plugins, which ship + with Mozilla, to run. + For the truly user-installed plugins, we rely on the judgement + of the user. */ + { + if (!exact_match_p) + clazz = (MimeObjectClass*)&mimeExternalObjectClass; // As attachment + } else + clazz = (MimeObjectClass*)tempClass; + } else { + if (!content_type || !*content_type || + !PL_strcasecmp(content_type, "text")) /* with no / in the type */ + clazz = (MimeObjectClass*)&mimeUntypedTextClass; + + /* Subtypes of text... + */ + else if (!PL_strncasecmp(content_type, "text/", 5)) { + if (!PL_strcasecmp(content_type + 5, "html")) { + if (opts && + (opts->format_out == nsMimeOutput::nsMimeMessageSaveAs || + opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer || + opts->format_out == nsMimeOutput::nsMimeMessageDecrypt || + opts->format_out == nsMimeOutput::nsMimeMessageAttach)) + // SaveAs in new modes doesn't work yet. + { + // Don't use the parsed HTML class if we're ... + // - saving the HTML of a message + // - getting message content for filtering + // - snarfing attachments (nsMimeMessageDecrypt used in + // SnarfMsgAttachment) + // - processing attachments (like deleting attachments). + clazz = (MimeObjectClass*)&mimeInlineTextHTMLClass; + types_of_classes_to_disallow = 0; + } else if (html_as == 0 || html_as == 4) // Render sender's HTML + clazz = (MimeObjectClass*)&mimeInlineTextHTMLParsedClass; + else if (html_as == 1) // convert HTML to plaintext + // Do a HTML->TXT->HTML conversion, see mimethpl.h. + clazz = (MimeObjectClass*)&mimeInlineTextHTMLAsPlaintextClass; + else if (html_as == 2) // display HTML source + /* This is for the freaks. Treat HTML as plaintext, + which will cause the HTML source to be displayed. + Not very user-friendly, but some seem to want this. */ + clazz = (MimeObjectClass*)&mimeInlineTextPlainClass; + else if (html_as == 3) // Sanitize + // Strip all but allowed HTML + clazz = (MimeObjectClass*)&mimeInlineTextHTMLSanitizedClass; + else // Goofy pref + /* User has an unknown pref value. Maybe he used a newer Mozilla + with a new alternative to avoid HTML. Defaulting to option 1, + which is less dangerous than defaulting to the raw HTML. */ + clazz = (MimeObjectClass*)&mimeInlineTextHTMLAsPlaintextClass; + } else if (!PL_strcasecmp(content_type + 5, "enriched")) + clazz = (MimeObjectClass*)&mimeInlineTextEnrichedClass; + else if (!PL_strcasecmp(content_type + 5, "richtext")) + clazz = (MimeObjectClass*)&mimeInlineTextRichtextClass; + else if (!PL_strcasecmp(content_type + 5, "rtf")) + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + else if (!PL_strcasecmp(content_type + 5, "plain")) { + // Preliminary use the normal plain text + clazz = (MimeObjectClass*)&mimeInlineTextPlainClass; + + if (opts && + opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer && + opts->format_out != nsMimeOutput::nsMimeMessageAttach && + opts->format_out != nsMimeOutput::nsMimeMessageRaw) { + bool disable_format_flowed = false; + if (prefBranch) + prefBranch->GetBoolPref( + "mailnews.display.disable_format_flowed_support", + &disable_format_flowed); + + if (!disable_format_flowed) { + // Check for format=flowed, damn, it is already stripped away from + // the contenttype! + // Look in headers instead even though it's expensive and clumsy + // First find Content-Type: + char* content_type_row = + hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false) + : 0; + // Then the format parameter if there is one. + // I would rather use a PARAM_FORMAT but I can't find the right + // place to put the define. The others seems to be in net.h + // but is that really really the right place? There is also + // a nsMimeTypes.h but that one isn't included. Bug? + char* content_type_format = + content_type_row + ? MimeHeaders_get_parameter(content_type_row, "format", + nullptr, nullptr) + : 0; + + if (content_type_format && + !PL_strcasecmp(content_type_format, "flowed")) + clazz = (MimeObjectClass*)&mimeInlineTextPlainFlowedClass; + PR_FREEIF(content_type_format); + PR_FREEIF(content_type_row); + } + } + } else if (!exact_match_p) + clazz = (MimeObjectClass*)&mimeInlineTextPlainClass; + } + + /* Subtypes of multipart... + */ + else if (!PL_strncasecmp(content_type, "multipart/", 10)) { + // When html_as is 4, we want all MIME parts of the message to + // show up in the displayed message body, if they are MIME types + // that we know how to display, and also in the attachment pane + // if it's appropriate to put them there. Both + // multipart/alternative and multipart/related play games with + // hiding various MIME parts, and we don't want that to happen, + // so we prevent that by parsing those MIME types as + // multipart/mixed, which won't mess with anything. + // + // When our output format is nsMimeOutput::nsMimeMessageAttach, + // i.e., we are reformatting the message to remove attachments, + // we are in a similar boat. The code for deleting + // attachments properly in that mode is in mimemult.cpp + // functions which are inherited by mimeMultipartMixedClass but + // not by mimeMultipartAlternativeClass or + // mimeMultipartRelatedClass. Therefore, to ensure that + // everything is handled properly, in this context too we parse + // those MIME types as multipart/mixed. + bool basic_formatting = + (html_as == 4) || + (opts && opts->format_out == nsMimeOutput::nsMimeMessageAttach); + if (!PL_strcasecmp(content_type + 10, "alternative")) + clazz = basic_formatting + ? (MimeObjectClass*)&mimeMultipartMixedClass + : (MimeObjectClass*)&mimeMultipartAlternativeClass; + else if (!PL_strcasecmp(content_type + 10, "related")) + clazz = basic_formatting ? (MimeObjectClass*)&mimeMultipartMixedClass + : (MimeObjectClass*)&mimeMultipartRelatedClass; + else if (!PL_strcasecmp(content_type + 10, "digest")) + clazz = (MimeObjectClass*)&mimeMultipartDigestClass; + else if (!PL_strcasecmp(content_type + 10, "appledouble") || + !PL_strcasecmp(content_type + 10, "header-set")) + clazz = (MimeObjectClass*)&mimeMultipartAppleDoubleClass; + else if (!PL_strcasecmp(content_type + 10, "parallel")) + clazz = (MimeObjectClass*)&mimeMultipartParallelClass; + else if (!PL_strcasecmp(content_type + 10, "mixed")) + clazz = (MimeObjectClass*)&mimeMultipartMixedClass; +#ifdef ENABLE_SMIME + else if (!PL_strcasecmp(content_type + 10, "signed")) { + /* Check that the "protocol" and "micalg" parameters are ones we + know about. */ + char* ct = + hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false) : 0; + char* proto = + ct ? MimeHeaders_get_parameter(ct, PARAM_PROTOCOL, nullptr, nullptr) + : 0; + char* micalg = + ct ? MimeHeaders_get_parameter(ct, PARAM_MICALG, nullptr, nullptr) + : 0; + + if (proto && ((/* is a signature */ + !PL_strcasecmp(proto, APPLICATION_XPKCS7_SIGNATURE) || + !PL_strcasecmp(proto, APPLICATION_PKCS7_SIGNATURE)) && + micalg && + (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_4) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_5) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD2)))) + clazz = (MimeObjectClass*)&mimeMultipartSignedCMSClass; + else + clazz = 0; + + PR_FREEIF(proto); + PR_FREEIF(micalg); + PR_FREEIF(ct); + } +#endif + + if (!clazz && !exact_match_p) + /* Treat all unknown multipart subtypes as "multipart/mixed" */ + clazz = (MimeObjectClass*)&mimeMultipartMixedClass; + + /* If we are sniffing a message, let's treat alternative parts as mixed */ + if (opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer) + if (clazz == (MimeObjectClass*)&mimeMultipartAlternativeClass) + clazz = (MimeObjectClass*)&mimeMultipartMixedClass; + } + + /* Subtypes of message... + */ + else if (!PL_strncasecmp(content_type, "message/", 8)) { + if (!PL_strcasecmp(content_type + 8, "rfc822") || + !PL_strcasecmp(content_type + 8, "news")) + clazz = (MimeObjectClass*)&mimeMessageClass; + else if (!PL_strcasecmp(content_type + 8, "external-body")) + clazz = (MimeObjectClass*)&mimeExternalBodyClass; + else if (!PL_strcasecmp(content_type + 8, "partial")) + /* I guess these are most useful as externals, for now... */ + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + else if (!exact_match_p) + /* Treat all unknown message subtypes as "text/plain" */ + clazz = (MimeObjectClass*)&mimeInlineTextPlainClass; + } + + /* The magic image types which we are able to display internally... + */ + else if (!PL_strncasecmp(content_type, "image/", 6)) { + if (imgLoader::SupportImageWithMimeType( + nsDependentCString(content_type), + AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) + clazz = (MimeObjectClass*)&mimeInlineImageClass; + else + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + } +#ifdef ENABLE_SMIME + else if (!PL_strcasecmp(content_type, APPLICATION_XPKCS7_MIME) || + !PL_strcasecmp(content_type, APPLICATION_PKCS7_MIME)) { + + if (opts->is_child) { + // We do not allow encrypted parts except as top level. + // Allowing them would leak the plain text in case the part is + // cleverly hidden and the decrypted content gets included in + // replies and forwards. + clazz = (MimeObjectClass*)&mimeSuppressedCryptoClass; + } else { + char* ct = + hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false) + : nullptr; + char* st = + ct ? MimeHeaders_get_parameter(ct, "smime-type", nullptr, nullptr) + : nullptr; + + /* by default, assume that it is an encrypted message */ + clazz = (MimeObjectClass*)&mimeEncryptedCMSClass; + + /* if the smime-type parameter says that it's a certs-only or + compressed file, then show it as an attachment, however + (MimeEncryptedCMS doesn't handle these correctly) */ + if (st && (!PL_strcasecmp(st, "certs-only") || + !PL_strcasecmp(st, "compressed-data"))) + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + else { + /* look at the file extension... less reliable, but still covered + by the S/MIME specification (RFC 3851, section 3.2.1) */ + char* name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr); + if (name) { + char* suf = PL_strrchr(name, '.'); + bool p7mExternal = false; + + if (prefBranch) + prefBranch->GetBoolPref("mailnews.p7m_external", &p7mExternal); + if (suf && + ((!PL_strcasecmp(suf, ".p7m") && p7mExternal) || + !PL_strcasecmp(suf, ".p7c") || !PL_strcasecmp(suf, ".p7z"))) + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + } + PR_Free(name); + } + PR_Free(st); + PR_Free(ct); + } + } +#endif + /* A few types which occur in the real world and which we would otherwise + treat as non-text types (which would be bad) without this special-case... + */ + else if (!PL_strcasecmp(content_type, APPLICATION_PGP) || + !PL_strcasecmp(content_type, APPLICATION_PGP2)) + clazz = (MimeObjectClass*)&mimeInlineTextPlainClass; + + else if (!PL_strcasecmp(content_type, SUN_ATTACHMENT)) + clazz = (MimeObjectClass*)&mimeSunAttachmentClass; + + /* Everything else gets represented as a clickable link. + */ + else if (!exact_match_p) + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + + if (!mime_is_allowed_class(clazz, types_of_classes_to_disallow)) { + /* Do that check here (not after the if block), because we want to allow + user-installed plugins. */ + if (!exact_match_p) + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + else + clazz = 0; + } + } + +#ifdef ENABLE_SMIME + // see bug #189988 + if (opts && opts->format_out == nsMimeOutput::nsMimeMessageDecrypt && + (clazz != (MimeObjectClass*)&mimeEncryptedCMSClass)) { + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + } +#endif + + if (!exact_match_p) + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz) return 0; + + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (clazz && !clazz->class_initialized) { + int status = mime_classinit(clazz); + if (status < 0) return 0; + } + + return clazz; +} + +MimeObject* mime_create(const char* content_type, MimeHeaders* hdrs, + MimeDisplayOptions* opts, + bool forceInline /* = false */) { + /* If there is no Content-Disposition header, or if the Content-Disposition + is ``inline'', then we display the part inline (and let mime_find_class() + decide how.) + + If there is any other Content-Disposition (either ``attachment'' or some + disposition that we don't recognise) then we always display the part as + an external link, by using MimeExternalObject to display it. + + But Content-Disposition is ignored for all containers except `message'. + (including multipart/mixed, and multipart/digest.) It's not clear if + this is to spec, but from a usability standpoint, I think it's necessary. + */ + + MimeObjectClass* clazz = 0; + char* content_disposition = 0; + MimeObject* obj = 0; + char* override_content_type = 0; + + /* We've had issues where the incoming content_type is invalid, of a format: + content_type="=?windows-1252?q?application/pdf" (bug 659355) + We decided to fix that by simply trimming the stuff before the ? + */ + if (content_type) { + const char* lastQuestion = strrchr(content_type, '?'); + if (lastQuestion) + content_type = lastQuestion + 1; // the substring after the last '?' + } + + /* There are some clients send out all attachments with a content-type + of application/octet-stream. So, if we have an octet-stream attachment, + try to guess what type it really is based on the file extension. I HATE + that we have to do this... + */ + if (hdrs && opts && opts->file_type_fn && + + /* ### mwelch - don't override AppleSingle */ + (content_type ? PL_strcasecmp(content_type, APPLICATION_APPLEFILE) + : true) && + /* ## davidm Apple double shouldn't use this #$%& either. */ + (content_type ? PL_strcasecmp(content_type, MULTIPART_APPLEDOUBLE) + : true) && + (!content_type || + !PL_strcasecmp(content_type, APPLICATION_OCTET_STREAM) || + !PL_strcasecmp(content_type, UNKNOWN_CONTENT_TYPE))) { + char* name = MimeHeaders_get_name(hdrs, opts); + if (name) { + override_content_type = opts->file_type_fn(name, opts->stream_closure); + // appledouble isn't a valid override content type, and makes + // attachments invisible. + if (!PL_strcasecmp(override_content_type, MULTIPART_APPLEDOUBLE)) + override_content_type = nullptr; + PR_FREEIF(name); + + // Workaround for saving '.eml" file encoded with base64. + // Do not override with message/rfc822 whenever Transfer-Encoding is + // base64 since base64 encoding of message/rfc822 is invalid. + // Our MimeMessageClass has no capability to decode it. + if (!PL_strcasecmp(override_content_type, MESSAGE_RFC822)) { + nsCString encoding; + encoding.Adopt(MimeHeaders_get(hdrs, HEADER_CONTENT_TRANSFER_ENCODING, + true, false)); + if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + override_content_type = nullptr; + } + + // If we get here and it is not the unknown content type from the + // file name, let's do some better checking not to inline something bad + if (override_content_type && *override_content_type && + (PL_strcasecmp(override_content_type, UNKNOWN_CONTENT_TYPE))) + content_type = override_content_type; + } + } + + clazz = mime_find_class(content_type, hdrs, opts, false); + + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz) goto FAIL; + + if (opts && opts->part_to_load) + /* Always ignore Content-Disposition when we're loading some specific + sub-part (which may be within some container that we wouldn't otherwise + descend into, if the container itself had a Content-Disposition of + `attachment'. */ + content_disposition = 0; + + else if (mime_subclass_p(clazz, (MimeObjectClass*)&mimeContainerClass) && + !mime_subclass_p(clazz, (MimeObjectClass*)&mimeMessageClass)) + /* Ignore Content-Disposition on all containers except `message'. + That is, Content-Disposition is ignored for multipart/mixed objects, + but is obeyed for message/rfc822 objects. */ + content_disposition = 0; + + else { + /* Check to see if the plugin should override the content disposition + to make it appear inline. One example is a vcard which has a content + disposition of an "attachment;" */ + if (force_inline_display(content_type)) + NS_MsgSACopy(&content_disposition, "inline"); + else + content_disposition = + hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION, true, false) + : 0; + } + + if (!content_disposition || !PL_strcasecmp(content_disposition, "inline")) + ; /* Use the class we've got. */ + else { + // override messages that have content disposition set to "attachment" + // even though we probably should show them inline. + if ((clazz != (MimeObjectClass*)&mimeMessageClass) && + (clazz != (MimeObjectClass*)&mimeInlineImageClass) && + (!opts->show_attachment_inline_text || + ((clazz != (MimeObjectClass*)&mimeInlineTextHTMLClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextHTMLParsedClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextHTMLSanitizedClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextHTMLAsPlaintextClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextRichtextClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextEnrichedClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextPlainClass) && + (clazz != (MimeObjectClass*)&mimeInlineTextPlainFlowedClass)))) { + // not a special inline type, so show as attachment + // However, mimeSuppressedCryptoClass is treated identically as + // mimeExternalObjectClass, let's not lose that type information. + if (clazz != (MimeObjectClass*)&mimeSuppressedCryptoClass) { + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + } + } + } + + /* If the option `Show Attachments Inline' is off, now would be the time to + * change our mind... */ + /* Also, if we're doing a reply (i.e. quoting the body), then treat that + * according to preference. */ + if (opts && + ((!opts->show_attachment_inline_p && !forceInline) || + (!opts->quote_attachment_inline_p && + (opts->format_out == nsMimeOutput::nsMimeMessageQuoting || + opts->format_out == nsMimeOutput::nsMimeMessageBodyQuoting)))) { + if (mime_subclass_p(clazz, (MimeObjectClass*)&mimeInlineTextClass)) { + /* It's a text type. Write it only if it's the *first* part + that we're writing, and then only if it has no "filename" + specified (the assumption here being, if it has a filename, + it wasn't simply typed into the text field -- it was actually + an attached document.) */ + if (opts->state && opts->state->first_part_written_p) + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + else { + /* If there's a name, then write this as an attachment. */ + char* name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr); + if (name) { + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + PR_Free(name); + } + } + } else if (mime_subclass_p(clazz, (MimeObjectClass*)&mimeContainerClass) && + !mime_subclass_p(clazz, (MimeObjectClass*)&mimeMessageClass)) + /* Multipart subtypes are ok, except for messages; descend into + multiparts, and defer judgement. + + Encrypted blobs are just like other containers (make the crypto + layer invisible, and treat them as simple containers. So there's + no easy way to save encrypted data directly to disk; it will tend + to always be wrapped inside a message/rfc822. That's ok.) */ + ; + else if (opts && opts->part_to_load && + mime_subclass_p(clazz, (MimeObjectClass*)&mimeMessageClass)) + /* Descend into messages only if we're looking for a specific sub-part. */ + ; + else { + /* Anything else, and display it as a link (and cause subsequent + text parts to also be displayed as links.) */ + clazz = (MimeObjectClass*)&mimeExternalObjectClass; + } + } + + PR_FREEIF(content_disposition); + obj = mime_new(clazz, hdrs, content_type); + +FAIL: + + /* If we decided to ignore the content-type in the headers of this object + (see above) then make sure that our new content-type is stored in the + object itself. (Or free it, if we're in an out-of-memory situation.) + */ + if (override_content_type) { + if (obj) { + PR_FREEIF(obj->content_type); + obj->content_type = override_content_type; + } else { + PR_Free(override_content_type); + } + } + + return obj; +} + +static int mime_classinit_1(MimeObjectClass* clazz, MimeObjectClass* target); + +static int mime_classinit(MimeObjectClass* clazz) { + int status; + if (clazz->class_initialized) return 0; + + NS_ASSERTION(clazz->class_initialize, + "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz->class_initialize) return -1; + + /* First initialize the superclass. + */ + if (clazz->superclass && !clazz->superclass->class_initialized) { + status = mime_classinit(clazz->superclass); + if (status < 0) return status; + } + + /* Now run each of the superclass-init procedures in turn, + parentmost-first. */ + status = mime_classinit_1(clazz, clazz); + if (status < 0) return status; + + /* Now we're done. */ + clazz->class_initialized = true; + return 0; +} + +static int mime_classinit_1(MimeObjectClass* clazz, MimeObjectClass* target) { + int status; + if (clazz->superclass) { + status = mime_classinit_1(clazz->superclass, target); + if (status < 0) return status; + } + return clazz->class_initialize(target); +} + +bool mime_subclass_p(MimeObjectClass* child, MimeObjectClass* parent) { + if (child == parent) return true; + if (!child->superclass) return false; + return mime_subclass_p(child->superclass, parent); +} + +bool mime_typep(MimeObject* obj, MimeObjectClass* clazz) { + return mime_subclass_p(obj->clazz, clazz); +} + +/* URL munging + */ + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +char* mime_part_address(MimeObject* obj) { + if (!obj->parent) return strdup("0"); + + /* Find this object in its parent. */ + int32_t i, j = -1; + char buf[20]; + char* higher = 0; + MimeContainer* cont = (MimeContainer*)obj->parent; + NS_ASSERTION(mime_typep(obj->parent, (MimeObjectClass*)&mimeContainerClass), + "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + for (i = 0; i < cont->nchildren; i++) + if (cont->children[i] == obj) { + j = i + 1; + break; + } + if (j == -1) { + NS_ERROR("No children under MeimContainer"); + return 0; + } + + PR_snprintf(buf, sizeof(buf), "%ld", j); + if (obj->parent->parent) { + higher = mime_part_address(obj->parent); + if (!higher) return 0; /* MIME_OUT_OF_MEMORY */ + } + + if (!higher) return strdup(buf); + + uint32_t slen = strlen(higher) + strlen(buf) + 3; + char* s = (char*)PR_MALLOC(slen); + if (!s) { + PR_Free(higher); + return 0; /* MIME_OUT_OF_MEMORY */ + } + PL_strncpyz(s, higher, slen); + PL_strcatn(s, slen, "."); + PL_strcatn(s, slen, buf); + PR_Free(higher); + return s; +} + +/* Returns a string describing the location of the *IMAP* part (like "2.5.3"). + This is not a full URL, just a part-number. + This part is explicitly passed in the X-Mozilla-IMAP-Part header. + Return value must be freed by the caller. + */ +char* mime_imap_part_address(MimeObject* obj) { + if (!obj || !obj->headers) return 0; + return MimeHeaders_get(obj->headers, IMAP_EXTERNAL_CONTENT_HEADER, false, + false); +} + +/* Returns a full URL if the current mime object has a + EXTERNAL_ATTACHMENT_URL_HEADER header. Return value must be freed by the + caller. +*/ +char* mime_external_attachment_url(MimeObject* obj) { + if (!obj || !obj->headers) return 0; + return MimeHeaders_get(obj->headers, EXTERNAL_ATTACHMENT_URL_HEADER, false, + false); +} + +#ifdef ENABLE_SMIME +/* Asks whether the given object is one of the cryptographically signed + or encrypted objects that we know about. (MimeMessageClass uses this + to decide if the headers need to be presented differently.) + */ +bool mime_crypto_object_p(MimeHeaders* hdrs, bool clearsigned_counts, + MimeDisplayOptions* opts) { + char* ct; + MimeObjectClass* clazz; + + if (!hdrs) return false; + + ct = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, true, false); + if (!ct) return false; + + /* Rough cut -- look at the string before doing a more complex comparison. */ + if (PL_strcasecmp(ct, MULTIPART_SIGNED) && + PL_strncasecmp(ct, "application/", 12)) { + PR_Free(ct); + return false; + } + + /* It's a candidate for being a crypto object. Let's find out for sure... */ + clazz = mime_find_class(ct, hdrs, opts, true); + PR_Free(ct); + + if (clazz == ((MimeObjectClass*)&mimeEncryptedCMSClass)) return true; + + if (clearsigned_counts && + clazz == ((MimeObjectClass*)&mimeMultipartSignedCMSClass)) + return true; + + return false; +} + +#endif // ENABLE_SMIME + +/* Puts a part-number into a URL. If append_p is true, then the part number + is appended to any existing part-number already in that URL; otherwise, + it replaces it. + */ +char* mime_set_url_part(const char* url, const char* part, bool append_p) { + const char* part_begin = 0; + const char* part_end = 0; + bool got_q = false; + const char* s; + char* result; + + if (!url || !part) return 0; + + nsAutoCString urlString(url); + int32_t typeIndex = urlString.Find("?type=application/x-message-display"); + if (typeIndex != -1) { + urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1); + if (urlString.CharAt(typeIndex) == '&') + urlString.Replace(typeIndex, 1, '?'); + url = urlString.get(); + } + + for (s = url; *s; s++) { + if (*s == '?') { + got_q = true; + if (!PL_strncasecmp(s, "?part=", 6)) part_begin = (s += 6); + } else if (got_q && *s == '&' && !PL_strncasecmp(s, "&part=", 6)) + part_begin = (s += 6); + + if (part_begin) { + while (*s && *s != '?' && *s != '&') s++; + part_end = s; + break; + } + } + + uint32_t resultlen = strlen(url) + strlen(part) + 10; + result = (char*)PR_MALLOC(resultlen); + if (!result) return 0; + + if (part_begin) { + if (append_p) { + memcpy(result, url, part_end - url); + result[part_end - url] = '.'; + result[part_end - url + 1] = 0; + } else { + memcpy(result, url, part_begin - url); + result[part_begin - url] = 0; + } + } else { + PL_strncpyz(result, url, resultlen); + if (got_q) + PL_strcatn(result, resultlen, "&part="); + else + PL_strcatn(result, resultlen, "?part="); + } + + PL_strcatn(result, resultlen, part); + + if (part_end && *part_end) PL_strcatn(result, resultlen, part_end); + + /* Semi-broken kludge to omit a trailing "?part=0". */ + { + int L = strlen(result); + if (L > 6 && (result[L - 7] == '?' || result[L - 7] == '&') && + !strcmp("part=0", result + L - 6)) + result[L - 7] = 0; + } + + return result; +} + +/* Puts an *IMAP* part-number into a URL. + Strips off any previous *IMAP* part numbers, since they are absolute, not + relative. + */ +char* mime_set_url_imap_part(const char* url, const char* imappart, + const char* libmimepart) { + char* result = 0; + char* whereCurrent = PL_strstr(url, "/;section="); + if (whereCurrent) { + *whereCurrent = 0; + } + + uint32_t resultLen = + strlen(url) + strlen(imappart) + strlen(libmimepart) + 17; + result = (char*)PR_MALLOC(resultLen); + if (!result) return 0; + + PL_strncpyz(result, url, resultLen); + PL_strcatn(result, resultLen, "/;section="); + PL_strcatn(result, resultLen, imappart); + PL_strcatn(result, resultLen, "?part="); + PL_strcatn(result, resultLen, libmimepart); + + if (whereCurrent) *whereCurrent = '/'; + + return result; +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches, and returns the MimeObject (else NULL.) + (part is not a URL -- it's of the form "1.3.5".) + */ +MimeObject* mime_address_to_part(const char* part, MimeObject* obj) { + /* Note: this is an N^2 operation, but the number of parts in a message + shouldn't ever be large enough that this really matters... */ + + bool match; + + if (!part || !*part) { + match = !obj->parent; + } else { + char* part2 = mime_part_address(obj); + if (!part2) return 0; /* MIME_OUT_OF_MEMORY */ + match = !strcmp(part, part2); + PR_Free(part2); + } + + if (match) { + /* These are the droids we're looking for. */ + return obj; + } else if (!mime_typep(obj, (MimeObjectClass*)&mimeContainerClass)) { + /* Not a container, pull up, pull up! */ + return 0; + } else { + int32_t i; + MimeContainer* cont = (MimeContainer*)obj; + for (i = 0; i < cont->nchildren; i++) { + MimeObject* o2 = mime_address_to_part(part, cont->children[i]); + if (o2) return o2; + } + return 0; + } +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +char* mime_find_content_type_of_part(const char* part, MimeObject* obj) { + char* result = 0; + + obj = mime_address_to_part(part, obj); + if (!obj) return 0; + + result = (obj->headers ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, + true, false) + : 0); + + return result; +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +char* mime_find_suggested_name_of_part(const char* part, MimeObject* obj) { + char* result = 0; + + obj = mime_address_to_part(part, obj); + if (!obj) return 0; + + result = + (obj->headers ? MimeHeaders_get_name(obj->headers, obj->options) : 0); + + /* If this part doesn't have a name, but this part is one fork of an + AppleDouble, and the AppleDouble itself has a name, then use that. */ + if (!result && obj->parent && obj->parent->headers && + mime_typep(obj->parent, (MimeObjectClass*)&mimeMultipartAppleDoubleClass)) + result = MimeHeaders_get_name(obj->parent->headers, obj->options); + + /* Else, if this part is itself an AppleDouble, and one of its children + has a name, then use that (check data fork first, then resource.) */ + if (!result && + mime_typep(obj, (MimeObjectClass*)&mimeMultipartAppleDoubleClass)) { + MimeContainer* cont = (MimeContainer*)obj; + if (cont->nchildren > 1 && cont->children[1] && cont->children[1]->headers) + result = MimeHeaders_get_name(cont->children[1]->headers, obj->options); + + if (!result && cont->nchildren > 0 && cont->children[0] && + cont->children[0]->headers) + result = MimeHeaders_get_name(cont->children[0]->headers, obj->options); + } + + /* Ok, now we have the suggested name, if any. + Now we remove any extensions that correspond to the + Content-Transfer-Encoding. For example, if we see the headers + + Content-Type: text/plain + Content-Disposition: inline; filename=foo.text.uue + Content-Transfer-Encoding: x-uuencode + + then we would look up (in mime.types) the file extensions which are + associated with the x-uuencode encoding, find that "uue" is one of + them, and remove that from the end of the file name, thus returning + "foo.text" as the name. This is because, by the time this file ends + up on disk, its content-transfer-encoding will have been removed; + therefore, we should suggest a file name that indicates that. + */ + if (result && obj->encoding && *obj->encoding) { + int32_t L = strlen(result); + const char** exts = 0; + + /* + I'd like to ask the mime.types file, "what extensions correspond + to obj->encoding (which happens to be "x-uuencode") but doing that + in a non-sphagetti way would require brain surgery. So, since + currently uuencode is the only content-transfer-encoding which we + understand which traditionally has an extension, we just special- + case it here! Icepicks in my forehead! + + Note that it's special-cased in a similar way in libmsg/compose.c. + */ + if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE)) { + static const char* uue_exts[] = {"uu", "uue", 0}; + exts = uue_exts; + } + + while (exts && *exts) { + const char* ext = *exts; + int32_t L2 = strlen(ext); + if (L > L2 + 1 && /* long enough */ + result[L - L2 - 1] == '.' && /* '.' in right place*/ + !PL_strcasecmp(ext, result + (L - L2))) /* ext matches */ + { + result[L - L2 - 1] = 0; /* truncate at '.' and stop. */ + break; + } + exts++; + } + } + + return result; +} + +/* Parse the various "?" options off the URL and into the options struct. + */ +int mime_parse_url_options(const char* url, MimeDisplayOptions* options) { + const char* q; + + if (!url || !*url) return 0; + if (!options) return 0; + + MimeHeadersState default_headers = options->headers; + + q = PL_strrchr(url, '?'); + if (!q) return 0; + q++; + while (*q) { + const char *end, *value, *name_end; + end = q; + while (*end && *end != '&') end++; + value = q; + while (*value != '=' && value < end) value++; + name_end = value; + if (value < end) value++; + if (name_end <= q) + ; + else if (!PL_strncasecmp("headers", q, name_end - q)) { + if (end > value && !PL_strncasecmp("only", value, end - value)) + options->headers = MimeHeadersOnly; + else if (end > value && !PL_strncasecmp("none", value, end - value)) + options->headers = MimeHeadersNone; + else if (end > value && !PL_strncasecmp("all", value, end - value)) + options->headers = MimeHeadersAll; + else if (end > value && !PL_strncasecmp("some", value, end - value)) + options->headers = MimeHeadersSome; + else if (end > value && !PL_strncasecmp("micro", value, end - value)) + options->headers = MimeHeadersMicro; + else if (end > value && !PL_strncasecmp("cite", value, end - value)) + options->headers = MimeHeadersCitation; + else if (end > value && !PL_strncasecmp("citation", value, end - value)) + options->headers = MimeHeadersCitation; + else + options->headers = default_headers; + } else if (!PL_strncasecmp("part", q, name_end - q) && + options->format_out != nsMimeOutput::nsMimeMessageBodyQuoting) { + PR_FREEIF(options->part_to_load); + if (end > value) { + options->part_to_load = (char*)PR_MALLOC(end - value + 1); + if (!options->part_to_load) return MIME_OUT_OF_MEMORY; + memcpy(options->part_to_load, value, end - value); + options->part_to_load[end - value] = 0; + } + } else if (!PL_strncasecmp("rot13", q, name_end - q)) { + options->rot13_p = + end <= value || !PL_strncasecmp("true", value, end - value); + } else if (!PL_strncasecmp("emitter", q, name_end - q)) { + if ((end > value) && !PL_strncasecmp("js", value, end - value)) { + // the js emitter needs to hear about nested message bodies + // in order to build a proper representation. + options->notify_nested_bodies = true; + // show_attachment_inline_p has the side-effect of letting the + // emitter see all parts of a multipart/alternative, which it + // really appreciates. + options->show_attachment_inline_p = true; + // however, show_attachment_inline_p also results in a few + // subclasses writing junk into the body for display purposes. + // put a stop to these shenanigans by enabling write_pure_bodies. + // current offenders are: + // - MimeInlineImage + options->write_pure_bodies = true; + // we don't actually care about the data in the attachments, just the + // metadata (i.e. size) + options->metadata_only = true; + } + } + + q = end; + if (*q) q++; + } + + /* Compatibility with the "?part=" syntax used in the old (Mozilla 2.0) + MIME parser. + + Basically, the problem is that the old part-numbering code was totally + busted: here's a comparison of the old and new numberings with a pair + of hypothetical messages (one with a single part, and one with nested + containers). + NEW: OLD: OR: + message/rfc822 + image/jpeg 1 0 0 + + message/rfc822 + multipart/mixed 1 0 0 + text/plain 1.1 1 1 + image/jpeg 1.2 2 2 + message/rfc822 1.3 - 3 + text/plain 1.3.1 3 - + message/rfc822 1.4 - 4 + multipart/mixed 1.4.1 4 - + text/plain 1.4.1.1 4.1 - + image/jpeg 1.4.1.2 4.2 - + text/plain 1.5 5 5 + + The "NEW" column is how the current code counts. The "OLD" column is + what "?part=" references would do in 3.0b4 and earlier; you'll see that + you couldn't directly refer to the child message/rfc822 objects at all! + But that's when it got really weird, because if you turned on + "Attachments As Links" (or used a URL like "?inline=false&part=...") + then you got a totally different numbering system (seen in the "OR" + column.) Gag! + + So, the problem is, ClariNet had been using these part numbers in their + HTML news feeds, as a sleazy way of transmitting both complex HTML layouts + and images using NNTP as transport, without invoking HTTP. + + The following clause is to provide some small amount of backward + compatibility. By looking at that table, one can see that in the new + model, "part=0" has no meaning, and neither does "part=2" or "part=3" + and so on. + + "part=1" is ambiguous between the old and new way, as is any part + specification that has a "." in it. + + So, the compatibility hack we do here is: if the part is "0", then map + that to "1". And if the part is >= "2", then prepend "1." to it (so that + we map "2" to "1.2", and "3" to "1.3".) + + This leaves the URLs compatible in the cases of: + + = single part messages + = references to elements of a top-level multipart except the first + + and leaves them incompatible for: + + = the first part of a top-level multipart + = all elements deeper than the outermost part + + Life s#$%s when you don't properly think out things that end up turning + into de-facto standards... + */ + + if (options->part_to_load && + !PL_strchr(options->part_to_load, '.')) /* doesn't contain a dot */ + { + if (!strcmp(options->part_to_load, "0")) /* 0 */ + { + PR_Free(options->part_to_load); + options->part_to_load = strdup("1"); + if (!options->part_to_load) return MIME_OUT_OF_MEMORY; + } else if (strcmp(options->part_to_load, "1")) /* not 1 */ + { + const char* prefix = "1."; + uint32_t slen = strlen(options->part_to_load) + strlen(prefix) + 1; + char* s = (char*)PR_MALLOC(slen); + if (!s) return MIME_OUT_OF_MEMORY; + PL_strncpyz(s, prefix, slen); + PL_strcatn(s, slen, options->part_to_load); + PR_Free(options->part_to_load); + options->part_to_load = s; + } + } + + return 0; +} + +/* Some output-generation utility functions... + */ + +int MimeOptions_write(MimeHeaders* hdrs, MimeDisplayOptions* opt, + const char* data, int32_t length, bool user_visible_p) { + int status = 0; + void* closure = 0; + if (!opt || !opt->output_fn || !opt->state) return 0; + + closure = opt->output_closure; + if (!closure) closure = opt->stream_closure; + + // PR_ASSERT(opt->state->first_data_written_p); + + if (opt->state->separator_queued_p && user_visible_p) { + opt->state->separator_queued_p = false; + if (opt->state->separator_suppressed_p) + opt->state->separator_suppressed_p = false; + else { + const char* sep = "<BR><FIELDSET CLASS=\"moz-mime-attachment-header\">"; + int lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString name; + name.Adopt(MimeHeaders_get_name(hdrs, opt)); + MimeHeaders_convert_header_value(opt, name, false); + + if (!name.IsEmpty()) { + sep = "<LEGEND CLASS=\"moz-mime-attachment-header-name\">"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString escapedName; + nsAppendEscapedHTML(name, escapedName); + + lstatus = + opt->output_fn(escapedName.get(), escapedName.Length(), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + sep = "</LEGEND>"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + } + + sep = "</FIELDSET>"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + } + } + if (user_visible_p) opt->state->separator_suppressed_p = false; + + if (length > 0) { + status = opt->output_fn(data, length, closure); + if (status < 0) return status; + } + + return 0; +} + +int MimeObject_write(MimeObject* obj, const char* output, int32_t length, + bool user_visible_p) { + if (!obj->output_p) return 0; + + // if we're stripping attachments, check if any parent is not being output + if (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) { + // if true, mime generates a separator in html - we don't want that. + user_visible_p = false; + + for (MimeObject* parent = obj->parent; parent; parent = parent->parent) { + if (!parent->output_p) return 0; + } + } + if (obj->options && !obj->options->state->first_data_written_p) { + int status = MimeObject_output_init(obj, 0); + if (status < 0) return status; + NS_ASSERTION(obj->options->state->first_data_written_p, + "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + + return MimeOptions_write(obj->headers, obj->options, output, length, + user_visible_p); +} + +int MimeObject_write_separator(MimeObject* obj) { + if (obj->options && obj->options->state && + // we never want separators if we are asking for pure bodies + !obj->options->write_pure_bodies) + obj->options->state->separator_queued_p = true; + return 0; +} + +int MimeObject_output_init(MimeObject* obj, const char* content_type) { + if (obj && obj->options && obj->options->state && + !obj->options->state->first_data_written_p) { + int status; + const char* charset = 0; + char *name = 0, *x_mac_type = 0, *x_mac_creator = 0; + + if (!obj->options->output_init_fn) { + obj->options->state->first_data_written_p = true; + return 0; + } + + if (obj->headers) { + char* ct; + name = MimeHeaders_get_name(obj->headers, obj->options); + + ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, false, false); + if (ct) { + x_mac_type = + MimeHeaders_get_parameter(ct, PARAM_X_MAC_TYPE, nullptr, nullptr); + x_mac_creator = MimeHeaders_get_parameter(ct, PARAM_X_MAC_CREATOR, + nullptr, nullptr); + /* if don't have a x_mac_type and x_mac_creator, we need to try to get + * it from its parent */ + if (!x_mac_type && !x_mac_creator && obj->parent && + obj->parent->headers) { + char* ctp = MimeHeaders_get(obj->parent->headers, HEADER_CONTENT_TYPE, + false, false); + if (ctp) { + x_mac_type = MimeHeaders_get_parameter(ctp, PARAM_X_MAC_TYPE, + nullptr, nullptr); + x_mac_creator = MimeHeaders_get_parameter(ctp, PARAM_X_MAC_CREATOR, + nullptr, nullptr); + PR_Free(ctp); + } + } + + if (!(obj->options->override_charset)) { + char* charset = + MimeHeaders_get_parameter(ct, "charset", nullptr, nullptr); + if (charset) { + PR_FREEIF(obj->options->default_charset); + obj->options->default_charset = charset; + } + } + PR_Free(ct); + } + } + + if (mime_typep(obj, (MimeObjectClass*)&mimeInlineTextClass)) + charset = ((MimeInlineText*)obj)->charset; + + if (!content_type) content_type = obj->content_type; + if (!content_type) content_type = TEXT_PLAIN; + + // + // Set the charset on the channel we are dealing with so people know + // what the charset is set to. Do this for quoting/Printing ONLY! + // + extern void ResetChannelCharset(MimeObject * obj); + if ((obj->options) && + (obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs || + obj->options->format_out == nsMimeOutput::nsMimeMessagePrintOutput)) + ResetChannelCharset(obj); + + status = obj->options->output_init_fn(content_type, charset, name, + x_mac_type, x_mac_creator, + obj->options->stream_closure); + PR_FREEIF(name); + PR_FREEIF(x_mac_type); + PR_FREEIF(x_mac_creator); + obj->options->state->first_data_written_p = true; + return status; + } + return 0; +} + +char* mime_get_base_url(const char* url) { + if (!url) return nullptr; + + const char* s = strrchr(url, '?'); + if (s && !strncmp(s, "?type=application/x-message-display", + sizeof("?type=application/x-message-display") - 1)) { + const char* nextTerm = strchr(s, '&'); + // strlen(s) cannot be zero, because it matches the above text + s = nextTerm ? nextTerm : s + strlen(s) - 1; + } + // we need to keep the ?number part of the url, or we won't know + // which local message the part belongs to. + if (s && *s && *(s + 1) && + !strncmp(s + 1, "number=", sizeof("number=") - 1)) { + const char* nextTerm = strchr(++s, '&'); + s = nextTerm ? nextTerm : s + strlen(s) - 1; + } + char* result = (char*)PR_MALLOC(strlen(url) + 1); + NS_ASSERTION(result, "out of memory"); + if (!result) return nullptr; + + memcpy(result, url, s - url); + result[s - url] = 0; + return result; +} diff --git a/comm/mailnews/mime/src/mimei.h b/comm/mailnews/mime/src/mimei.h new file mode 100644 index 0000000000..d3a0684224 --- /dev/null +++ b/comm/mailnews/mime/src/mimei.h @@ -0,0 +1,405 @@ +/* -*- 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: + This 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: + This 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 knowledge 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; + +/* (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/comm/mailnews/mime/src/mimeiimg.cpp b/comm/mailnews/mime/src/mimeiimg.cpp new file mode 100644 index 0000000000..f2ac8a0e09 --- /dev/null +++ b/comm/mailnews/mime/src/mimeiimg.cpp @@ -0,0 +1,220 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimeiimg.h b/comm/mailnews/mime/src/mimeiimg.h new file mode 100644 index 0000000000..b95400b967 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimeleaf.cpp b/comm/mailnews/mime/src/mimeleaf.cpp new file mode 100644 index 0000000000..24d8c8989f --- /dev/null +++ b/comm/mailnews/mime/src/mimeleaf.cpp @@ -0,0 +1,187 @@ +/* -*- 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 "modmimee.h" +#include "mimeleaf.h" +#include "nsMimeTypes.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeStringResources.h" +#include "modmimee.h" // for MimeConverterOutputCallback + +#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 attachment parts if headers are also written + // via the parent, so that the header matches the encoding. + (obj->options->format_out == nsMimeOutput::nsMimeMessageRaw && + obj->parent && obj->parent->output_p)) + /* 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/comm/mailnews/mime/src/mimeleaf.h b/comm/mailnews/mime/src/mimeleaf.h new file mode 100644 index 0000000000..cca651e3d0 --- /dev/null +++ b/comm/mailnews/mime/src/mimeleaf.h @@ -0,0 +1,60 @@ +/* -*- 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/comm/mailnews/mime/src/mimemalt.cpp b/comm/mailnews/mime/src/mimemalt.cpp new file mode 100644 index 0000000000..82e56ee6e8 --- /dev/null +++ b/comm/mailnews/mime/src/mimemalt.cpp @@ -0,0 +1,549 @@ +/* -*- 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/. */ + +/* + 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 immediately, 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 achieve 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 chosen.) + + - 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 +#include "modmimee.h" // for MimeConverterOutputCallback + +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) { + for (int32_t i = 0; i < malt->pending_parts; i++) { + MimeHeaders* hdrs = malt->buffered_hdrs[i]; + char* ct = + (hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, true, false) : 0); + bool display_part = + (i == 0) || (ct && !PL_strncasecmp(ct, "text/calendar", 13)); + MimeMultipartAlternative_display_cached_part(obj, malt->buffered_hdrs[i], + malt->part_buffers[i], + do_display && display_part); + 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) { + if (obj->options) obj->options->is_child = true; + + 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 pending_parts = malt->pending_parts; + int32_t max_parts = malt->max_parts; + + int32_t i = pending_parts++; + + if (i == 0) { + malt->buffered_priority = priority; + } + + if (pending_parts > max_parts) { + max_parts = pending_parts; + MimeHeaders** newBuf = (MimeHeaders**)PR_REALLOC( + malt->buffered_hdrs, 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, 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); + + malt->pending_parts = pending_parts; + malt->max_parts = max_parts; + 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; + } + + /* 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, "rtf", 3)) { + return PRIORITY_HIGH; + } + + if (!PL_strncasecmp(text_type, "calendar", 8)) { + // Prioritise text/calendar below text/html, etc. since we always show + // it anyway. + 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/comm/mailnews/mime/src/mimemalt.h b/comm/mailnews/mime/src/mimemalt.h new file mode 100644 index 0000000000..622704d8f4 --- /dev/null +++ b/comm/mailnews/mime/src/mimemalt.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 _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/comm/mailnews/mime/src/mimemapl.cpp b/comm/mailnews/mime/src/mimemapl.cpp new file mode 100644 index 0000000000..4fca1defe1 --- /dev/null +++ b/comm/mailnews/mime/src/mimemapl.cpp @@ -0,0 +1,173 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimemapl.h b/comm/mailnews/mime/src/mimemapl.h new file mode 100644 index 0000000000..28d29bf5f7 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimemcms.cpp b/comm/mailnews/mime/src/mimemcms.cpp new file mode 100644 index 0000000000..d2e5b18f19 --- /dev/null +++ b/comm/mailnews/mime/src/mimemcms.cpp @@ -0,0 +1,501 @@ +/* -*- 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 "nsIMsgSMIMEHeaderSink.h" +#include "nsCOMPtr.h" +#include "nsIX509Cert.h" +#include "plstr.h" +#include "nsComponentManagerUtils.h" +#include "nsIMailChannel.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; + bool reject_signature; + 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), + reject_signature(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, nsCString& msg_date); +extern void MimeCMSRequestAsyncSignatureVerification( + nsICMSMessage* aCMSMsg, const char* aFromAddr, const char* aFromName, + const char* aSenderAddr, const char* aSenderName, const char* aMsgDate, + nsIMsgSMIMEHeaderSink* aHeaderSink, int32_t aMimeNestingLevel, + const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber, + const nsTArray<uint8_t>& aDigestData, int16_t aDigestType); +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; + 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")) { + nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(channel); + if (mailChannel) { + mailChannel->GetSmimeHeaderSink( + getter_AddRefs(data->smimeHeaderSink)); + } + } + } + } // 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? + // We skip signature verfication, and return bad signature status. + + // Note: We must return a valid pointer to ourselve's data, + // otherwise the parent will attempt to re-init us. + + data->reject_signature = true; + if (data->smimeHeaderSink) { + int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self); + nsAutoCString partnum; + partnum.Adopt(mime_part_address(data->self)); + data->smimeHeaderSink->SignedStatus(aRelativeNestLevel, + nsICMSMessageErrors::GENERAL_ERROR, + nullptr, data->url, partnum); + } + return data; + } + + 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? */ + } + + bool allowSha1 = mozilla::Preferences::GetBool( + "mail.smime.accept_insecure_sha1_message_signatures", false); + + if (allowSha1 && (!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 { + data->reject_signature = true; + if (data->smimeHeaderSink) { + int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self); + nsAutoCString partnum; + partnum.Adopt(mime_part_address(data->self)); + data->smimeHeaderSink->SignedStatus(aRelativeNestLevel, + nsICMSMessageErrors::GENERAL_ERROR, + nullptr, data->url, partnum); + } + PR_Free(micalg); + return data; + } + + PR_Free(micalg); + micalg = 0; + + 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) { + return -1; + } + + if (data->reject_signature) { + return 0; + } + + if (!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) { + return -1; + } + + if (data->reject_signature) { + return 0; + } + + if (!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 (data->reject_signature) { + return 0; + } + + 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) { + return -1; + } + + if (data->reject_signature) { + return 0; + } + + if (!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; + } + + if (data->reject_signature) { + return 0; + } + + /* 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) { + if (data->reject_signature) { + return; + } + + nsAutoCString partnum; + partnum.Adopt(mime_part_address(data->self)); + data->smimeHeaderSink->SignedStatus( + MIMEGetRelativeCryptoNestLevel(data->self), + nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url, partnum); + } +} + +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; + + if (aRelativeNestLevel >= 0) { + // maxWantedNesting 1: only want outermost nesting level + if (aRelativeNestLevel > 1) return nullptr; + } + + nsAutoCString partnum; + partnum.Adopt(mime_part_address(data->self)); + + 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, partnum); + } + 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; + nsCString msg_date; + + MimeCMSGetFromSender(data->self, from_addr, from_name, sender_addr, + sender_name, msg_date); + + nsTArray<uint8_t> digest; + digest.AppendElements(data->item_data, data->item_len); + + if (!data->reject_signature && data->smimeHeaderSink) { + MimeCMSRequestAsyncSignatureVerification( + data->content_info, from_addr.get(), from_name.get(), sender_addr.get(), + sender_name.get(), msg_date.get(), data->smimeHeaderSink, + aRelativeNestLevel, data->url, partnum, digest, 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/comm/mailnews/mime/src/mimemcms.h b/comm/mailnews/mime/src/mimemcms.h new file mode 100644 index 0000000000..e8bfc6aac0 --- /dev/null +++ b/comm/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; // for function arguments in mimemcms.c + +/* 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/comm/mailnews/mime/src/mimemdig.cpp b/comm/mailnews/mime/src/mimemdig.cpp new file mode 100644 index 0000000000..89500e3074 --- /dev/null +++ b/comm/mailnews/mime/src/mimemdig.cpp @@ -0,0 +1,22 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimemdig.h b/comm/mailnews/mime/src/mimemdig.h new file mode 100644 index 0000000000..1701004611 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimemmix.cpp b/comm/mailnews/mime/src/mimemmix.cpp new file mode 100644 index 0000000000..a4c6d22d6c --- /dev/null +++ b/comm/mailnews/mime/src/mimemmix.cpp @@ -0,0 +1,19 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimemmix.h b/comm/mailnews/mime/src/mimemmix.h new file mode 100644 index 0000000000..6d24939ff6 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimemoz2.cpp b/comm/mailnews/mime/src/mimemoz2.cpp new file mode 100644 index 0000000000..e5e4a6a644 --- /dev/null +++ b/comm/mailnews/mime/src/mimemoz2.cpp @@ -0,0 +1,1862 @@ +/* -*- 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 "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 "nsIStringBundle.h" +#include "nsString.h" +#include "nsMimeStringResources.h" +#include "nsStreamConverter.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 "nsMsgUtils.h" +#include "nsIChannel.h" +#include "nsIMailChannel.h" +#include "mimeebod.h" +#include "mimeeobj.h" +// <for functions="HTML2Plaintext,HTMLSantinize"> +#include "nsXPCOM.h" +#include "nsLayoutCID.h" +#include "nsIParserUtils.h" +// </for> +#include "mozilla/Components.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) {} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Attachment handling routines +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +MimeObject* mime_get_main_object(MimeObject* obj); + +// Appends a "filename" parameter with the attachment name to the object url. +void AppendFilenameParameterToAttachmentDataUrl( + const nsMsgAttachmentData* attachmentData, nsCString& url) { + url.AppendLiteral("&filename="); + nsAutoCString aResult; + if (NS_SUCCEEDED(MsgEscapeString(attachmentData->m_realName, + nsINetUtil::ESCAPE_XALPHAS, aResult))) { + url.Append(aResult); + } else { + url.Append(attachmentData->m_realName); + } + if (attachmentData->m_realType.EqualsLiteral("message/rfc822") && + !StringEndsWith(url, ".eml"_ns, nsCaseInsensitiveCStringComparator)) { + url.AppendLiteral(".eml"); + } +} + +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 believe 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, "text"_ns, + 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); + nsCString urlString(tmpURL); + if (!tmp->m_realName.IsEmpty()) { + AppendFilenameParameterToAttachmentDataUrl(tmp, urlString); + } + rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), urlString.get(), 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, "multipart"_ns, + 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, "http"_ns, + 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) { + AppendFilenameParameterToAttachmentDataUrl(tmp, urlString); + } else if (tmp->m_isExternalAttachment) { + // Allows the JS mime emitter to figure out the part information. + urlString.AppendLiteral("?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.AppendLiteral(".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. + *data = nullptr; + return rv; + } + } + rv = BuildAttachmentList((MimeObject*)cobj, *data, aMessageURL); + if (NS_FAILED(rv)) { + delete[] * data; // release data in case of error return. + *data = nullptr; + } + return rv; +} + +extern "C" void NotifyEmittersOfAttachmentList(MimeDisplayOptions* opt, + nsMsgAttachmentData* data) { + 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.EqualsLiteral("attachment") && + tmp->m_displayableInline && + (tmp->m_realName.IsEmpty() || + (!tmp->m_hasFilename && + (opt->html_as_p != 4 || opt->metadata_only)))) { + ++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); + ++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::components::IO::Service(); + 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) { + nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(msd->channel); + if (mailChannel) { + if (!PL_strcasecmp(aCharacterSet, "us-ascii")) { + mailChannel->SetMailCharacterSet("ISO-8859-1"_ns); + } else { + mailChannel->SetMailCharacterSet(nsCString(aCharacterSet)); + } + } + } + } + + return rv; +} + +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 ConvertToUTF8(const char* stringToUse, int32_t inLength, + const char* input_charset, nsACString& outString) { + nsresult rv = NS_OK; + + // Look up Thunderbird's special aliases from charsetalias.properties. + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, -1); + + nsCString newCharset; + rv = ccm->GetCharsetAlias(input_charset, newCharset); + NS_ENSURE_SUCCESS(rv, -1); + + if (newCharset.Equals("UTF-7", nsCaseInsensitiveCStringComparator)) { + nsAutoString utf16; + rv = CopyUTF7toUTF16(nsDependentCSubstring(stringToUse, inLength), utf16); + if (NS_FAILED(rv)) return -1; + CopyUTF16toUTF8(utf16, outString); + return 0; + } + + auto encoding = mozilla::Encoding::ForLabel(newCharset); + NS_ENSURE_TRUE(encoding, + -1); // Impossible since GetCharsetAlias() already checked. + + rv = encoding->DecodeWithoutBOMHandling( + nsDependentCSubstring(stringToUse, inLength), outString); + return NS_SUCCEEDED(rv) ? 0 : -1; +} + +static int mime_convert_charset(const char* input_line, int32_t input_length, + const char* input_charset, + nsACString& convertedString, + void* stream_closure) { + return ConvertToUTF8(input_line, input_length, input_charset, + convertedString); +} + +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; + + 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); + } + delete[] 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) free(msd->url_name); + + if (msd->orig_url_name) 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) free(msd->url_name); + + if (msd->orig_url_name) 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; + + PR_ASSERT(mid); + if (!mid) return 0; + + /* Internal-external-reconnect only works when going to the screen. */ + if (!mid->istream) + return strdup( + "<DIV CLASS=\"moz-attached-image-container\"><IMG " + "SRC=\"resource://gre-resources/loading-image.png\" " + "ALT=\"[Image]\"></DIV>"); + + const char* prefix; + const char* url; + char* buf; + /* Wouldn't it be nice if attributes were case-sensitive? */ + const char* scaledPrefix = + "<DIV CLASS=\"moz-attached-image-container\"><IMG " + "CLASS=\"moz-attached-image\" shrinktofit=\"yes\" SRC=\""; + const char* suffix = "\"></DIV>"; + // Thunderbird doesn't have this pref. +#ifdef MOZ_SUITE + const char* unscaledPrefix = + "<DIV CLASS=\"moz-attached-image-container\"><IMG " + "CLASS=\"moz-attached-image\" SRC=\""; + 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; +#else + prefix = scaledPrefix; +#endif + + 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 connection 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; + show_attachment_inline_text = 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; + + // 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) { + nsAutoCString orgSpec; + msgUrl->GetOriginalSpec(orgSpec); + msd->orig_url_name = ToNewCString(orgSpec); + } + } + } + + 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.inline_attachments.text", + &(msd->options->show_attachment_inline_text)); + 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; + } + + 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 || + format_out == nsMimeOutput::nsMimeMessageQuoting || + format_out == nsMimeOutput::nsMimeMessageBodyQuoting) + 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::components::StringBundle::Service(); + + nsCOMPtr<nsIStringBundle> stringBundle; + stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle)); + if (stringBundle) { + nsString v; + if (NS_SUCCEEDED(stringBundle->GetStringFromID(stringID, 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( + NS_ConvertUTF16toUTF8(stringName).get(), 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* cSet = MimeHeaders_get_parameter(ct, "charset", nullptr, nullptr); + if (cSet) { + // The content-type does specify a charset. 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. + if (msd->format_out == nsMimeOutput::nsMimeMessageSaveAs) { + // The previous version of this code would have entered an infinite + // loop. But it never showed up, so it's not clear that we ever get + // here... See bug #1597891. + PR_FREEIF(obj->options->default_charset); + obj->options->default_charset = cSet; + cSet = nullptr; // Ownership was transferred. + obj->options->override_charset = true; + MOZ_DIAGNOSTIC_ASSERT( + false, "Ahh. So this code _is_ run after all! (see bug 1597891)"); + } + 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.AssignLiteral("us-ascii"); + else + charset.Assign(text->charset); + + nsCOMPtr<nsICharsetConverterManager> charSetConverterManager2; + 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(), fontLang); + if (NS_FAILED(rv)) return rv; + + // get a font size from pref + prefStr.Assign(!styleFixed ? "font.size.variable." + : "font.size.monospace."); + 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/comm/mailnews/mime/src/mimemoz2.h b/comm/mailnews/mime/src/mimemoz2.h new file mode 100644 index 0000000000..daec700240 --- /dev/null +++ b/comm/mailnews/mime/src/mimemoz2.h @@ -0,0 +1,207 @@ +/* -*- 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 "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 */ +}; + +// +// 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; + nsCString originalMsgURI; // the original URI of the message we are currently + // processing + nsCOMPtr<nsIMsgDBHdr> origMsgHdr; + bool autodetectCharset; // Used to indicate pending autodetection while + // streaming contents. +}; + +//////////////////////////////////////////////////////////////// +// 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 connection 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/comm/mailnews/mime/src/mimempar.cpp b/comm/mailnews/mime/src/mimempar.cpp new file mode 100644 index 0000000000..2c27e1e95d --- /dev/null +++ b/comm/mailnews/mime/src/mimempar.cpp @@ -0,0 +1,20 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimempar.h b/comm/mailnews/mime/src/mimempar.h new file mode 100644 index 0000000000..7c29f67331 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimemrel.cpp b/comm/mailnews/mime/src/mimemrel.cpp new file mode 100644 index 0000000000..b3efb3323d --- /dev/null +++ b/comm/mailnews/mime/src/mimemrel.cpp @@ -0,0 +1,1113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made + * by IBM described herein are Copyright (c) International Business Machines + * Corporation, 2000. Modifications to Mozilla code or documentation identified + * per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +/* 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 "nsString.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; + + 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* base_url = + MimeHeaders_get(child->headers, HEADER_CONTENT_BASE, false, false); + char* 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 therefore we will have + to hope the datafork is the second one! + */ + if (mime_typep(child, + (MimeObjectClass*)&mimeMultipartAppleDoubleClass)) + partnum.AppendLiteral(".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) { + // Mozilla platform code will correct the file extension + // when copying the embedded image. That doesn't work + // since our MailNews URLs don't allow setting the file + // extension. So provide a filename and valid extension. + char* ct = MimeHeaders_get(child->headers, HEADER_CONTENT_TYPE, + false, false); + if (ct) { + name = ct; + char* slash = strchr(name, '/'); + if (slash) *slash = '.'; + char* semi = strchr(name, ';'); + if (semi) *semi = 0; + } + } + 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 careful 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. */ + relobj->head_loaded = true; + relobj->headobj = child; + relobj->buffered_hdrs = MimeHeaders_copy(child->headers); + char* base_url = + MimeHeaders_get(child->headers, HEADER_CONTENT_BASE, false, false); + if (!base_url) { + base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, false, + false); + } + + 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); + nsCOMPtr<nsIURI> url; + nsresult rv = nsMimeNewURI(getter_AddRefs(url), base_url, nullptr); + if (NS_SUCCEEDED(rv)) { + 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; + } + + // Don't actually parse this child; we'll handle all that at eof time. + return false; +} + +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 = 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 = 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/comm/mailnews/mime/src/mimemrel.h b/comm/mailnews/mime/src/mimemrel.h new file mode 100644 index 0000000000..e861e7268c --- /dev/null +++ b/comm/mailnews/mime/src/mimemrel.h @@ -0,0 +1,61 @@ +/* -*- 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 "modmimee.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/comm/mailnews/mime/src/mimemsg.cpp b/comm/mailnews/mime/src/mimemsg.cpp new file mode 100644 index 0000000000..fcd86651f7 --- /dev/null +++ b/comm/mailnews/mime/src/mimemsg.cpp @@ -0,0 +1,847 @@ +/* -*- 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 "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 "nsString.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)) { + 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, 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 (without 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/comm/mailnews/mime/src/mimemsg.h b/comm/mailnews/mime/src/mimemsg.h new file mode 100644 index 0000000000..ec8c45336c --- /dev/null +++ b/comm/mailnews/mime/src/mimemsg.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/. */ + +#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/comm/mailnews/mime/src/mimemsig.cpp b/comm/mailnews/mime/src/mimemsig.cpp new file mode 100644 index 0000000000..5f390abd94 --- /dev/null +++ b/comm/mailnews/mime/src/mimemsig.cpp @@ -0,0 +1,716 @@ +/* -*- 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 "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 "modmimee.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) { + obj->closed_p = true; + 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 preceding 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. */ + [[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 preceded 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 preceding 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. */ + [[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"); + [[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 */ + [[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 preceded 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 preceding 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"); + [[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/comm/mailnews/mime/src/mimemsig.h b/comm/mailnews/mime/src/mimemsig.h new file mode 100644 index 0000000000..5581778f80 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimemult.cpp b/comm/mailnews/mime/src/mimemult.cpp new file mode 100644 index 0000000000..29caf5f5b0 --- /dev/null +++ b/comm/mailnews/mime/src/mimemult.cpp @@ -0,0 +1,676 @@ +/* -*- 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 "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, nsLiteralCString(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)); + // clang-format off + if (detachingPart) { + char *contentType = + MimeHeaders_get(mult->hdrs, "Content-Type", false, false); + if (contentType) { + MimeWriteAString(obj, "Content-Type: "_ns); + MimeWriteAString(obj, nsDependentCString(contentType)); + PR_Free(contentType); + } + MimeWriteAString(obj, nsLiteralCString(MSG_LINEBREAK)); + MimeWriteAString(obj, "Content-Disposition: attachment; filename=\""_ns); + MimeWriteAString(obj, fileName); + MimeWriteAString(obj, "\""_ns MSG_LINEBREAK); + MimeWriteAString(obj, "X-Mozilla-External-Attachment-URL: "_ns); + MimeWriteAString(obj, obj->options->state->detachedFilePath); + MimeWriteAString(obj, nsLiteralCString(MSG_LINEBREAK)); + MimeWriteAString(obj, "X-Mozilla-Altered: AttachmentDetached; date=\""_ns); + } else { + nsAutoCString header("Content-Type: text/x-moz-deleted; name=\"Deleted: "); + header.Append(fileName); + MimeWriteAString(obj, header); + MimeWriteAString(obj, "\""_ns MSG_LINEBREAK + "Content-Transfer-Encoding: 8bit"_ns MSG_LINEBREAK); + MimeWriteAString(obj, "Content-Disposition: inline; filename=\"Deleted: "_ns); + MimeWriteAString(obj, fileName); + MimeWriteAString(obj, "\""_ns MSG_LINEBREAK + "X-Mozilla-Altered: AttachmentDeleted; date=\""_ns); + } + 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 MSG_LINEBREAK); + MimeWriteAString(obj, MSG_LINEBREAK + "You deleted an attachment from this message. The original "_ns + "MIME headers for the attachment were:"_ns MSG_LINEBREAK); + MimeHeaders_write_raw_headers(mult->hdrs, obj->options, false); + // clang-format on + } + 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) && + /* 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 termination 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) && + !((mime_typep(body, (MimeObjectClass*)&mimeExternalObjectClass) || + mime_typep(body, (MimeObjectClass*)&mimeSuppressedCryptoClass)) && + (!strcmp(body->content_type, "text/vcard") || + !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 + * content 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(nsLiteralCString(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) { + // clang-format off + if (!mime_typep(object, (MimeObjectClass *)&mimeMultipartRelatedClass) && + !mime_typep(object, (MimeObjectClass *)&mimeMultipartAlternativeClass) && + !mime_typep(object, (MimeObjectClass *)&mimeMultipartSignedClass) && + /* 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 termination 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) && + !((mime_typep(kid, (MimeObjectClass *)&mimeExternalObjectClass) || + mime_typep(kid, (MimeObjectClass *)&mimeSuppressedCryptoClass)) && + (!strcmp(kid->content_type, "text/vcard") || + !strcmp(kid->content_type, "text/x-vcard")))) { + status = object->options->decompose_file_close_fn( + object->options->stream_closure); + if (status < 0) return status; + } + // clang-format on + } +#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) && + /* 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 termination 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) && + !((mime_typep(kid, (MimeObjectClass*)&mimeExternalObjectClass) || + mime_typep(kid, (MimeObjectClass*)&mimeSuppressedCryptoClass)) && + (!strcmp(kid->content_type, "text/vcard") || + !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 preceded 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 preceding 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/comm/mailnews/mime/src/mimemult.h b/comm/mailnews/mime/src/mimemult.h new file mode 100644 index 0000000000..9afa74885a --- /dev/null +++ b/comm/mailnews/mime/src/mimemult.h @@ -0,0 +1,101 @@ +/* -*- 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/comm/mailnews/mime/src/mimeobj.cpp b/comm/mailnews/mime/src/mimeobj.cpp new file mode 100644 index 0000000000..7ad7e290d1 --- /dev/null +++ b/comm/mailnews/mime/src/mimeobj.cpp @@ -0,0 +1,301 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made + * by IBM described herein are Copyright (c) International Business Machines + * Corporation, 2000. Modifications to Mozilla code or documentation identified + * per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#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 (abort_p) { + obj->closed_p = true; + return 0; + } + 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 (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/comm/mailnews/mime/src/mimeobj.h b/comm/mailnews/mime/src/mimeobj.h new file mode 100644 index 0000000000..9a74481c65 --- /dev/null +++ b/comm/mailnews/mime/src/mimeobj.h @@ -0,0 +1,182 @@ +/* -*- 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 + information 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/comm/mailnews/mime/src/mimepbuf.cpp b/comm/mailnews/mime/src/mimepbuf.cpp new file mode 100644 index 0000000000..c428f66eb4 --- /dev/null +++ b/comm/mailnews/mime/src/mimepbuf.cpp @@ -0,0 +1,251 @@ +/* -*- 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 "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() + : part_buffer(nullptr), part_buffer_fp(0), part_buffer_size(0) {} +}; + +MimePartBufferData* MimePartBufferCreate(void) { + return new MimePartBufferData(); +} + +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); + delete 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 = 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 = 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/comm/mailnews/mime/src/mimepbuf.h b/comm/mailnews/mime/src/mimepbuf.h new file mode 100644 index 0000000000..b0e19ca379 --- /dev/null +++ b/comm/mailnews/mime/src/mimepbuf.h @@ -0,0 +1,63 @@ +/* -*- 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/comm/mailnews/mime/src/mimesun.cpp b/comm/mailnews/mime/src/mimesun.cpp new file mode 100644 index 0000000000..03e25336c9 --- /dev/null +++ b/comm/mailnews/mime/src/mimesun.cpp @@ -0,0 +1,313 @@ +/* -*- 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 "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) { + if (obj->options) obj->options->is_child = true; + + 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/comm/mailnews/mime/src/mimesun.h b/comm/mailnews/mime/src/mimesun.h new file mode 100644 index 0000000000..8d3b15bd1c --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimetenr.cpp b/comm/mailnews/mime/src/mimetenr.cpp new file mode 100644 index 0000000000..600cec7397 --- /dev/null +++ b/comm/mailnews/mime/src/mimetenr.cpp @@ -0,0 +1,27 @@ +/* -*- 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 "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/comm/mailnews/mime/src/mimetenr.h b/comm/mailnews/mime/src/mimetenr.h new file mode 100644 index 0000000000..9ff2d6eab4 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimetext.cpp b/comm/mailnews/mime/src/mimetext.cpp new file mode 100644 index 0000000000..d13b8f8eb4 --- /dev/null +++ b/comm/mailnews/mime/src/mimetext.cpp @@ -0,0 +1,442 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made + * by IBM described herein are Copyright (c) International Business Machines + * Corporation, 2000. Modifications to Mozilla code or documentation identified + * per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +#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 "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) { + if (obj->options->default_charset) { + text->charset = strdup(obj->options->default_charset); + } else { + text->charsetOverridable = true; + text->inputAutodetect = true; + text->needUpdateMsgWinCharset = true; + text->charset = strdup(""); + } + } 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) { + text->charsetOverridable = true; + text->inputAutodetect = true; + text->needUpdateMsgWinCharset = true; + + if (obj->options && obj->options->default_charset) + text->charset = strdup(obj->options->default_charset); + else + text->charset = strdup("UTF-8"); + } + } + } + + 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); + + 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 found 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 found charset yet? Now is 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; + nsAutoCString converted; + + MimeInlineText* text = (MimeInlineText*)obj; + + // In case of charset autodetection, charset can be overridden 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. + 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); + } + } + } + + status = obj->options->charset_conversion_fn( + line, length, text->charset, converted, obj->options->stream_closure); + + if (status == 0) { + line = (char*)converted.get(); + length = converted.Length(); + } + + // 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); + + 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; + nsAutoCString detectedCharset; + 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 object. + if (NS_SUCCEEDED(res) && !detectedCharset.IsEmpty()) { + PR_FREEIF(text->charset); + text->charset = ToNewCString(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/comm/mailnews/mime/src/mimetext.h b/comm/mailnews/mime/src/mimetext.h new file mode 100644 index 0000000000..d3a7a29677 --- /dev/null +++ b/comm/mailnews/mime/src/mimetext.h @@ -0,0 +1,79 @@ +/* -*- 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; + + 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/comm/mailnews/mime/src/mimethpl.cpp b/comm/mailnews/mime/src/mimethpl.cpp new file mode 100644 index 0000000000..d21efb82bb --- /dev/null +++ b/comm/mailnews/mime/src/mimethpl.cpp @@ -0,0 +1,151 @@ +/* -*- 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/. */ + +/* 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 "nsString.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 synchronously, 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 asynchronously, 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/comm/mailnews/mime/src/mimethpl.h b/comm/mailnews/mime/src/mimethpl.h new file mode 100644 index 0000000000..8a06e6a2b4 --- /dev/null +++ b/comm/mailnews/mime/src/mimethpl.h @@ -0,0 +1,36 @@ +/* -*- 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 "nsString.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/comm/mailnews/mime/src/mimethsa.cpp b/comm/mailnews/mime/src/mimethsa.cpp new file mode 100644 index 0000000000..19c1e5460a --- /dev/null +++ b/comm/mailnews/mime/src/mimethsa.cpp @@ -0,0 +1,127 @@ +/* -*- 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/. */ + +/* 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 "nsString.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/comm/mailnews/mime/src/mimethsa.h b/comm/mailnews/mime/src/mimethsa.h new file mode 100644 index 0000000000..5d4f318fd2 --- /dev/null +++ b/comm/mailnews/mime/src/mimethsa.h @@ -0,0 +1,30 @@ +/* -*- 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" +#include "nsString.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/comm/mailnews/mime/src/mimethtm.cpp b/comm/mailnews/mime/src/mimethtm.cpp new file mode 100644 index 0000000000..e35b6a6446 --- /dev/null +++ b/comm/mailnews/mime/src/mimethtm.cpp @@ -0,0 +1,229 @@ +/* -*- 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 "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.LowerCaseFindASCII("<body"); + 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' attribute 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( + "<div class=\"moz-text-html\" lang=\""_ns + fontLang + "\">"_ns, index); + } else { + message.Insert("<div class=\"moz-text-html\">"_ns, index); + } + + nsACString::const_iterator begin, end; + message.BeginReading(begin); + message.EndReading(end); + nsACString::const_iterator messageBegin = begin; + if (RFindInReadable("</body>"_ns, begin, end, + nsCaseInsensitiveCStringComparator)) { + message.InsertLiteral("</div>", begin - messageBegin); + } +} + +/* + * 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.LowerCaseFindASCII("<plaintext", index)) != + kNotFound) { + message.Insert("x-", index + 1); + index += 12; + replaced = true; + } + if (replaced) { + index = 0; + while ((index = message.LowerCaseFindASCII("</plaintext", index)) != + kNotFound) { + message.Insert("x-", index + 2); + index += 13; + } + } +} diff --git a/comm/mailnews/mime/src/mimethtm.h b/comm/mailnews/mime/src/mimethtm.h new file mode 100644 index 0000000000..f5e178a937 --- /dev/null +++ b/comm/mailnews/mime/src/mimethtm.h @@ -0,0 +1,34 @@ +/* -*- 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/comm/mailnews/mime/src/mimetpfl.cpp b/comm/mailnews/mime/src/mimetpfl.cpp new file mode 100644 index 0000000000..d354217e55 --- /dev/null +++ b/comm/mailnews/mime/src/mimetpfl.cpp @@ -0,0 +1,613 @@ +/* -*- 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 "mimetpfl.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "mozITXTToHTMLConv.h" +#include "nsString.h" +#include "nsMimeStringResources.h" +#include "nsIPrefBranch.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 + nsACString& 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.Truncate(); // 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); + + // Clear mCitationColor + MimeInlineTextPlainFlowed* text = (MimeInlineTextPlainFlowed*)obj; + text->mCitationColor.Truncate(); + + 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) { + line++; + linep++; + length--; + } 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(nsDependentCString(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, whattodo, 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(nsDependentCString(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.Append(' '); + } + + 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/comm/mailnews/mime/src/mimetpfl.h b/comm/mailnews/mime/src/mimetpfl.h new file mode 100644 index 0000000000..110808e63d --- /dev/null +++ b/comm/mailnews/mime/src/mimetpfl.h @@ -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/. */ + +#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 + nsCString 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/comm/mailnews/mime/src/mimetpla.cpp b/comm/mailnews/mime/src/mimetpla.cpp new file mode 100644 index 0000000000..081d7951a4 --- /dev/null +++ b/comm/mailnews/mime/src/mimetpla.cpp @@ -0,0 +1,409 @@ +/* -*- 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 "mimetpla.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "mozITXTToHTMLConv.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.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 + nsACString& citationColor, // mail.citation_color + nsACString& style) { + switch (quotedStyleSetting) { + case 0: // regular + break; + case 1: // bold + style.AppendLiteral("font-weight: bold; "); + break; + case 2: // italic + style.AppendLiteral("font-style: italic; "); + break; + case 3: // bold-italic + style.AppendLiteral("font-weight: bold; font-style: italic; "); + break; + } + + switch (quotedSizeSetting) { + case 0: // regular + break; + case 1: // large + style.AppendLiteral("font-size: large; "); + break; + case 2: // small + style.AppendLiteral("font-size: small; "); + break; + } + + if (!citationColor.IsEmpty()) { + 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.Truncate(); // 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 class=\"moz-quote-pre\">\n"; + } else + openingDiv = "<pre wrap class=\"moz-quote-pre\">\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.IsEmpty()) + citationColor = 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) { + nsDependentCSubstring 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(nsDependentCString(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 class=\"moz-quote-pre\">\n"; + } else if (text->mCiteLevel < oldCiteLevel) { + prefaceResultStr += "</pre>"; + for (uint32_t i = 0; i < oldCiteLevel - text->mCiteLevel; i++) + prefaceResultStr += "</blockquote>"; + prefaceResultStr += "<pre wrap class=\"moz-quote-pre\">\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, 0 /* no recognition */, + 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(Substring(lineSourceStr, logicalLineStart), whattodo, + 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(nsDependentCString(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/comm/mailnews/mime/src/mimetpla.h b/comm/mailnews/mime/src/mimetpla.h new file mode 100644 index 0000000000..de70a277ec --- /dev/null +++ b/comm/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 + nsCString 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/comm/mailnews/mime/src/mimetric.cpp b/comm/mailnews/mime/src/mimetric.cpp new file mode 100644 index 0000000000..79eedea75d --- /dev/null +++ b/comm/mailnews/mime/src/mimetric.cpp @@ -0,0 +1,356 @@ +/* -*- 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 "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 superseded 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 preceding 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, 6)) + // 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=JUSTIFY>"; + 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 (!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>"; + } + 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>"; + } + 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/comm/mailnews/mime/src/mimetric.h b/comm/mailnews/mime/src/mimetric.h new file mode 100644 index 0000000000..6ec0961aed --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/mimeunty.cpp b/comm/mailnews/mime/src/mimeunty.cpp new file mode 100644 index 0000000000..6cdea05268 --- /dev/null +++ b/comm/mailnews/mime/src/mimeunty.cpp @@ -0,0 +1,523 @@ +/* -*- 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 "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 going to 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++; + + int name_len = (line + length) - s; + + name = (char*)PR_MALLOC(name_len + 1); + if (!name) return false; /* grr... */ + memcpy(name, s, name_len); + name[name_len] = 0; + + if (name_len) { + /* take off newline. */ + if (name_len && name[name_len - 1] == '\n') { + name[name_len] = 0; + name_len--; + } + if (name_len && name[name_len - 1] == '\r') { + name[name_len] = 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; + + int name_len = endofline - s; + + name = (char*)PR_MALLOC(name_len + 1); + if (!name) return false; /* grr... */ + memcpy(name, s, name_len); + name[name_len] = 0; + + if (name_len) { + /* take off newline. */ + if (name_len && name[name_len - 1] == '\n') { + name[name_len] = 0; + name_len--; + } + if (name_len && name[name_len - 1] == '\r') { + name[name_len] = 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/comm/mailnews/mime/src/mimeunty.h b/comm/mailnews/mime/src/mimeunty.h new file mode 100644 index 0000000000..580cbbb8db --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/modlmime.h b/comm/mailnews/mime/src/modlmime.h new file mode 100644 index 0000000000..14d53c9cd6 --- /dev/null +++ b/comm/mailnews/mime/src/modlmime.h @@ -0,0 +1,388 @@ +/* -*- 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 "nsString.h" +#include "nsMailHeaders.h" +#include "nsIMimeStreamConverter.h" +#include "mozilla/Encoding.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 heads_size 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 number of entries (pointers) in the heads + array (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); + +// clang-format off +/* 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" + */ +// clang-format on + +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 + + 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.) + The conversion is always to UTF-8. + `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, + nsACString& output_ret, void* stream_closure); + + /* 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 show_attachment_inline_text; /* Whether or not we should display text + attachment inline (whatever the + content-disposition says) */ + + 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/comm/mailnews/mime/src/modmimee.h b/comm/mailnews/mime/src/modmimee.h new file mode 100644 index 0000000000..14c6933265 --- /dev/null +++ b/comm/mailnews/mime/src/modmimee.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ +/* + 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/comm/mailnews/mime/src/moz.build b/comm/mailnews/mime/src/moz.build new file mode 100644 index 0000000000..7bd35ca1c6 --- /dev/null +++ b/comm/mailnews/mime/src/moz.build @@ -0,0 +1,88 @@ +# 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", + "nsMimeObjectClassAccess.cpp", + "nsSimpleMimeConverterStub.cpp", + "nsStreamConverter.cpp", +] + +EXTRA_COMPONENTS += [ + "msgMime.manifest", +] + +EXTRA_JS_MODULES += [ + "extraMimeParsers.jsm", + "jsmime.jsm", + "MimeJSComponents.jsm", + "mimeParser.jsm", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +FINAL_LIBRARY = "mail" + +DEFINES["ENABLE_SMIME"] = True diff --git a/comm/mailnews/mime/src/msgMime.manifest b/comm/mailnews/mime/src/msgMime.manifest new file mode 100644 index 0000000000..c6060c321b --- /dev/null +++ b/comm/mailnews/mime/src/msgMime.manifest @@ -0,0 +1 @@ +category custom-mime-encoder A-extra resource:///modules/extraMimeParsers.jsm diff --git a/comm/mailnews/mime/src/nsMimeObjectClassAccess.cpp b/comm/mailnews/mime/src/nsMimeObjectClassAccess.cpp new file mode 100644 index 0000000000..a769ae192e --- /dev/null +++ b/comm/mailnews/mime/src/nsMimeObjectClassAccess.cpp @@ -0,0 +1,75 @@ +/* -*- 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/comm/mailnews/mime/src/nsMimeObjectClassAccess.h b/comm/mailnews/mime/src/nsMimeObjectClassAccess.h new file mode 100644 index 0000000000..d131ffa1a6 --- /dev/null +++ b/comm/mailnews/mime/src/nsMimeObjectClassAccess.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/. */ + +/* + * 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/comm/mailnews/mime/src/nsMimeStringResources.h b/comm/mailnews/mime/src/nsMimeStringResources.h new file mode 100644 index 0000000000..7857086093 --- /dev/null +++ b/comm/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/comm/mailnews/mime/src/nsSimpleMimeConverterStub.cpp b/comm/mailnews/mime/src/nsSimpleMimeConverterStub.cpp new file mode 100644 index 0000000000..0ff3bd686a --- /dev/null +++ b/comm/mailnews/mime/src/nsSimpleMimeConverterStub.cpp @@ -0,0 +1,191 @@ +/* -*- 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 "mimecth.h" +#include "mimeobj.h" +#include "mimetext.h" +#include "mimemoz2.h" +#include "mimecom.h" +#include "nsString.h" +#include "nsComponentManagerUtils.h" +#include "nsICategoryManager.h" +#include "nsCOMPtr.h" +#include "nsISimpleMimeConverter.h" +#include "nsServiceManagerUtils.h" +#include "nsSimpleMimeConverterStub.h" +#include "nsIMailChannel.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, length); + 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); + + nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(channel); + ssobj->innerScriptable->SetMailChannel(mailChannel); + } + // Remove possible embedded NULL bytes. + // Parsers can't handle this but e.g. calendar invitation might contain such + // as fillers. + ssobj->buffer->StripChar('\0'); + 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, + 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); + + inst.forget(aResult); + return NS_OK; +} diff --git a/comm/mailnews/mime/src/nsSimpleMimeConverterStub.h b/comm/mailnews/mime/src/nsSimpleMimeConverterStub.h new file mode 100644 index 0000000000..ec32091598 --- /dev/null +++ b/comm/mailnews/mime/src/nsSimpleMimeConverterStub.h @@ -0,0 +1,12 @@ +/* -*- 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/comm/mailnews/mime/src/nsStreamConverter.cpp b/comm/mailnews/mime/src/nsStreamConverter.cpp new file mode 100644 index 0000000000..7311295402 --- /dev/null +++ b/comm/mailnews/mime/src/nsStreamConverter.cpp @@ -0,0 +1,981 @@ +/* -*- 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 "nsString.h" +#include "nsUnicharUtils.h" +#include "nsMemory.h" +#include "nsIPipe.h" +#include "nsMimeStringResources.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsNetUtil.h" +#include "nsIMsgQuote.h" +#include "nsNetUtil.h" +#include "mozITXTToHTMLConv.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsINntpUrl.h" +#include "nsIMsgWindow.h" +#include "nsICategoryManager.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) { + // Check whether we need to auto-detect the charset. + nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(aURI)); + if (i18nUrl) { + bool autodetectCharset = false; + nsresult rv = i18nUrl->GetAutodetectCharset(&autodetectCharset); + if (NS_SUCCEEDED(rv) && autodetectCharset) { + *override_charset = true; + *default_charset = nullptr; + } else { + *override_charset = false; + // Special treatment for news: URLs. Get the server default charset. + nsCOMPtr<nsINntpUrl> nntpURL(do_QueryInterface(aURI)); + if (nntpURL) { + nsCString charset; + rv = nntpURL->GetCharset(charset); + if (NS_SUCCEEDED(rv)) { + *default_charset = ToNewCString(charset); + } else { + *default_charset = strdup("UTF-8"); + } + } else { + *default_charset = strdup("UTF-8"); + } + } + } + nsAutoCString urlString; + if (NS_SUCCEEDED(aURI->GetSpec(urlString))) { + if (!urlString.IsEmpty()) { + 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); + mOutputFormat.ReplaceSubstring("%2F", "/"); + mOutputFormat.ReplaceSubstring("%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.EqualsLiteral("application/xhtml+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.EqualsLiteral("message/rfc822")) { + mRealContentType = "application/x-message-display"; + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + } else if (mRealContentType.EqualsLiteral( + "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; +} + +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; + mOutgoingChannel = aChannel; + + // 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->GetSpecIgnoringRef(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, 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.EqualsLiteral("raw")) + *aOutputContentType = + (char*)moz_xmemdup(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! + NS_IF_ADDREF(*aIdentity = mIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetIdentity(nsIMsgIdentity* aIdentity) { + mIdentity = aIdentity; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOriginalMsgURI(const nsACString& originalMsgURI) { + mOriginalMsgURI = originalMsgURI; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOriginalMsgURI(nsACString& result) { + result = 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 whenever data is written into the input stream by the +// networking library... +// +nsresult nsStreamConverter::OnDataAvailable(nsIRequest* request, + nsIInputStream* aIStream, + uint64_t sourceOffset, + uint32_t aLength) { + nsresult rc = NS_OK; // should this be an error instead? + 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; + } + + nsCOMPtr<nsIInputStream> stream = aIStream; + NS_ENSURE_TRUE(stream, NS_ERROR_NULL_POINTER); + char* buf = (char*)PR_Malloc(aLength); + if (!buf) return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */ + + uint32_t readLen; + stream->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) { +#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; + } else + mOutListener->OnStartRequest(request); + } + + 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, + nsresult status) { + // Make sure we fire any pending OnStartRequest before we do OnStop. + FirePendingStartRequest(); + + // + // 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(); + + if (mOutgoingChannel) { + nsCOMPtr<nsILoadGroup> loadGroup; + mOutgoingChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) loadGroup->RemoveRequest(mOutgoingChannel, nullptr, status); + } + + // Make sure to do necessary cleanup! + InternalCleanup(); + + // forward on top request to any listeners + if (mOutListener) mOutListener->OnStopRequest(request, 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 synchronous 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); + mPendingRequest = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsStreamConverter::GetConvertedType(const nsACString& aFromType, + nsIChannel* aChannel, + nsACString& aToType) { + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/comm/mailnews/mime/src/nsStreamConverter.h b/comm/mailnews/mime/src/nsStreamConverter.h new file mode 100644 index 0000000000..1d2e1bcccc --- /dev/null +++ b/comm/mailnews/mime/src/nsStreamConverter.h @@ -0,0 +1,94 @@ +/* -*- 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 "nsString.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 +}; + +#endif /* nsStreamConverter_h_ */ |