5
0
mirror of https://github.com/cwinfo/envayasms.git synced 2024-09-18 21:39:34 +00:00

MMS fixes: prevent httpclient from using transfer-encoding: chunked since nginx doesn't supportit; allow forwarding mms when content-location not set

This commit is contained in:
Jesse Young 2011-09-21 21:12:39 -07:00
parent dbf35364ca
commit 73bc3c9fc6
9 changed files with 122 additions and 57 deletions

View File

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.envaya.kalsms" package="org.envaya.kalsms"
android:versionCode="3" android:versionCode="3"
android:versionName="2.0-beta 2"> android:versionName="2.0-beta 3">
<uses-sdk android:minSdkVersion="4" /> <uses-sdk android:minSdkVersion="4" />

View File

@ -657,6 +657,15 @@ public final class App extends Application {
private HttpClient httpClient; 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() public synchronized HttpClient getHttpClient()
{ {
if (httpClient == null) 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/ // via http://thinkandroid.wordpress.com/2009/12/31/creating-an-http-client-example/
// also http://hc.apache.org/httpclient-3.x/threading.html // also http://hc.apache.org/httpclient-3.x/threading.html
HttpParams httpParams = new BasicHttpParams(); HttpParams httpParams = getDefaultHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParams, 8000);
HttpConnectionParams.setSoTimeout(httpParams, 8000);
HttpProtocolParams.setContentCharset(httpParams, "utf-8");
SchemeRegistry registry = new SchemeRegistry(); SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));

View File

@ -34,9 +34,10 @@ public class CheckMmsInboxService extends IntentService
{ {
List<IncomingMms> messages = mmsUtils.getMessagesInInbox(); List<IncomingMms> messages = mmsUtils.getMessagesInInbox();
for (IncomingMms mms : messages) for (IncomingMms mms : messages)
{ {
if (mmsUtils.isNewMms(mms)) if (mmsUtils.isNewMms(mms))
{ {
app.log("New MMS id=" + mms.getId() + " in inbox");
// prevent forwarding MMS messages that existed in inbox // prevent forwarding MMS messages that existed in inbox
// before KalSMS started, or re-forwarding MMS multiple // before KalSMS started, or re-forwarding MMS multiple
// times if we don't delete them. // times if we don't delete them.

View File

@ -11,7 +11,6 @@ import java.util.List;
import org.apache.http.entity.mime.FormBodyPart; import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.content.ByteArrayBody; import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.ContentBody; import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.envaya.kalsms.task.ForwarderTask; import org.envaya.kalsms.task.ForwarderTask;
@ -106,21 +105,24 @@ public class IncomingMms extends IncomingMessage {
{ {
if (contentType != null) if (contentType != null)
{ {
contentType += "; charset=utf-8"; contentType += "; charset=UTF-8";
} }
body = new ByteArrayBody(text.getBytes(), contentType, partName); body = new ByteArrayBody(text.getBytes(), contentType, partName);
} }
else 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 try
{ {
body = new InputStreamBody(part.openInputStream(), body = new ByteArrayBody(part.getData(), contentType, partName);
contentType, partName);
} }
catch (IOException ex) catch (IOException ex)
{ {
app.logError("Error opening data for " + part.toString(), ex); app.logError("Error reading data for " + part.toString(), ex);
continue; continue;
} }
} }

View File

@ -36,7 +36,6 @@ final class MmsObserver extends ContentObserver {
@Override @Override
public void onChange(final boolean selfChange) { public void onChange(final boolean selfChange) {
super.onChange(selfChange); super.onChange(selfChange);
if (!selfChange) if (!selfChange)
{ {
// check MMS inbox in an IntentService since it may be slow // check MMS inbox in an IntentService since it may be slow

View File

@ -1,7 +1,9 @@
package org.envaya.kalsms; package org.envaya.kalsms;
import android.net.Uri; import android.net.Uri;
import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
public class MmsPart { public class MmsPart {
@ -11,6 +13,7 @@ public class MmsPart {
private String name; private String name;
private String text; private String text;
private String cid; private String cid;
private String dataFile;
public MmsPart(App app, long partId) public MmsPart(App app, long partId)
{ {
@ -88,6 +91,60 @@ public class MmsPart {
return text; 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 * For multimedia parts, the _data column of the MMS Parts table contains the
* path on the Android filesystem containing that file, and openInputStream * path on the Android filesystem containing that file, and openInputStream

View File

@ -4,6 +4,7 @@ package org.envaya.kalsms;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -29,7 +30,7 @@ public class MmsUtils
private static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84; private static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
// todo -- prevent unbounded growth? // todo -- prevent unbounded growth?
private final Set<String> seenMmsContentLocations = new HashSet<String>(); private final Set<Long> seenMmsIds = new HashSet<Long>();
private App app; private App app;
private ContentResolver contentResolver; private ContentResolver contentResolver;
@ -43,7 +44,7 @@ public class MmsUtils
private List<MmsPart> getMmsParts(long id) private List<MmsPart> getMmsParts(long id)
{ {
Cursor cur = contentResolver.query(PART_URI, new String[] { Cursor cur = contentResolver.query(PART_URI, new String[] {
"_id", "ct", "name", "text", "cid" "_id", "ct", "name", "text", "cid", "_data"
}, "mid = ?", new String[] { "" + id }, null); }, "mid = ?", new String[] { "" + id }, null);
// assume that if there is at least one part saved in database // 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); MmsPart part = new MmsPart(app, partId);
part.setContentType(cur.getString(1)); part.setContentType(cur.getString(1));
part.setName(cur.getString(2)); part.setName(cur.getString(2));
part.setDataFile(cur.getString(5));
// todo interpret charset like com.google.android.mms.pdu.EncodedStringValue // todo interpret charset like com.google.android.mms.pdu.EncodedStringValue
part.setText(cur.getString(3)); part.setText(cur.getString(3));
@ -105,18 +108,18 @@ public class MmsUtils
Cursor c = contentResolver.query(INBOX_URI, Cursor c = contentResolver.query(INBOX_URI,
new String[] {"_id", "ct_l"}, 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<IncomingMms> messages = new ArrayList<IncomingMms>(); List<IncomingMms> messages = new ArrayList<IncomingMms>();
while (c.moveToNext()) while (c.moveToNext())
{ {
long id = c.getLong(0); long id = c.getLong(0);
IncomingMms mms = new IncomingMms(app, getSenderNumber(id), id); IncomingMms mms = new IncomingMms(app, getSenderNumber(id), id);
mms.setContentLocation(c.getString(1)); mms.setContentLocation(c.getString(1));
for (MmsPart part : getMmsParts(id)) for (MmsPart part : getMmsParts(id))
{ {
mms.addPart(part); mms.addPart(part);
@ -129,48 +132,39 @@ public class MmsUtils
return messages; return messages;
} }
public boolean deleteFromInbox(IncomingMms mms) public synchronized boolean deleteFromInbox(IncomingMms mms)
{ {
String contentLocation = mms.getContentLocation(); long id = mms.getId();
int res; Uri uri = Uri.parse("content://mms/inbox/" + id);
if (contentLocation != null) int res = contentResolver.delete(uri, null, null);
{
Uri uri = Uri.parse("content://mms/inbox"); if (res > 0)
{
/* app.log("MMS id="+id+" deleted from inbox");
* Delete by content location (ct_l) rather than _id so that
* M-Notification.ind and M-Retrieve.conf messages are both deleted // remove id from set because Messaging app reuses ids
* (otherwise it would remain in Messaging inbox with a Download button) // of deleted messages.
*/ // TODO: handle reuse of IDs deleted directly through Messaging
// app while KalSMS is running
res = contentResolver.delete(uri, seenMmsIds.remove(id);
"ct_l = ?",
new String[] { contentLocation });
} }
else else
{ {
app.log("mms has no content-location"); app.log("MMS id="+id+" could not be deleted from inbox");
Uri uri = Uri.parse("content://mms/inbox/" + mms.getId());
res = contentResolver.delete(uri, null, null);
} }
app.log(res + " rows deleted");
return res > 0; return res > 0;
} }
public synchronized void markOldMms(IncomingMms mms) public synchronized void markOldMms(IncomingMms mms)
{ {
String contentLocation = mms.getContentLocation(); long id = mms.getId();
if (contentLocation != null) seenMmsIds.add(id);
{
seenMmsContentLocations.add(contentLocation);
}
} }
public synchronized boolean isNewMms(IncomingMms mms) public synchronized boolean isNewMms(IncomingMms mms)
{ {
String contentLocation = mms.getContentLocation(); long id = mms.getId();
return contentLocation != null && !seenMmsContentLocations.contains(contentLocation); return !seenMmsIds.contains(id);
} }
} }

View File

@ -8,6 +8,7 @@ import android.os.AsyncTask;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
@ -19,13 +20,17 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.FormBodyPart; import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.StringBody; 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.message.BasicNameValuePair;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.envaya.kalsms.App; import org.envaya.kalsms.App;
import org.envaya.kalsms.Base64Coder; import org.envaya.kalsms.Base64Coder;
import org.envaya.kalsms.OutgoingMessage; import org.envaya.kalsms.OutgoingMessage;
@ -109,27 +114,30 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
{ {
MultipartEntity entity = new MultipartEntity();//HttpMultipartMode.BROWSER_COMPATIBLE); MultipartEntity entity = new MultipartEntity();//HttpMultipartMode.BROWSER_COMPATIBLE);
Charset charset = Charset.forName("UTF-8");
for (BasicNameValuePair param : params) 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) for (FormBodyPart formPart : formParts)
{ {
entity.addPart(formPart); entity.addPart(formPart);
} }
post.setEntity(entity); post.setEntity(entity);
} }
else else
{ {
post.setEntity(new UrlEncodedFormEntity(params)); post.setEntity(new UrlEncodedFormEntity(params));
} }
HttpClient client = app.getHttpClient();
String signature = getSignature(); String signature = getSignature();
post.setHeader("X-Kalsms-Signature", signature); post.setHeader("X-Kalsms-Signature", signature);
HttpClient client = app.getHttpClient();
HttpResponse response = client.execute(post); HttpResponse response = client.execute(post);
int statusCode = response.getStatusLine().getStatusCode(); int statusCode = response.getStatusLine().getStatusCode();

View File

@ -3,14 +3,11 @@ package org.envaya.kalsms.ui;
import org.envaya.kalsms.task.HttpTask; import org.envaya.kalsms.task.HttpTask;
import android.app.Activity; import android.app.Activity;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.Html;
import android.text.method.ScrollingMovementMethod; import android.text.method.ScrollingMovementMethod;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -18,6 +15,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import java.util.List;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.envaya.kalsms.App; import org.envaya.kalsms.App;
@ -92,13 +90,13 @@ public class Main extends Activity {
case R.id.check_now: case R.id.check_now:
app.checkOutgoingMessages(); app.checkOutgoingMessages();
return true; return true;
case R.id.retry_now: case R.id.retry_now:
app.retryStuckMessages(); app.retryStuckMessages();
return true; return true;
case R.id.forward_inbox: case R.id.forward_inbox:
startActivity(new Intent(this, ForwardInbox.class)); startActivity(new Intent(this, ForwardInbox.class));
return true; return true;
case R.id.help: case R.id.help:
startActivity(new Intent(this, Help.class)); startActivity(new Intent(this, Help.class));
return true; return true;
case R.id.test: case R.id.test: