mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-23 18:02:15 -05:00
Fix fatal IndexOutOfBoundsException
The problem:
Configure the account (just an example -- problems can occur in other
configurations as well):
Message Format: HTML
Reply quoting style: Prefix
Quote message when replying: yes
Reply after quoted text: yes
Reply to a message that has a large quantity (20+) of \r\n scattered in
the body of its HTML version (not an unusual scenario).
Add a reply. Save the message as a draft. Go back & open the draft
again. A fatal IndexOutOfBoundsException occurs.
The cause:
When the draft was saved, the X-K9mail-Identity header was computed and
added to the message, then the text of the message was processed with
MimeUtility.fixDraftTextBody, replacing all occurrences of \r\n with \n in
the quoted message before being saved in LocalStore, thus invalidating the
X-K9mail-Identity header.
The fix:
Remove MimeUtility.fixDraftTextBody and implement
MessageCompose$EolConvertingEditText instead. Any message text placed in
an EolConvertingEditText widget is assured to have \n line endings. Any
message text extracted from an EolConvertingEditText widget is assured to
have \r\n line endings. The X-K9mail-Identity header will always be
computed correctly.
Issues thought to be related: 4782, 5010, 5634, 5725
As noted in some of the referenced issues, errors didn't always result in
a fatal exception, but instead with mixed up text.
Ref: commit f9a35aeaee
This commit is contained in:
parent
005241d97b
commit
bfb0316583
@ -216,7 +216,8 @@
|
||||
|
||||
<!-- We have to use "wrap_content" (not "0dip") for "layout_height", otherwise the
|
||||
EditText won't properly grow in height while the user is typing the message -->
|
||||
<EditText
|
||||
<view
|
||||
class="com.fsck.k9.activity.MessageCompose$EolConvertingEditText"
|
||||
android:id="@+id/message_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -228,7 +229,8 @@
|
||||
android:minLines="3"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<EditText
|
||||
<view
|
||||
class="com.fsck.k9.activity.MessageCompose$EolConvertingEditText"
|
||||
android:id="@+id/upper_signature"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -256,7 +258,8 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<EditText
|
||||
<view
|
||||
class="com.fsck.k9.activity.MessageCompose$EolConvertingEditText"
|
||||
android:id="@+id/quoted_text"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -298,7 +301,8 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<EditText
|
||||
<view
|
||||
class="com.fsck.k9.activity.MessageCompose$EolConvertingEditText"
|
||||
android:id="@+id/lower_signature"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -19,6 +19,7 @@ import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.util.Rfc822Tokenizer;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
@ -273,14 +274,14 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
private MultiAutoCompleteTextView mCcView;
|
||||
private MultiAutoCompleteTextView mBccView;
|
||||
private EditText mSubjectView;
|
||||
private EditText mSignatureView;
|
||||
private EditText mMessageContentView;
|
||||
private EolConvertingEditText mSignatureView;
|
||||
private EolConvertingEditText mMessageContentView;
|
||||
private LinearLayout mAttachments;
|
||||
private Button mQuotedTextShow;
|
||||
private View mQuotedTextBar;
|
||||
private ImageButton mQuotedTextEdit;
|
||||
private ImageButton mQuotedTextDelete;
|
||||
private EditText mQuotedText;
|
||||
private EolConvertingEditText mQuotedText;
|
||||
private MessageWebView mQuotedHTML;
|
||||
private InsertableHtmlContent mQuotedHtmlContent; // Container for HTML reply as it's being built.
|
||||
private View mEncryptLayout;
|
||||
@ -588,10 +589,10 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
onAddCcBcc();
|
||||
}
|
||||
|
||||
EditText upperSignature = (EditText)findViewById(R.id.upper_signature);
|
||||
EditText lowerSignature = (EditText)findViewById(R.id.lower_signature);
|
||||
EolConvertingEditText upperSignature = (EolConvertingEditText)findViewById(R.id.upper_signature);
|
||||
EolConvertingEditText lowerSignature = (EolConvertingEditText)findViewById(R.id.lower_signature);
|
||||
|
||||
mMessageContentView = (EditText)findViewById(R.id.message_content);
|
||||
mMessageContentView = (EolConvertingEditText)findViewById(R.id.message_content);
|
||||
mMessageContentView.getInputExtras(true).putBoolean("allowEmoji", true);
|
||||
|
||||
mAttachments = (LinearLayout)findViewById(R.id.attachments);
|
||||
@ -599,7 +600,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
mQuotedTextBar = findViewById(R.id.quoted_text_bar);
|
||||
mQuotedTextEdit = (ImageButton)findViewById(R.id.quoted_text_edit);
|
||||
mQuotedTextDelete = (ImageButton)findViewById(R.id.quoted_text_delete);
|
||||
mQuotedText = (EditText)findViewById(R.id.quoted_text);
|
||||
mQuotedText = (EolConvertingEditText)findViewById(R.id.quoted_text);
|
||||
mQuotedText.getInputExtras(true).putBoolean("allowEmoji", true);
|
||||
|
||||
mQuotedHTML = (MessageWebView) findViewById(R.id.quoted_html);
|
||||
@ -949,7 +950,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
|
||||
// Only use EXTRA_TEXT if the body hasn't already been set by the mailto URI
|
||||
if (text != null && mMessageContentView.getText().length() == 0) {
|
||||
mMessageContentView.setText(text);
|
||||
mMessageContentView.setCharacters(text);
|
||||
}
|
||||
|
||||
String type = intent.getType();
|
||||
@ -1307,7 +1308,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
boolean signatureBeforeQuotedText = mAccount.isSignatureBeforeQuotedText();
|
||||
|
||||
// Get the user-supplied text
|
||||
String text = mMessageContentView.getText().toString();
|
||||
String text = mMessageContentView.getCharacters();
|
||||
|
||||
// Handle HTML separate from the rest of the text content
|
||||
if (messageFormat == SimpleMessageFormat.HTML) {
|
||||
@ -1391,7 +1392,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
}
|
||||
|
||||
if (includeQuotedText) {
|
||||
String quotedText = mQuotedText.getText().toString();
|
||||
String quotedText = mQuotedText.getCharacters();
|
||||
if (replyAfterQuote) {
|
||||
composedMessageOffset = quotedText.length() + "\n".length();
|
||||
text = quotedText + "\n" + text;
|
||||
@ -1661,7 +1662,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
|
||||
// If we're not using the standard identity of signature, append it on to the identity blob.
|
||||
if (mIdentity.getSignatureUse() && mSignatureChanged) {
|
||||
uri.appendQueryParameter(IdentityField.SIGNATURE.value(), mSignatureView.getText().toString());
|
||||
uri.appendQueryParameter(IdentityField.SIGNATURE.value(), mSignatureView.getCharacters());
|
||||
}
|
||||
|
||||
if (mIdentityChanged) {
|
||||
@ -1766,7 +1767,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
private String appendSignature(String originalText) {
|
||||
String text = originalText;
|
||||
if (mIdentity.getSignatureUse()) {
|
||||
String signature = mSignatureView.getText().toString();
|
||||
String signature = mSignatureView.getCharacters();
|
||||
|
||||
if (signature != null && !signature.contentEquals("")) {
|
||||
text += "\n" + signature;
|
||||
@ -1783,7 +1784,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
private String getSignatureHtml() {
|
||||
String signature = "";
|
||||
if (mIdentity.getSignatureUse()) {
|
||||
signature = mSignatureView.getText().toString();
|
||||
signature = mSignatureView.getCharacters();
|
||||
if(!StringUtils.isNullOrEmpty(signature)) {
|
||||
signature = HtmlConverter.textToHtmlFragment("\n" + signature);
|
||||
}
|
||||
@ -2320,7 +2321,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
|
||||
private void updateSignature() {
|
||||
if (mIdentity.getSignatureUse()) {
|
||||
mSignatureView.setText(mIdentity.getSignature());
|
||||
mSignatureView.setCharacters(mIdentity.getSignature());
|
||||
mSignatureView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
mSignatureView.setVisibility(View.GONE);
|
||||
@ -3006,7 +3007,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
// we'll display the whole message (including the quoted part) in the
|
||||
// composition window. If that's the case, try and convert it to text to
|
||||
// match the behavior in text mode.
|
||||
mMessageContentView.setText(getBodyTextFromMessage(message, SimpleMessageFormat.TEXT));
|
||||
mMessageContentView.setCharacters(getBodyTextFromMessage(message, SimpleMessageFormat.TEXT));
|
||||
mForcePlainText = true;
|
||||
|
||||
showOrHideQuotedText(quotedMode);
|
||||
@ -3025,7 +3026,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
|
||||
// Grab our reply text.
|
||||
String bodyText = text.substring(bodyOffset, bodyOffset + bodyLength);
|
||||
mMessageContentView.setText(HtmlConverter.htmlToText(bodyText));
|
||||
mMessageContentView.setCharacters(HtmlConverter.htmlToText(bodyText));
|
||||
|
||||
// Regenerate the quoted html without our user content in it.
|
||||
StringBuilder quotedHTML = new StringBuilder();
|
||||
@ -3101,10 +3102,15 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
quotedText.append(text.substring(bodyOffset + bodyLength));
|
||||
}
|
||||
|
||||
if (viewMessageContent) mMessageContentView.setText(bodyText);
|
||||
mQuotedText.setText(quotedText.toString());
|
||||
if (viewMessageContent) {
|
||||
mMessageContentView.setCharacters(bodyText);
|
||||
}
|
||||
|
||||
mQuotedText.setCharacters(quotedText);
|
||||
} else {
|
||||
if (viewMessageContent) mMessageContentView.setText(text);
|
||||
if (viewMessageContent) {
|
||||
mMessageContentView.setCharacters(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3228,7 +3234,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
mQuotedHTML.setText(mQuotedHtmlContent.getQuotedContent());
|
||||
|
||||
// TODO: Also strip the signature from the text/plain part
|
||||
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage,
|
||||
mQuotedText.setCharacters(quoteOriginalTextMessage(mSourceMessage,
|
||||
getBodyTextFromMessage(mSourceMessage, SimpleMessageFormat.TEXT), mQuoteStyle));
|
||||
|
||||
} else if (mQuotedTextFormat == SimpleMessageFormat.TEXT) {
|
||||
@ -3239,7 +3245,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
}
|
||||
}
|
||||
|
||||
mQuotedText.setText(quoteOriginalTextMessage(mSourceMessage, content, mQuoteStyle));
|
||||
mQuotedText.setCharacters(quoteOriginalTextMessage(mSourceMessage, content, mQuoteStyle));
|
||||
}
|
||||
|
||||
if (showQuotedText) {
|
||||
@ -3558,7 +3564,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
// Read message body from the "body" parameter.
|
||||
List<String> body = uri.getQueryParameters("body");
|
||||
if (!body.isEmpty()) {
|
||||
mMessageContentView.setText(body.get(0));
|
||||
mMessageContentView.setCharacters(body.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -3970,4 +3976,35 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
||||
private boolean includeQuotedText() {
|
||||
return (mQuotedTextMode == QuotedTextMode.SHOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link EditText} extension with methods that convert line endings from
|
||||
* {@code \r\n} to {@code \n} and back again when setting and getting text.
|
||||
*
|
||||
*/
|
||||
private static class EolConvertingEditText extends EditText {
|
||||
|
||||
public EolConvertingEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the text the EolConvertingEditText is displaying.
|
||||
*
|
||||
* @return A string with any line endings converted to {@code \r\n}.
|
||||
*/
|
||||
public String getCharacters() {
|
||||
return getText().toString().replace("\n", "\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string value of the EolConvertingEditText. Any line endings
|
||||
* in the string will be converted to {@code \n}.
|
||||
*
|
||||
* @param text
|
||||
*/
|
||||
public void setCharacters(CharSequence text) {
|
||||
setText(text.toString().replace("\r\n", "\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3416,7 +3416,7 @@ public class MimeUtility {
|
||||
if (part.isMimeType("text/plain")) {
|
||||
String bodyText = getTextFromPart(part);
|
||||
if (bodyText != null) {
|
||||
text = fixDraftTextBody(bodyText);
|
||||
text = bodyText;
|
||||
html = HtmlConverter.textToHtml(text);
|
||||
}
|
||||
} else if (part.isMimeType("multipart/alternative") &&
|
||||
@ -3427,9 +3427,9 @@ public class MimeUtility {
|
||||
String bodyText = getTextFromPart(bodyPart);
|
||||
if (bodyText != null) {
|
||||
if (text.length() == 0 && bodyPart.isMimeType("text/plain")) {
|
||||
text = fixDraftTextBody(bodyText);
|
||||
text = bodyText;
|
||||
} else if (html.length() == 0 && bodyPart.isMimeType("text/html")) {
|
||||
html = fixDraftTextBody(bodyText);
|
||||
html = bodyText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3437,21 +3437,4 @@ public class MimeUtility {
|
||||
|
||||
return new ViewableContainer(text, html, attachments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix line endings of text bodies in draft messages.
|
||||
*
|
||||
* <p>
|
||||
* We create drafts with LF line endings. The values in the identity header are based on that.
|
||||
* So we replace CRLF with LF when loading messages (from the server).
|
||||
* </p>
|
||||
*
|
||||
* @param text
|
||||
* The body text with CRLF line endings
|
||||
*
|
||||
* @return The text with LF line endings
|
||||
*/
|
||||
private static String fixDraftTextBody(String text) {
|
||||
return text.replace("\r\n", "\n");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user