Check result of bitmap size operation for failure

Fixes #5046
// FREEBIE
This commit is contained in:
Moxie Marlinspike 2016-01-30 15:22:55 -08:00
parent 1b97756b05
commit 4a261bcf68
6 changed files with 44 additions and 27 deletions

View File

@ -21,6 +21,7 @@ import android.util.SparseArray;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ListenableFutureTask;
@ -29,7 +30,6 @@ import org.thoughtcrime.securesms.util.Util;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -221,7 +221,7 @@ public class EmojiProvider {
try { try {
Log.w(TAG, "loading page " + model.getSprite()); Log.w(TAG, "loading page " + model.getSprite());
return loadPage(); return loadPage();
} catch (IOException | ExecutionException ioe) { } catch (IOException ioe) {
Log.w(TAG, ioe); Log.w(TAG, ioe);
} }
return null; return null;
@ -242,7 +242,7 @@ public class EmojiProvider {
return task; return task;
} }
private Bitmap loadPage() throws IOException, ExecutionException { private Bitmap loadPage() throws IOException {
if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get(); if (bitmapReference != null && bitmapReference.get() != null) return bitmapReference.get();
try { try {
@ -252,9 +252,9 @@ public class EmojiProvider {
bitmapReference = new SoftReference<>(bitmap); bitmapReference = new SoftReference<>(bitmap);
Log.w(TAG, "onPageLoaded(" + model.getSprite() + ")"); Log.w(TAG, "onPageLoaded(" + model.getSprite() + ")");
return bitmap; return bitmap;
} catch (ExecutionException e) { } catch (BitmapDecodingException e) {
Log.w(TAG, e); Log.w(TAG, e);
throw e; throw new IOException(e);
} }
} }

View File

@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel; import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
import org.thoughtcrime.securesms.push.TextSecurePushTrustStore; import org.thoughtcrime.securesms.push.TextSecurePushTrustStore;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
@ -63,7 +64,7 @@ public class AvatarDownloadJob extends MasterSecretJob {
database.updateAvatar(groupId, avatar); database.updateAvatar(groupId, avatar);
} }
} catch (ExecutionException | NonSuccessfulResponseCodeException e) { } catch (BitmapDecodingException | NonSuccessfulResponseCodeException e) {
Log.w(TAG, e); Log.w(TAG, e);
} finally { } finally {
if (attachment != null) if (attachment != null)

View File

@ -10,13 +10,13 @@ import android.util.Pair;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.ExecutionException;
import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.ContentType;
@ -50,10 +50,14 @@ public abstract class MediaConstraints {
} }
public boolean isWithinBounds(Context context, MasterSecret masterSecret, Uri uri) throws IOException { public boolean isWithinBounds(Context context, MasterSecret masterSecret, Uri uri) throws IOException {
try {
InputStream is = PartAuthority.getAttachmentStream(context, masterSecret, uri); InputStream is = PartAuthority.getAttachmentStream(context, masterSecret, uri);
Pair<Integer, Integer> dimensions = BitmapUtil.getDimensions(is); Pair<Integer, Integer> dimensions = BitmapUtil.getDimensions(is);
return dimensions.first > 0 && dimensions.first <= getImageMaxWidth(context) && return dimensions.first > 0 && dimensions.first <= getImageMaxWidth(context) &&
dimensions.second > 0 && dimensions.second <= getImageMaxHeight(context); dimensions.second > 0 && dimensions.second <= getImageMaxHeight(context);
} catch (BitmapDecodingException e) {
throw new IOException(e);
}
} }
public boolean canResize(@Nullable Attachment attachment) { public boolean canResize(@Nullable Attachment attachment) {
@ -73,8 +77,8 @@ public abstract class MediaConstraints {
// XXX - This is loading everything into memory! We want the send path to be stream-like. // XXX - This is loading everything into memory! We want the send path to be stream-like.
return new MediaStream(new ByteArrayInputStream(BitmapUtil.createScaledBytes(context, new DecryptableUri(masterSecret, attachment.getDataUri()), this)), return new MediaStream(new ByteArrayInputStream(BitmapUtil.createScaledBytes(context, new DecryptableUri(masterSecret, attachment.getDataUri()), this)),
ContentType.IMAGE_JPEG); ContentType.IMAGE_JPEG);
} catch (ExecutionException ee) { } catch (BitmapDecodingException e) {
throw new IOException(ee); throw new IOException(e);
} }
} }
} }

View File

@ -1,7 +1,12 @@
package org.thoughtcrime.securesms.util; package org.thoughtcrime.securesms.util;
public class BitmapDecodingException extends Throwable { public class BitmapDecodingException extends Exception {
public BitmapDecodingException(String s) { public BitmapDecodingException(String s) {
super(s); super(s);
} }
public BitmapDecodingException(Exception nested) {
super(nested);
}
} }

View File

@ -30,10 +30,10 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class BitmapUtil { public class BitmapUtil {
private static final String TAG = BitmapUtil.class.getSimpleName(); private static final String TAG = BitmapUtil.class.getSimpleName();
private static final int MAX_COMPRESSION_QUALITY = 80; private static final int MAX_COMPRESSION_QUALITY = 80;
@ -41,7 +41,7 @@ public class BitmapUtil {
private static final int MAX_COMPRESSION_ATTEMPTS = 4; private static final int MAX_COMPRESSION_ATTEMPTS = 4;
public static <T> byte[] createScaledBytes(Context context, T model, MediaConstraints constraints) public static <T> byte[] createScaledBytes(Context context, T model, MediaConstraints constraints)
throws ExecutionException, IOException throws BitmapDecodingException
{ {
int quality = MAX_COMPRESSION_QUALITY; int quality = MAX_COMPRESSION_QUALITY;
int attempts = 0; int attempts = 0;
@ -62,7 +62,7 @@ public class BitmapUtil {
} }
while (bytes.length > constraints.getImageMaxSize() && attempts++ < MAX_COMPRESSION_ATTEMPTS); while (bytes.length > constraints.getImageMaxSize() && attempts++ < MAX_COMPRESSION_ATTEMPTS);
if (bytes.length > constraints.getImageMaxSize()) { if (bytes.length > constraints.getImageMaxSize()) {
throw new IOException("Unable to scale image below: " + bytes.length); throw new BitmapDecodingException("Unable to scale image below: " + bytes.length);
} }
Log.w(TAG, "createScaledBytes(" + model.toString() + ") -> quality " + Math.min(quality, MAX_COMPRESSION_QUALITY) + ", " + attempts + " attempt(s)"); Log.w(TAG, "createScaledBytes(" + model.toString() + ") -> quality " + Math.min(quality, MAX_COMPRESSION_QUALITY) + ", " + attempts + " attempt(s)");
return bytes; return bytes;
@ -72,7 +72,7 @@ public class BitmapUtil {
} }
public static <T> Bitmap createScaledBitmap(Context context, T model, int maxWidth, int maxHeight) public static <T> Bitmap createScaledBitmap(Context context, T model, int maxWidth, int maxHeight)
throws ExecutionException throws BitmapDecodingException
{ {
final Pair<Integer, Integer> dimensions = getDimensions(getInputStreamForModel(context, model)); final Pair<Integer, Integer> dimensions = getDimensions(getInputStreamForModel(context, model));
final Pair<Integer, Integer> clamped = clampDimensions(dimensions.first, dimensions.second, final Pair<Integer, Integer> clamped = clampDimensions(dimensions.first, dimensions.second,
@ -81,19 +81,19 @@ public class BitmapUtil {
} }
private static <T> InputStream getInputStreamForModel(Context context, T model) private static <T> InputStream getInputStreamForModel(Context context, T model)
throws ExecutionException throws BitmapDecodingException
{ {
try { try {
return Glide.buildStreamModelLoader(model, context) return Glide.buildStreamModelLoader(model, context)
.getResourceFetcher(model, -1, -1) .getResourceFetcher(model, -1, -1)
.loadData(Priority.NORMAL); .loadData(Priority.NORMAL);
} catch (Exception e) { } catch (Exception e) {
throw new ExecutionException(e); throw new BitmapDecodingException(e);
} }
} }
private static <T> Bitmap createScaledBitmapInto(Context context, T model, int width, int height) private static <T> Bitmap createScaledBitmapInto(Context context, T model, int width, int height)
throws ExecutionException throws BitmapDecodingException
{ {
final Bitmap rough = Downsampler.AT_LEAST.decode(getInputStreamForModel(context, model), final Bitmap rough = Downsampler.AT_LEAST.decode(getInputStreamForModel(context, model),
Glide.get(context).getBitmapPool(), Glide.get(context).getBitmapPool(),
@ -104,20 +104,22 @@ public class BitmapUtil {
final Resource<Bitmap> result = new FitCenter(context).transform(resource, width, height); final Resource<Bitmap> result = new FitCenter(context).transform(resource, width, height);
if (result == null) { if (result == null) {
throw new ExecutionException(new BitmapDecodingException("unable to transform Bitmap")); throw new BitmapDecodingException("unable to transform Bitmap");
} }
return result.get(); return result.get();
} }
public static <T> Bitmap createScaledBitmap(Context context, T model, float scale) public static <T> Bitmap createScaledBitmap(Context context, T model, float scale)
throws ExecutionException throws BitmapDecodingException
{ {
Pair<Integer, Integer> dimens = getDimensions(getInputStreamForModel(context, model)); Pair<Integer, Integer> dimens = getDimensions(getInputStreamForModel(context, model));
return createScaledBitmapInto(context, model, return createScaledBitmapInto(context, model,
(int)(dimens.first * scale), (int)(dimens.second * scale)); (int)(dimens.first * scale), (int)(dimens.second * scale));
} }
private static BitmapFactory.Options getImageDimensions(InputStream inputStream) { private static BitmapFactory.Options getImageDimensions(InputStream inputStream)
throws BitmapDecodingException
{
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true;
BufferedInputStream fis = new BufferedInputStream(inputStream); BufferedInputStream fis = new BufferedInputStream(inputStream);
@ -127,10 +129,15 @@ public class BitmapUtil {
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w(TAG, "failed to close the InputStream after reading image dimensions"); Log.w(TAG, "failed to close the InputStream after reading image dimensions");
} }
if (options.outWidth == -1 || options.outHeight == -1) {
throw new BitmapDecodingException("Failed to decode image dimensions: " + options.outWidth + ", " + options.outHeight);
}
return options; return options;
} }
public static Pair<Integer, Integer> getDimensions(InputStream inputStream) { public static Pair<Integer, Integer> getDimensions(InputStream inputStream) throws BitmapDecodingException {
BitmapFactory.Options options = getImageDimensions(inputStream); BitmapFactory.Options options = getImageDimensions(inputStream);
return new Pair<>(options.outWidth, options.outHeight); return new Pair<>(options.outWidth, options.outHeight);
} }

View File

@ -30,7 +30,7 @@ public class MediaUtil {
private static final String TAG = MediaUtil.class.getSimpleName(); private static final String TAG = MediaUtil.class.getSimpleName();
public static @Nullable ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, String contentType, Uri uri) public static @Nullable ThumbnailData generateThumbnail(Context context, MasterSecret masterSecret, String contentType, Uri uri)
throws ExecutionException throws BitmapDecodingException
{ {
long startMillis = System.currentTimeMillis(); long startMillis = System.currentTimeMillis();
ThumbnailData data = null; ThumbnailData data = null;
@ -49,7 +49,7 @@ public class MediaUtil {
} }
private static Bitmap generateImageThumbnail(Context context, MasterSecret masterSecret, Uri uri) private static Bitmap generateImageThumbnail(Context context, MasterSecret masterSecret, Uri uri)
throws ExecutionException throws BitmapDecodingException
{ {
int maxSize = context.getResources().getDimensionPixelSize(R.dimen.media_bubble_height); int maxSize = context.getResources().getDimensionPixelSize(R.dimen.media_bubble_height);
return BitmapUtil.createScaledBitmap(context, new DecryptableUri(masterSecret, uri), maxSize, maxSize); return BitmapUtil.createScaledBitmap(context, new DecryptableUri(masterSecret, uri), maxSize, maxSize);