Search in sources :

Example 1 with MimeHeader

use of com.fsck.k9.mail.internet.MimeHeader in project k-9 by k9mail.

the class MigrationTo51 method insertBodyAsMultipartAlternative.

private static MimeStructureState insertBodyAsMultipartAlternative(SQLiteDatabase db, MimeStructureState structureState, MimeHeader mimeHeader, String textContent, String htmlContent) throws IOException {
    if (mimeHeader == null) {
        mimeHeader = new MimeHeader();
    }
    String boundary = MimeUtility.getHeaderParameter(mimeHeader.getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE), "boundary");
    if (TextUtils.isEmpty(boundary)) {
        boundary = MimeUtil.createUniqueBoundary();
    }
    mimeHeader.setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("multipart/alternative; boundary=\"%s\";", boundary));
    int dataLocation = textContent != null || htmlContent != null ? DATA_LOCATION__IN_DATABASE : DATA_LOCATION__MISSING;
    ContentValues cv = new ContentValues();
    cv.put("type", MESSAGE_PART_TYPE__UNKNOWN);
    cv.put("data_location", dataLocation);
    cv.put("mime_type", "multipart/alternative");
    cv.put("header", mimeHeader.toString());
    cv.put("boundary", boundary);
    structureState.applyValues(cv);
    long multipartAlternativePartId = db.insertOrThrow("message_parts", null, cv);
    structureState = structureState.nextMultipartChild(multipartAlternativePartId);
    if (textContent != null) {
        structureState = insertTextualPartIntoDatabase(db, structureState, null, textContent, false);
    }
    if (htmlContent != null) {
        structureState = insertTextualPartIntoDatabase(db, structureState, null, htmlContent, true);
    }
    return structureState;
}
Also used : ContentValues(android.content.ContentValues) MimeHeader(com.fsck.k9.mail.internet.MimeHeader)

Example 2 with MimeHeader

use of com.fsck.k9.mail.internet.MimeHeader in project k-9 by k9mail.

the class MigrationTo51 method insertMimeAttachmentPart.

private static MimeStructureState insertMimeAttachmentPart(SQLiteDatabase db, File attachmentDirOld, File attachmentDirNew, MimeStructureState structureState, long id, int size, String name, String mimeType, String storeData, String contentUriString, String contentId, String contentDisposition) {
    Timber.d("processing attachment %d, %s, %s, %s, %s", id, name, mimeType, storeData, contentUriString);
    if (contentDisposition == null) {
        contentDisposition = "attachment";
    }
    MimeHeader mimeHeader = new MimeHeader();
    mimeHeader.setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\r\n name=\"%s\"", mimeType, name));
    mimeHeader.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, String.format(Locale.US, "%s;\r\n filename=\"%s\";\r\n size=%d", contentDisposition, name, // TODO: Should use encoded word defined in RFC 2231.
    size));
    if (contentId != null) {
        mimeHeader.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
    }
    boolean hasData = contentUriString != null;
    File attachmentFileToMove;
    if (hasData) {
        try {
            Uri contentUri = Uri.parse(contentUriString);
            List<String> pathSegments = contentUri.getPathSegments();
            String attachmentId = pathSegments.get(1);
            boolean isMatchingAttachmentId = Long.parseLong(attachmentId) == id;
            File attachmentFile = new File(attachmentDirOld, attachmentId);
            boolean isExistingAttachmentFile = attachmentFile.exists();
            if (!isMatchingAttachmentId) {
                Timber.e("mismatched attachment id. mark as missing");
                attachmentFileToMove = null;
            } else if (!isExistingAttachmentFile) {
                Timber.e("attached file doesn't exist. mark as missing");
                attachmentFileToMove = null;
            } else {
                attachmentFileToMove = attachmentFile;
            }
        } catch (Exception e) {
            // anything here fails, conservatively assume the data doesn't exist
            attachmentFileToMove = null;
        }
    } else {
        attachmentFileToMove = null;
    }
    if (attachmentFileToMove == null) {
        Timber.d("matching attachment is in local cache");
    }
    boolean hasContentTypeAndIsInline = !TextUtils.isEmpty(contentId) && "inline".equalsIgnoreCase(contentDisposition);
    int messageType = hasContentTypeAndIsInline ? MESSAGE_PART_TYPE__HIDDEN_ATTACHMENT : MESSAGE_PART_TYPE__UNKNOWN;
    ContentValues cv = new ContentValues();
    cv.put("type", messageType);
    cv.put("mime_type", mimeType);
    cv.put("decoded_body_size", size);
    cv.put("display_name", name);
    cv.put("header", mimeHeader.toString());
    cv.put("encoding", MimeUtil.ENC_BINARY);
    cv.put("data_location", attachmentFileToMove != null ? DATA_LOCATION__ON_DISK : DATA_LOCATION__MISSING);
    cv.put("content_id", contentId);
    cv.put("server_extra", storeData);
    structureState.applyValues(cv);
    long partId = db.insertOrThrow("message_parts", null, cv);
    structureState = structureState.nextChild(partId);
    if (attachmentFileToMove != null) {
        boolean moveOk = attachmentFileToMove.renameTo(new File(attachmentDirNew, Long.toString(partId)));
        if (!moveOk) {
            Timber.e("Moving attachment to new dir failed!");
        }
    }
    return structureState;
}
Also used : ContentValues(android.content.ContentValues) MimeHeader(com.fsck.k9.mail.internet.MimeHeader) File(java.io.File) Uri(android.net.Uri) IOException(java.io.IOException)

Example 3 with MimeHeader

use of com.fsck.k9.mail.internet.MimeHeader 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);
}
Also used : ContentValues(android.content.ContentValues) Account(com.fsck.k9.Account) MimeHeader(com.fsck.k9.mail.internet.MimeHeader) IOException(java.io.IOException) Cursor(android.database.Cursor) File(java.io.File)

Example 4 with MimeHeader

use of com.fsck.k9.mail.internet.MimeHeader in project k-9 by k9mail.

the class MigrationTo51 method loadHeaderFromHeadersTable.

private static MimeHeader loadHeaderFromHeadersTable(SQLiteDatabase db, long messageId) {
    Cursor headersCursor = db.query("headers", new String[] { "name", "value" }, "message_id = ?", new String[] { Long.toString(messageId) }, null, null, null);
    try {
        MimeHeader mimeHeader = new MimeHeader();
        while (headersCursor.moveToNext()) {
            String name = headersCursor.getString(0);
            String value = headersCursor.getString(1);
            mimeHeader.addHeader(name, value);
        }
        return mimeHeader;
    } finally {
        headersCursor.close();
    }
}
Also used : MimeHeader(com.fsck.k9.mail.internet.MimeHeader) Cursor(android.database.Cursor)

Example 5 with MimeHeader

use of com.fsck.k9.mail.internet.MimeHeader in project k-9 by k9mail.

the class MigrationTo51 method insertTextualPartIntoDatabase.

private static MimeStructureState insertTextualPartIntoDatabase(SQLiteDatabase db, MimeStructureState structureState, MimeHeader mimeHeader, String content, boolean isHtml) throws IOException {
    if (mimeHeader == null) {
        mimeHeader = new MimeHeader();
    }
    mimeHeader.setHeader(MimeHeader.HEADER_CONTENT_TYPE, isHtml ? "text/html; charset=\"utf-8\"" : "text/plain; charset=\"utf-8\"");
    mimeHeader.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, MimeUtil.ENC_QUOTED_PRINTABLE);
    byte[] contentBytes;
    int decodedBodySize;
    int dataLocation;
    if (content != null) {
        ByteArrayOutputStream contentOutputStream = new ByteArrayOutputStream();
        QuotedPrintableOutputStream quotedPrintableOutputStream = new QuotedPrintableOutputStream(contentOutputStream, false);
        quotedPrintableOutputStream.write(content.getBytes());
        quotedPrintableOutputStream.flush();
        dataLocation = DATA_LOCATION__IN_DATABASE;
        contentBytes = contentOutputStream.toByteArray();
        decodedBodySize = content.length();
    } else {
        dataLocation = DATA_LOCATION__MISSING;
        contentBytes = null;
        decodedBodySize = 0;
    }
    ContentValues cv = new ContentValues();
    cv.put("type", MESSAGE_PART_TYPE__UNKNOWN);
    cv.put("data_location", dataLocation);
    cv.put("mime_type", isHtml ? "text/html" : "text/plain");
    cv.put("header", mimeHeader.toString());
    cv.put("data", contentBytes);
    cv.put("decoded_body_size", decodedBodySize);
    cv.put("encoding", MimeUtil.ENC_QUOTED_PRINTABLE);
    cv.put("charset", "utf-8");
    structureState.applyValues(cv);
    long partId = db.insertOrThrow("message_parts", null, cv);
    return structureState.nextChild(partId);
}
Also used : ContentValues(android.content.ContentValues) QuotedPrintableOutputStream(org.apache.james.mime4j.codec.QuotedPrintableOutputStream) MimeHeader(com.fsck.k9.mail.internet.MimeHeader) ByteArrayOutputStream(java.io.ByteArrayOutputStream)

Aggregations

MimeHeader (com.fsck.k9.mail.internet.MimeHeader)5 ContentValues (android.content.ContentValues)4 Cursor (android.database.Cursor)2 File (java.io.File)2 IOException (java.io.IOException)2 Uri (android.net.Uri)1 Account (com.fsck.k9.Account)1 ByteArrayOutputStream (java.io.ByteArrayOutputStream)1 QuotedPrintableOutputStream (org.apache.james.mime4j.codec.QuotedPrintableOutputStream)1