use of com.fsck.k9.mail.internet.Viewable.Alternative in project k-9 by k9mail.
the class MessageExtractor method findViewablesAndAttachments.
/** Traverse the MIME tree of a message and extract viewable parts. */
public static void findViewablesAndAttachments(Part part, @Nullable List<Viewable> outputViewableParts, @Nullable List<Part> outputNonViewableParts) throws MessagingException {
boolean skipSavingNonViewableParts = outputNonViewableParts == null;
boolean skipSavingViewableParts = outputViewableParts == null;
if (skipSavingNonViewableParts && skipSavingViewableParts) {
throw new IllegalArgumentException("method was called but no output is to be collected - this a bug!");
}
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart multipart = (Multipart) body;
if (isSameMimeType(part.getMimeType(), "multipart/alternative")) {
/*
* For multipart/alternative parts we try to find a text/plain and a text/html
* child. Everything else we find is put into 'attachments'.
*/
List<Viewable> text = findTextPart(multipart, true);
Set<Part> knownTextParts = getParts(text);
List<Viewable> html = findHtmlPart(multipart, knownTextParts, outputNonViewableParts, true);
if (skipSavingViewableParts) {
return;
}
if (!text.isEmpty() || !html.isEmpty()) {
Alternative alternative = new Alternative(text, html);
outputViewableParts.add(alternative);
}
} else {
// For all other multipart parts we recurse to grab all viewable children.
for (Part bodyPart : multipart.getBodyParts()) {
findViewablesAndAttachments(bodyPart, outputViewableParts, outputNonViewableParts);
}
}
} else if (body instanceof Message && !("attachment".equalsIgnoreCase(getContentDisposition(part)))) {
if (skipSavingViewableParts) {
return;
}
/*
* We only care about message/rfc822 parts whose Content-Disposition header has a value
* other than "attachment".
*/
Message message = (Message) body;
// We add the Message object so we can extract the filename later.
outputViewableParts.add(new MessageHeader(part, message));
// Recurse to grab all viewable parts and attachments from that message.
findViewablesAndAttachments(message, outputViewableParts, outputNonViewableParts);
} else if (isPartTextualBody(part)) {
if (skipSavingViewableParts) {
return;
}
String mimeType = part.getMimeType();
Viewable viewable;
if (isSameMimeType(mimeType, "text/plain")) {
if (isFormatFlowed(part.getContentType())) {
viewable = new Flowed(part);
} else {
viewable = new Text(part);
}
} else {
viewable = new Html(part);
}
outputViewableParts.add(viewable);
} else if (isSameMimeType(part.getMimeType(), "application/pgp-signature")) {
// ignore this type explicitly
} else {
if (skipSavingNonViewableParts) {
return;
}
// Everything else is treated as attachment.
outputNonViewableParts.add(part);
}
}
use of com.fsck.k9.mail.internet.Viewable.Alternative in project k-9 by k9mail.
the class MessageExtractor method findHtmlPart.
/**
* Search the children of a {@link Multipart} for {@code text/html} parts.
* Every part that is not a {@code text/html} we want to display, we add to 'attachments'.
*
* @param multipart The {@code Multipart} to search through.
* @param knownTextParts A set of {@code text/plain} parts that shouldn't be added to 'attachments'.
* @param outputNonViewableParts A list that will receive the parts that are considered attachments.
* @param directChild If {@code true}, this method will add all {@code text/html} parts except the first
* found to 'attachments'.
*
* @return A list of {@link Text} viewables.
*
* @throws MessagingException In case of an error.
*/
private static List<Viewable> findHtmlPart(Multipart multipart, Set<Part> knownTextParts, @Nullable List<Part> outputNonViewableParts, boolean directChild) throws MessagingException {
boolean saveNonViewableParts = outputNonViewableParts != null;
List<Viewable> viewables = new ArrayList<>();
boolean partFound = false;
for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
if (directChild && partFound) {
if (saveNonViewableParts) {
// We already found our text/html part. Now we're only looking for attachments.
findAttachments(innerMultipart, knownTextParts, outputNonViewableParts);
}
} else {
/*
* Recurse to find HTML parts. Since this is a multipart that is a child of a
* multipart/alternative we don't want to stop after the first text/html part
* we find. This will allow to get all text parts for constructions like this:
*
* 1. multipart/alternative
* 1.1. text/plain
* 1.2. multipart/mixed
* 1.2.1. text/html
* 1.2.2. text/html
* 1.3. image/jpeg
*/
List<Viewable> htmlViewables = findHtmlPart(innerMultipart, knownTextParts, outputNonViewableParts, false);
if (!htmlViewables.isEmpty()) {
partFound = true;
viewables.addAll(htmlViewables);
}
}
} else if (!(directChild && partFound) && isPartTextualBody(part) && isSameMimeType(part.getMimeType(), "text/html")) {
Html html = new Html(part);
viewables.add(html);
partFound = true;
} else if (!knownTextParts.contains(part)) {
if (saveNonViewableParts) {
// Only add this part as attachment if it's not a viewable text/plain part found earlier
outputNonViewableParts.add(part);
}
}
}
return viewables;
}
use of com.fsck.k9.mail.internet.Viewable.Alternative in project k-9 by k9mail.
the class MessageViewInfoExtractor method extractTextFromViewables.
/**
* Extract the viewable textual parts of a message and return the rest as attachments.
*
* @return A {@link ViewableExtractedText} instance containing the textual parts of the message as
* plain text and HTML, and a list of message parts considered attachments.
*
* @throws com.fsck.k9.mail.MessagingException
* In case of an error.
*/
@VisibleForTesting
ViewableExtractedText extractTextFromViewables(List<Viewable> viewables) throws MessagingException {
try {
// Collect all viewable parts
/*
* Convert the tree of viewable parts into text and HTML
*/
// Used to suppress the divider for the first viewable part
boolean hideDivider = true;
StringBuilder text = new StringBuilder();
StringBuilder html = new StringBuilder();
for (Viewable viewable : viewables) {
if (viewable instanceof Textual) {
// This is either a text/plain or text/html part. Fill the variables 'text' and
// 'html', converting between plain text and HTML as necessary.
text.append(buildText(viewable, !hideDivider));
html.append(buildHtml(viewable, !hideDivider));
hideDivider = false;
} else if (viewable instanceof MessageHeader) {
MessageHeader header = (MessageHeader) viewable;
Part containerPart = header.getContainerPart();
Message innerMessage = header.getMessage();
addTextDivider(text, containerPart, !hideDivider);
addMessageHeaderText(text, innerMessage);
addHtmlDivider(html, containerPart, !hideDivider);
addMessageHeaderHtml(html, innerMessage);
hideDivider = true;
} else if (viewable instanceof Alternative) {
// Handle multipart/alternative contents
Alternative alternative = (Alternative) viewable;
/*
* We made sure at least one of text/plain or text/html is present when
* creating the Alternative object. If one part is not present we convert the
* other one to make sure 'text' and 'html' always contain the same text.
*/
List<Viewable> textAlternative = alternative.getText().isEmpty() ? alternative.getHtml() : alternative.getText();
List<Viewable> htmlAlternative = alternative.getHtml().isEmpty() ? alternative.getText() : alternative.getHtml();
// Fill the 'text' variable
boolean divider = !hideDivider;
for (Viewable textViewable : textAlternative) {
text.append(buildText(textViewable, divider));
divider = true;
}
// Fill the 'html' variable
divider = !hideDivider;
for (Viewable htmlViewable : htmlAlternative) {
html.append(buildHtml(htmlViewable, divider));
divider = true;
}
hideDivider = false;
}
}
String content = HtmlConverter.wrapMessageContent(html);
String sanitizedHtml = htmlSanitizer.sanitize(content);
return new ViewableExtractedText(text.toString(), sanitizedHtml);
} catch (Exception e) {
throw new MessagingException("Couldn't extract viewable parts", e);
}
}
use of com.fsck.k9.mail.internet.Viewable.Alternative in project k-9 by k9mail.
the class MessageViewInfoExtractor method buildHtml.
/**
* Use the contents of a {@link com.fsck.k9.mail.internet.Viewable} to create the HTML to be displayed.
*
* <p>
* This will use {@link HtmlConverter#textToHtml(String)} to convert plain text parts
* to HTML if necessary.
* </p>
*
* @param viewable
* The viewable part to build the HTML from.
* @param prependDivider
* {@code true}, if the HTML divider should be inserted as first element.
* {@code false}, otherwise.
*
* @return The contents of the supplied viewable instance as HTML.
*/
private StringBuilder buildHtml(Viewable viewable, boolean prependDivider) {
StringBuilder html = new StringBuilder();
if (viewable instanceof Textual) {
Part part = ((Textual) viewable).getPart();
addHtmlDivider(html, part, prependDivider);
String t = MessageExtractor.getTextFromPart(part);
if (t == null) {
t = "";
} else if (viewable instanceof Flowed) {
t = FlowedMessageUtils.deflow(t, false);
t = HtmlConverter.textToHtml(t);
} else if (viewable instanceof Text) {
t = HtmlConverter.textToHtml(t);
} else if (!(viewable instanceof Html)) {
throw new IllegalStateException("unhandled case!");
}
html.append(t);
} else if (viewable instanceof Alternative) {
// That's odd - an Alternative as child of an Alternative; go ahead and try to use the
// text/html child; fall-back to the text/plain part.
Alternative alternative = (Alternative) viewable;
List<Viewable> htmlAlternative = alternative.getHtml().isEmpty() ? alternative.getText() : alternative.getHtml();
boolean divider = prependDivider;
for (Viewable htmlViewable : htmlAlternative) {
html.append(buildHtml(htmlViewable, divider));
divider = true;
}
}
return html;
}
use of com.fsck.k9.mail.internet.Viewable.Alternative in project k-9 by k9mail.
the class MigrationTo51 method db51MigrateMessageFormat.
/**
* This method converts from the old message table structure to the new one.
*
* This is a complex migration, and ultimately we do not have enough
* information to recreate the mime structure of the original mails.
* What we have:
* - general mail info
* - html_content and text_content data, which is the squashed readable content of the mail
* - a table with message headers
* - attachments
*
* What we need to do:
* - migrate general mail info as-is
* - flag mails as migrated for re-download
* - for each message, recreate a mime structure from its message content and attachments:
* + insert one or both of textContent and htmlContent, depending on mimeType
* + if mimeType is text/plain, text/html or multipart/alternative and no
* attachments are present, just insert that.
* + otherwise, use multipart/mixed, adding attachments after textual content
* + revert content:// URIs in htmlContent to original cid: URIs.
*/
public static void db51MigrateMessageFormat(SQLiteDatabase db, MigrationsHelper migrationsHelper) {
renameOldMessagesTableAndCreateNew(db);
copyMessageMetadataToNewTable(db);
File attachmentDirNew, attachmentDirOld;
Account account = migrationsHelper.getAccount();
attachmentDirNew = StorageManager.getInstance(K9.app).getAttachmentDirectory(account.getUuid(), account.getLocalStorageProviderId());
attachmentDirOld = renameOldAttachmentDirAndCreateNew(account, attachmentDirNew);
Cursor msgCursor = db.query("messages_old", new String[] { "id", "flags", "html_content", "text_content", "mime_type", "attachment_count" }, null, null, null, null, null);
try {
Timber.d("migrating %d messages", msgCursor.getCount());
ContentValues cv = new ContentValues();
while (msgCursor.moveToNext()) {
long messageId = msgCursor.getLong(0);
String messageFlags = msgCursor.getString(1);
String htmlContent = msgCursor.getString(2);
String textContent = msgCursor.getString(3);
String mimeType = msgCursor.getString(4);
int attachmentCount = msgCursor.getInt(5);
try {
updateFlagsForMessage(db, messageId, messageFlags, migrationsHelper);
MimeHeader mimeHeader = loadHeaderFromHeadersTable(db, messageId);
MimeStructureState structureState = MimeStructureState.getNewRootState();
boolean messageHadSpecialFormat = false;
// we do not rely on the protocol parameter here but guess by the multipart structure
boolean isMaybePgpMimeEncrypted = attachmentCount == 2 && MimeUtil.isSameMimeType(mimeType, "multipart/encrypted");
if (isMaybePgpMimeEncrypted) {
MimeStructureState maybeStructureState = migratePgpMimeEncryptedContent(db, messageId, attachmentDirOld, attachmentDirNew, mimeHeader, structureState);
if (maybeStructureState != null) {
structureState = maybeStructureState;
messageHadSpecialFormat = true;
}
}
if (!messageHadSpecialFormat) {
boolean isSimpleStructured = attachmentCount == 0 && Utility.isAnyMimeType(mimeType, "text/plain", "text/html", "multipart/alternative");
if (isSimpleStructured) {
structureState = migrateSimpleMailContent(db, htmlContent, textContent, mimeType, mimeHeader, structureState);
} else {
mimeType = "multipart/mixed";
structureState = migrateComplexMailContent(db, attachmentDirOld, attachmentDirNew, messageId, htmlContent, textContent, mimeHeader, structureState);
}
}
cv.clear();
cv.put("mime_type", mimeType);
cv.put("message_part_id", structureState.rootPartId);
cv.put("attachment_count", attachmentCount);
db.update("messages", cv, "id = ?", new String[] { Long.toString(messageId) });
} catch (IOException e) {
Timber.e(e, "error inserting into database");
}
}
} finally {
msgCursor.close();
}
cleanUpOldAttachmentDirectory(attachmentDirOld);
dropOldMessagesTable(db);
}
Aggregations