From 73bc3c9fc6fec12249169d3131048582295a8949 Mon Sep 17 00:00:00 2001 From: Jesse Young Date: Wed, 21 Sep 2011 21:12:39 -0700 Subject: [PATCH] MMS fixes: prevent httpclient from using transfer-encoding: chunked since nginx doesn't supportit; allow forwarding mms when content-location not set --- AndroidManifest.xml | 2 +- src/org/envaya/kalsms/App.java | 14 ++-- .../envaya/kalsms/CheckMmsInboxService.java | 3 +- src/org/envaya/kalsms/IncomingMms.java | 12 ++-- src/org/envaya/kalsms/MmsObserver.java | 1 - src/org/envaya/kalsms/MmsPart.java | 57 ++++++++++++++++ src/org/envaya/kalsms/MmsUtils.java | 68 +++++++++---------- src/org/envaya/kalsms/task/HttpTask.java | 14 +++- src/org/envaya/kalsms/ui/Main.java | 8 +-- 9 files changed, 122 insertions(+), 57 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 59e354e..81c31b3 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:versionName="2.0-beta 3"> diff --git a/src/org/envaya/kalsms/App.java b/src/org/envaya/kalsms/App.java index 85f7bd0..1cd117e 100755 --- a/src/org/envaya/kalsms/App.java +++ b/src/org/envaya/kalsms/App.java @@ -657,6 +657,15 @@ public final class App extends Application { private HttpClient httpClient; + public HttpParams getDefaultHttpParams() + { + HttpParams httpParams = new BasicHttpParams(); + HttpConnectionParams.setConnectionTimeout(httpParams, 8000); + HttpConnectionParams.setSoTimeout(httpParams, 8000); + HttpProtocolParams.setContentCharset(httpParams, "UTF-8"); + return httpParams; + } + public synchronized HttpClient getHttpClient() { if (httpClient == null) @@ -664,10 +673,7 @@ public final class App extends Application { // via http://thinkandroid.wordpress.com/2009/12/31/creating-an-http-client-example/ // also http://hc.apache.org/httpclient-3.x/threading.html - HttpParams httpParams = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(httpParams, 8000); - HttpConnectionParams.setSoTimeout(httpParams, 8000); - HttpProtocolParams.setContentCharset(httpParams, "utf-8"); + HttpParams httpParams = getDefaultHttpParams(); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); diff --git a/src/org/envaya/kalsms/CheckMmsInboxService.java b/src/org/envaya/kalsms/CheckMmsInboxService.java index b430a56..a9d0207 100755 --- a/src/org/envaya/kalsms/CheckMmsInboxService.java +++ b/src/org/envaya/kalsms/CheckMmsInboxService.java @@ -34,9 +34,10 @@ public class CheckMmsInboxService extends IntentService { List messages = mmsUtils.getMessagesInInbox(); for (IncomingMms mms : messages) - { + { if (mmsUtils.isNewMms(mms)) { + app.log("New MMS id=" + mms.getId() + " in inbox"); // prevent forwarding MMS messages that existed in inbox // before KalSMS started, or re-forwarding MMS multiple // times if we don't delete them. diff --git a/src/org/envaya/kalsms/IncomingMms.java b/src/org/envaya/kalsms/IncomingMms.java index 36b090e..82d3217 100755 --- a/src/org/envaya/kalsms/IncomingMms.java +++ b/src/org/envaya/kalsms/IncomingMms.java @@ -11,7 +11,6 @@ import java.util.List; import org.apache.http.entity.mime.FormBodyPart; import org.apache.http.entity.mime.content.ByteArrayBody; import org.apache.http.entity.mime.content.ContentBody; -import org.apache.http.entity.mime.content.InputStreamBody; import org.apache.http.message.BasicNameValuePair; import org.envaya.kalsms.task.ForwarderTask; @@ -106,21 +105,24 @@ public class IncomingMms extends IncomingMessage { { if (contentType != null) { - contentType += "; charset=utf-8"; + contentType += "; charset=UTF-8"; } body = new ByteArrayBody(text.getBytes(), contentType, partName); } else { + // avoid using InputStreamBody because it forces the HTTP request + // to be sent using Transfer-Encoding: chunked, which is not + // supported by some web servers (including nginx) + try { - body = new InputStreamBody(part.openInputStream(), - contentType, partName); + body = new ByteArrayBody(part.getData(), contentType, partName); } catch (IOException ex) { - app.logError("Error opening data for " + part.toString(), ex); + app.logError("Error reading data for " + part.toString(), ex); continue; } } diff --git a/src/org/envaya/kalsms/MmsObserver.java b/src/org/envaya/kalsms/MmsObserver.java index c0aeb4f..3c9fb22 100755 --- a/src/org/envaya/kalsms/MmsObserver.java +++ b/src/org/envaya/kalsms/MmsObserver.java @@ -36,7 +36,6 @@ final class MmsObserver extends ContentObserver { @Override public void onChange(final boolean selfChange) { super.onChange(selfChange); - if (!selfChange) { // check MMS inbox in an IntentService since it may be slow diff --git a/src/org/envaya/kalsms/MmsPart.java b/src/org/envaya/kalsms/MmsPart.java index fe38bae..27b5af9 100755 --- a/src/org/envaya/kalsms/MmsPart.java +++ b/src/org/envaya/kalsms/MmsPart.java @@ -1,7 +1,9 @@ package org.envaya.kalsms; import android.net.Uri; +import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; public class MmsPart { @@ -11,6 +13,7 @@ public class MmsPart { private String name; private String text; private String cid; + private String dataFile; public MmsPart(App app, long partId) { @@ -88,6 +91,60 @@ public class MmsPart { return text; } + public void setDataFile(String dataFile) + { + this.dataFile = dataFile; + } + + public String getDataFile() + { + return dataFile; + } + + public long getDataLength() + { + if (dataFile != null) + { + return new File(dataFile).length(); + } + else + { + return -1; + } + } + + public byte[] getData() throws IOException + { + int length = (int)getDataLength(); + byte[] bytes = new byte[length]; + + int offset = 0; + int bytesRead = 0; + + InputStream in = openInputStream(); + + while (offset < bytes.length) + { + bytesRead = in.read(bytes, offset, bytes.length - offset); + + if (bytesRead < 0) + { + break; + } + + offset += bytesRead; + } + + in.close(); + + if (offset < bytes.length) + { + throw new IOException("Failed to read complete data of MMS part"); + } + + return bytes; + } + /* * For multimedia parts, the _data column of the MMS Parts table contains the * path on the Android filesystem containing that file, and openInputStream diff --git a/src/org/envaya/kalsms/MmsUtils.java b/src/org/envaya/kalsms/MmsUtils.java index 6027d84..d20e51b 100755 --- a/src/org/envaya/kalsms/MmsUtils.java +++ b/src/org/envaya/kalsms/MmsUtils.java @@ -4,6 +4,7 @@ package org.envaya.kalsms; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; +import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -29,7 +30,7 @@ public class MmsUtils private static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84; // todo -- prevent unbounded growth? - private final Set seenMmsContentLocations = new HashSet(); + private final Set seenMmsIds = new HashSet(); private App app; private ContentResolver contentResolver; @@ -43,7 +44,7 @@ public class MmsUtils private List getMmsParts(long id) { Cursor cur = contentResolver.query(PART_URI, new String[] { - "_id", "ct", "name", "text", "cid" + "_id", "ct", "name", "text", "cid", "_data" }, "mid = ?", new String[] { "" + id }, null); // assume that if there is at least one part saved in database @@ -58,7 +59,9 @@ public class MmsUtils MmsPart part = new MmsPart(app, partId); part.setContentType(cur.getString(1)); part.setName(cur.getString(2)); - + + part.setDataFile(cur.getString(5)); + // todo interpret charset like com.google.android.mms.pdu.EncodedStringValue part.setText(cur.getString(3)); @@ -105,18 +108,18 @@ public class MmsUtils Cursor c = contentResolver.query(INBOX_URI, new String[] {"_id", "ct_l"}, - "m_type = ? AND ct_l is not NULL", new String[] { m_type }, null); - + "m_type = ? ", new String[] { m_type }, null); + List messages = new ArrayList(); while (c.moveToNext()) { long id = c.getLong(0); - + IncomingMms mms = new IncomingMms(app, getSenderNumber(id), id); mms.setContentLocation(c.getString(1)); - + for (MmsPart part : getMmsParts(id)) { mms.addPart(part); @@ -129,48 +132,39 @@ public class MmsUtils return messages; } - public boolean deleteFromInbox(IncomingMms mms) + public synchronized boolean deleteFromInbox(IncomingMms mms) { - String contentLocation = mms.getContentLocation(); - - int res; - if (contentLocation != null) - { - Uri uri = Uri.parse("content://mms/inbox"); - - /* - * Delete by content location (ct_l) rather than _id so that - * M-Notification.ind and M-Retrieve.conf messages are both deleted - * (otherwise it would remain in Messaging inbox with a Download button) - */ - - res = contentResolver.delete(uri, - "ct_l = ?", - new String[] { contentLocation }); + long id = mms.getId(); + + Uri uri = Uri.parse("content://mms/inbox/" + id); + int res = contentResolver.delete(uri, null, null); + + if (res > 0) + { + app.log("MMS id="+id+" deleted from inbox"); + + // remove id from set because Messaging app reuses ids + // of deleted messages. + // TODO: handle reuse of IDs deleted directly through Messaging + // app while KalSMS is running + seenMmsIds.remove(id); } else { - app.log("mms has no content-location"); - Uri uri = Uri.parse("content://mms/inbox/" + mms.getId()); - res = contentResolver.delete(uri, null, null); + app.log("MMS id="+id+" could not be deleted from inbox"); } - - app.log(res + " rows deleted"); return res > 0; } public synchronized void markOldMms(IncomingMms mms) { - String contentLocation = mms.getContentLocation(); - if (contentLocation != null) - { - seenMmsContentLocations.add(contentLocation); - } + long id = mms.getId(); + seenMmsIds.add(id); } public synchronized boolean isNewMms(IncomingMms mms) { - String contentLocation = mms.getContentLocation(); - return contentLocation != null && !seenMmsContentLocations.contains(contentLocation); - } + long id = mms.getId(); + return !seenMmsIds.contains(id); + } } diff --git a/src/org/envaya/kalsms/task/HttpTask.java b/src/org/envaya/kalsms/task/HttpTask.java index c0207e6..02589ad 100755 --- a/src/org/envaya/kalsms/task/HttpTask.java +++ b/src/org/envaya/kalsms/task/HttpTask.java @@ -8,6 +8,7 @@ import android.os.AsyncTask; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -19,13 +20,17 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.http.HttpResponse; +import org.apache.http.HttpVersion; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.FormBodyPart; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.StringBody; +import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; import org.envaya.kalsms.App; import org.envaya.kalsms.Base64Coder; import org.envaya.kalsms.OutgoingMessage; @@ -109,27 +114,30 @@ public class HttpTask extends AsyncTask { { MultipartEntity entity = new MultipartEntity();//HttpMultipartMode.BROWSER_COMPATIBLE); + Charset charset = Charset.forName("UTF-8"); + for (BasicNameValuePair param : params) { - entity.addPart(param.getName(), new StringBody(param.getValue())); + entity.addPart(param.getName(), new StringBody(param.getValue(), charset)); } for (FormBodyPart formPart : formParts) { entity.addPart(formPart); } - post.setEntity(entity); + post.setEntity(entity); } else { post.setEntity(new UrlEncodedFormEntity(params)); } + HttpClient client = app.getHttpClient(); + String signature = getSignature(); post.setHeader("X-Kalsms-Signature", signature); - HttpClient client = app.getHttpClient(); HttpResponse response = client.execute(post); int statusCode = response.getStatusLine().getStatusCode(); diff --git a/src/org/envaya/kalsms/ui/Main.java b/src/org/envaya/kalsms/ui/Main.java index e7dff8b..65e9e16 100755 --- a/src/org/envaya/kalsms/ui/Main.java +++ b/src/org/envaya/kalsms/ui/Main.java @@ -3,14 +3,11 @@ package org.envaya.kalsms.ui; import org.envaya.kalsms.task.HttpTask; import android.app.Activity; import android.content.BroadcastReceiver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; -import android.text.Html; import android.text.method.ScrollingMovementMethod; import android.view.Menu; import android.view.MenuInflater; @@ -18,6 +15,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.ScrollView; import android.widget.TextView; +import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.message.BasicNameValuePair; import org.envaya.kalsms.App; @@ -92,13 +90,13 @@ public class Main extends Activity { case R.id.check_now: app.checkOutgoingMessages(); return true; - case R.id.retry_now: + case R.id.retry_now: app.retryStuckMessages(); return true; case R.id.forward_inbox: startActivity(new Intent(this, ForwardInbox.class)); return true; - case R.id.help: + case R.id.help: startActivity(new Intent(this, Help.class)); return true; case R.id.test: