diff options
Diffstat (limited to 'comm/mailnews/mime/src/mimetpfl.cpp')
-rw-r--r-- | comm/mailnews/mime/src/mimetpfl.cpp | 613 |
1 files changed, 613 insertions, 0 deletions
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; +} |