mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Revised support for outgoing attachments
This commit is contained in:
parent
fd045f2354
commit
4bb337a3a0
@ -1,7 +1,7 @@
|
||||
package org.whispersystems.textsecure;
|
||||
|
||||
public class Release {
|
||||
public static final String PUSH_SERVICE_URL = "https://gcm.textsecure.whispersystems.org";
|
||||
// public static final String PUSH_SERVICE_URL = "http://192.168.1.135:8080";
|
||||
public static final boolean ENFORCE_SSL = true;
|
||||
// public static final String PUSH_SERVICE_URL = "https://gcm.textsecure.whispersystems.org";
|
||||
public static final String PUSH_SERVICE_URL = "http://192.168.1.135:8080";
|
||||
public static final boolean ENFORCE_SSL = false;
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles providing lookups, serializing, and deserializing the RedPhone directory.
|
||||
@ -81,6 +82,26 @@ public class NumberFilter {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean containsNumbers(List<String> numbers) {
|
||||
try {
|
||||
if (bloomFilter == null) return false;
|
||||
if (numbers == null || numbers.size() == 0) return false;
|
||||
|
||||
BloomFilter filter = new BloomFilter(bloomFilter, hashCount);
|
||||
|
||||
for (String number : numbers) {
|
||||
if (!filter.contains(number)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
Log.w("NumberFilter", ioe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void update(File bloomFilter, long capacity, int hashCount, String version)
|
||||
{
|
||||
if (this.bloomFilter != null)
|
||||
|
@ -9,6 +9,19 @@ public class OutgoingPushMessage {
|
||||
private String messageText;
|
||||
private List<String> attachments;
|
||||
|
||||
public OutgoingPushMessage(String destination, String messageText) {
|
||||
this.destinations = new LinkedList<String>();
|
||||
this.attachments = new LinkedList<String>();
|
||||
this.messageText = messageText;
|
||||
this.destinations.add(destination);
|
||||
}
|
||||
|
||||
public OutgoingPushMessage(List<String> destinations, String messageText) {
|
||||
this.destinations = destinations;
|
||||
this.messageText = messageText;
|
||||
this.attachments = new LinkedList<String>();
|
||||
}
|
||||
|
||||
public OutgoingPushMessage(List<String> destinations, String messageText,
|
||||
List<String> attachments)
|
||||
{
|
||||
@ -17,13 +30,6 @@ public class OutgoingPushMessage {
|
||||
this.attachments = attachments;
|
||||
}
|
||||
|
||||
public OutgoingPushMessage(String destination, String messageText) {
|
||||
this.destinations = new LinkedList<String>();
|
||||
this.attachments = new LinkedList<String>();
|
||||
this.messageText = messageText;
|
||||
this.destinations.add(destination);
|
||||
}
|
||||
|
||||
public List<String> getDestinations() {
|
||||
return destinations;
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
public class PushAttachment {
|
||||
|
||||
private final String contentType;
|
||||
private final byte[] data;
|
||||
|
||||
public PushAttachment(String contentType, byte[] data) {
|
||||
this.contentType = contentType;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@ package org.whispersystems.textsecure.push;
|
||||
import android.content.Context;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.google.thoughtcrimegson.Gson;
|
||||
import org.whispersystems.textsecure.R;
|
||||
@ -22,12 +23,16 @@ import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class PushServiceSocket {
|
||||
@ -39,6 +44,7 @@ public class PushServiceSocket {
|
||||
|
||||
private static final String DIRECTORY_PATH = "/v1/directory/";
|
||||
private static final String MESSAGE_PATH = "/v1/messages/";
|
||||
private static final String ATTACHMENT_PATH = "/v1/attachments/%s";
|
||||
|
||||
private final String localNumber;
|
||||
private final String password;
|
||||
@ -72,6 +78,26 @@ public class PushServiceSocket {
|
||||
throws IOException, RateLimitException
|
||||
{
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendMessage(List<String> recipients, String messageText)
|
||||
throws IOException, RateLimitException
|
||||
{
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
public void sendMessage(List<String> recipients, String messageText,
|
||||
List<PushAttachment> attachments)
|
||||
throws IOException, RateLimitException
|
||||
{
|
||||
List<String> attachmentIds = sendAttachments(attachments);
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds);
|
||||
sendMessage(message);
|
||||
}
|
||||
|
||||
private void sendMessage(OutgoingPushMessage message) throws IOException, RateLimitException {
|
||||
String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(message));
|
||||
GcmMessageResponse response = new Gson().fromJson(responseText, GcmMessageResponse.class);
|
||||
|
||||
@ -79,12 +105,41 @@ public class PushServiceSocket {
|
||||
throw new IOException("Got send failure: " + response.getFailure().get(0));
|
||||
}
|
||||
|
||||
private List<String> sendAttachments(List<PushAttachment> attachments)
|
||||
throws IOException, RateLimitException
|
||||
{
|
||||
List<String> attachmentIds = new LinkedList<String>();
|
||||
|
||||
for (PushAttachment attachment : attachments) {
|
||||
attachmentIds.add(sendAttachment(attachment));
|
||||
}
|
||||
|
||||
return attachmentIds;
|
||||
}
|
||||
|
||||
private String sendAttachment(PushAttachment attachment) throws IOException, RateLimitException {
|
||||
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, ""),
|
||||
"GET", null, "Content-Location");
|
||||
|
||||
String contentLocation = response.first;
|
||||
Log.w("PushServiceSocket", "Got attachment content location: " + contentLocation);
|
||||
|
||||
if (contentLocation == null) {
|
||||
throw new IOException("Server failed to allocate an attachment key!");
|
||||
}
|
||||
|
||||
uploadExternalFile("PUT", contentLocation, attachment.getData());
|
||||
|
||||
return new Gson().fromJson(response.second, AttachmentDescriptor.class).getId();
|
||||
}
|
||||
|
||||
public void retrieveDirectory(Context context ) {
|
||||
try {
|
||||
DirectoryDescriptor directoryDescriptor = new Gson().fromJson(makeRequest(DIRECTORY_PATH, "GET", null),
|
||||
DirectoryDescriptor.class);
|
||||
|
||||
File directoryData = downloadExternalFile(context, directoryDescriptor.getUrl());
|
||||
File directoryData = File.createTempFile("directory", ".dat", context.getFilesDir());
|
||||
downloadExternalFile(directoryDescriptor.getUrl(), directoryData);
|
||||
|
||||
NumberFilter.getInstance(context).update(directoryData,
|
||||
directoryDescriptor.getCapacity(),
|
||||
@ -98,32 +153,81 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
private File downloadExternalFile(Context context, String url) throws IOException {
|
||||
File download = File.createTempFile("directory", ".dat", context.getFilesDir());
|
||||
URL downloadUrl = new URL(url);
|
||||
HttpsURLConnection connection = (HttpsURLConnection)downloadUrl.openConnection();
|
||||
private void downloadExternalFile(String url, File localDestination)
|
||||
throws IOException
|
||||
{
|
||||
URL downloadUrl = new URL(url);
|
||||
HttpsURLConnection connection = (HttpsURLConnection) downloadUrl.openConnection();
|
||||
connection.setDoInput(true);
|
||||
|
||||
if (connection.getResponseCode() != 200) {
|
||||
throw new IOException("Bad response: " + connection.getResponseCode());
|
||||
try {
|
||||
if (connection.getResponseCode() != 200) {
|
||||
throw new IOException("Bad response: " + connection.getResponseCode());
|
||||
}
|
||||
|
||||
OutputStream output = new FileOutputStream(localDestination);
|
||||
InputStream input = new GZIPInputStream(connection.getInputStream());
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
while ((read = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, read);
|
||||
}
|
||||
|
||||
output.close();
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream output = new FileOutputStream(download);
|
||||
InputStream input = new GZIPInputStream(connection.getInputStream());
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
private void uploadExternalFile(String method, String url, byte[] data)
|
||||
throws IOException
|
||||
{
|
||||
URL uploadUrl = new URL(url);
|
||||
HttpsURLConnection connection = (HttpsURLConnection) uploadUrl.openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestMethod(method);
|
||||
connection.setRequestProperty("Content-Type", "application/octet-stream");
|
||||
connection.connect();
|
||||
|
||||
while ((read = input.read(buffer)) != -1) {
|
||||
output.write(buffer, 0, read);
|
||||
try {
|
||||
OutputStream out = connection.getOutputStream();
|
||||
out.write(data);
|
||||
out.close();
|
||||
|
||||
if (connection.getResponseCode() != 200) {
|
||||
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
|
||||
}
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
output.close();
|
||||
private Pair<String, String> makeRequestForResponseHeader(String urlFragment, String method,
|
||||
String body, String responseHeader)
|
||||
throws IOException, RateLimitException
|
||||
{
|
||||
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
|
||||
String response = Util.readFully(connection.getInputStream());
|
||||
String headerValue = connection.getHeaderField(responseHeader);
|
||||
connection.disconnect();
|
||||
|
||||
return download;
|
||||
return new Pair<String, String>(headerValue, response);
|
||||
}
|
||||
|
||||
private String makeRequest(String urlFragment, String method, String body)
|
||||
throws IOException, RateLimitException
|
||||
{
|
||||
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
|
||||
String response = Util.readFully(connection.getInputStream());
|
||||
|
||||
connection.disconnect();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private HttpURLConnection makeBaseRequest(String urlFragment, String method, String body)
|
||||
throws IOException, RateLimitException
|
||||
{
|
||||
HttpURLConnection connection = getConnection(urlFragment, method);
|
||||
|
||||
@ -148,7 +252,7 @@ public class PushServiceSocket {
|
||||
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
|
||||
}
|
||||
|
||||
return Util.readFully(connection.getInputStream());
|
||||
return connection;
|
||||
}
|
||||
|
||||
private HttpURLConnection getConnection(String urlFragment, String method) throws IOException {
|
||||
@ -220,4 +324,16 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
private class AttachmentDescriptor {
|
||||
private String id;
|
||||
|
||||
public AttachmentDescriptor(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.SendReceiveService.ToastHandler;
|
||||
import org.thoughtcrime.securesms.transport.MmsTransport;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.transport.UniversalTransport;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
@ -52,10 +53,10 @@ public class MmsSender {
|
||||
}
|
||||
|
||||
private void handleSendMms(MasterSecret masterSecret, Intent intent) {
|
||||
long messageId = intent.getLongExtra("message_id", -1);
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
|
||||
MmsTransport transport = new MmsTransport(context, masterSecret);
|
||||
long messageId = intent.getLongExtra("message_id", -1);
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
|
||||
UniversalTransport transport = new UniversalTransport(context, masterSecret);
|
||||
|
||||
try {
|
||||
SendReq[] messages = database.getOutgoingMessages(masterSecret, messageId);
|
||||
|
@ -5,12 +5,21 @@ import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.PartParser;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.push.PushAttachment;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.RateLimitException;
|
||||
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import ws.com.google.android.mms.ContentType;
|
||||
import ws.com.google.android.mms.pdu.PduBody;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
public class PushTransport extends BaseTransport {
|
||||
|
||||
@ -40,4 +49,37 @@ public class PushTransport extends BaseTransport {
|
||||
throw new IOException("Rate limit exceeded.");
|
||||
}
|
||||
}
|
||||
|
||||
public void deliver(SendReq message, List<String> destinations) throws IOException {
|
||||
try {
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
String password = TextSecurePreferences.getPushServerPassword(context);
|
||||
PushServiceSocket socket = new PushServiceSocket(context, localNumber, password);
|
||||
String messageText = PartParser.getMessageText(message.getBody());
|
||||
List<PushAttachment> attachments = getAttachmentsFromBody(message.getBody());
|
||||
|
||||
if (attachments.isEmpty()) socket.sendMessage(destinations, messageText);
|
||||
else socket.sendMessage(destinations, messageText, attachments);
|
||||
} catch (RateLimitException e) {
|
||||
Log.w("PushTransport", e);
|
||||
throw new IOException("Rate limit exceeded.");
|
||||
}
|
||||
}
|
||||
|
||||
private List<PushAttachment> getAttachmentsFromBody(PduBody body) {
|
||||
List<PushAttachment> attachments = new LinkedList<PushAttachment>();
|
||||
|
||||
for (int i=0;i<body.getPartsNum();i++) {
|
||||
String contentType = Util.toIsoString(body.getPart(i).getContentType());
|
||||
|
||||
if (ContentType.isImageType(contentType) ||
|
||||
ContentType.isAudioType(contentType) ||
|
||||
ContentType.isVideoType(contentType))
|
||||
{
|
||||
attachments.add(new PushAttachment(contentType, body.getPart(i).getData()));
|
||||
}
|
||||
}
|
||||
|
||||
return attachments;
|
||||
}
|
||||
}
|
||||
|
@ -2,26 +2,35 @@ package org.thoughtcrime.securesms.transport;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.directory.NumberFilter;
|
||||
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
public class UniversalTransport {
|
||||
|
||||
private final Context context;
|
||||
private final PushTransport pushTransport;
|
||||
private final SmsTransport smsTransport;
|
||||
private final MmsTransport mmsTransport;
|
||||
|
||||
public UniversalTransport(Context context, MasterSecret masterSecret) {
|
||||
this.context = context;
|
||||
this.pushTransport = new PushTransport(context, masterSecret);
|
||||
this.smsTransport = new SmsTransport(context, masterSecret);
|
||||
this.mmsTransport = new MmsTransport(context, masterSecret);
|
||||
}
|
||||
|
||||
public void deliver(SmsMessageRecord message) throws UndeliverableMessageException {
|
||||
@ -31,8 +40,7 @@ public class UniversalTransport {
|
||||
}
|
||||
|
||||
Recipient recipient = message.getIndividualRecipient();
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
String number = PhoneNumberFormatter.formatNumber(recipient.getNumber(), localNumber);
|
||||
String number = Util.canonicalizeNumber(context, recipient.getNumber());
|
||||
|
||||
if (NumberFilter.getInstance(context).containsNumber(number)) {
|
||||
try {
|
||||
@ -47,4 +55,51 @@ public class UniversalTransport {
|
||||
smsTransport.deliver(message);
|
||||
}
|
||||
}
|
||||
|
||||
public Pair<byte[], Integer> deliver(SendReq mediaMessage) throws UndeliverableMessageException {
|
||||
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
|
||||
List<String> destinations = getMediaDestinations(mediaMessage);
|
||||
|
||||
if (NumberFilter.getInstance(context).containsNumbers(destinations)) {
|
||||
try {
|
||||
Log.w("UniversalTransport", "Delivering media message with GCM...");
|
||||
pushTransport.deliver(mediaMessage, destinations);
|
||||
return new Pair<byte[], Integer>("push".getBytes("UTF-8"), 0);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("UniversalTransport", ioe);
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
} else {
|
||||
Log.w("UniversalTransport", "Delivering media message with MMS...");
|
||||
return mmsTransport.deliver(mediaMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getMediaDestinations(SendReq mediaMessage) {
|
||||
LinkedList<String> destinations = new LinkedList<String>();
|
||||
|
||||
if (mediaMessage.getTo() != null) {
|
||||
for (EncodedStringValue to : mediaMessage.getTo()) {
|
||||
destinations.add(Util.canonicalizeNumber(context, to.getString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaMessage.getCc() != null) {
|
||||
for (EncodedStringValue cc : mediaMessage.getCc()) {
|
||||
destinations.add(Util.canonicalizeNumber(context, cc.getString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaMessage.getBcc() != null) {
|
||||
for (EncodedStringValue bcc : mediaMessage.getBcc()) {
|
||||
destinations.add(Util.canonicalizeNumber(context, bcc.getString()));
|
||||
}
|
||||
}
|
||||
|
||||
return destinations;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
@ -26,6 +27,7 @@ import android.os.Build;
|
||||
import android.provider.Telephony;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.MmsRadio;
|
||||
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@ -144,6 +146,11 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
public static String canonicalizeNumber(Context context, String number) {
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
return PhoneNumberFormatter.formatNumber(number, localNumber);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isDefaultSmsProvider(Context context){
|
||||
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) ||
|
||||
|
Loading…
Reference in New Issue
Block a user