diff --git a/briar-android/src/androidTestOfficial/assets/animated.gif b/briar-android/src/androidTestOfficial/assets/animated.gif new file mode 100644 index 000000000..1962fbfb5 Binary files /dev/null and b/briar-android/src/androidTestOfficial/assets/animated.gif differ diff --git a/briar-android/src/androidTestOfficial/assets/animated2.gif b/briar-android/src/androidTestOfficial/assets/animated2.gif new file mode 100644 index 000000000..46cfc5df7 Binary files /dev/null and b/briar-android/src/androidTestOfficial/assets/animated2.gif differ diff --git a/briar-android/src/androidTestOfficial/assets/error_high.jpg b/briar-android/src/androidTestOfficial/assets/error_high.jpg new file mode 100644 index 000000000..e2511cb26 Binary files /dev/null and b/briar-android/src/androidTestOfficial/assets/error_high.jpg differ diff --git a/briar-android/src/androidTestOfficial/assets/error_large.gif b/briar-android/src/androidTestOfficial/assets/error_large.gif new file mode 100644 index 000000000..c3d431741 Binary files /dev/null and b/briar-android/src/androidTestOfficial/assets/error_large.gif differ diff --git a/briar-android/src/androidTestOfficial/assets/error_wide.jpg b/briar-android/src/androidTestOfficial/assets/error_wide.jpg new file mode 100644 index 000000000..c547e21ee Binary files /dev/null and b/briar-android/src/androidTestOfficial/assets/error_wide.jpg differ diff --git a/briar-android/src/androidTestOfficial/assets/error_zero.jpg b/briar-android/src/androidTestOfficial/assets/error_zero.jpg new file mode 100644 index 000000000..e69de29bb diff --git a/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/conversation/AttachmentControllerTest.java b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/conversation/AttachmentControllerTest.java new file mode 100644 index 000000000..6fbc6baf9 --- /dev/null +++ b/briar-android/src/androidTestOfficial/java/org/briarproject/briar/android/conversation/AttachmentControllerTest.java @@ -0,0 +1,364 @@ +package org.briarproject.briar.android.conversation; + +import android.content.res.AssetManager; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.briarproject.bramble.api.UniqueId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.briar.api.messaging.Attachment; +import org.briarproject.briar.api.messaging.AttachmentHeader; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +@RunWith(AndroidJUnit4.class) +public class AttachmentControllerTest { + + private static final String smallKitten = + "https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg/160px-Kitten_in_Rizal_Park%2C_Manila.jpg"; + private static final String originalKitten = + "https://upload.wikimedia.org/wikipedia/commons/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg"; + private static final String pngKitten = + "https://upload.wikimedia.org/wikipedia/commons/c/c8/Young_cat.png"; + private static final String uberGif = + "https://raw.githubusercontent.com/fuzzdb-project/fuzzdb/master/attack/file-upload/malicious-images/uber.gif"; + private static final String lottaPixel = + "https://raw.githubusercontent.com/fuzzdb-project/fuzzdb/master/attack/file-upload/malicious-images/lottapixel.jpg"; + private static final String imageIoCrash = + "https://www.landaire.net/img/crasher.png"; + private static final String gimpCrash = + "https://gitlab.gnome.org/GNOME/gimp/uploads/75f5b7ed3b09b3f1c13f1f65bffe784f/31153c919d3aa634e8e6cff82219fe7352dd8a37.png"; + private static final String optiPngAfl = + "https://sourceforge.net/p/optipng/bugs/64/attachment/test.gif"; + private static final String librawError = + "https://www.libraw.org/sites/libraw.org/files/P1010671.JPG"; + + private final AttachmentDimensions dimensions = new AttachmentDimensions( + 100, 50, 200, 75, 300 + ); + private final MessageId msgId = new MessageId(getRandomId()); + + @SuppressWarnings("ConstantConditions") // not needed for now + private final AttachmentController controller = + new AttachmentController(null, dimensions); + + @Test + public void testNoSizeWrongMimeTypeProducesError() throws Exception { + AttachmentHeader h = + new AttachmentHeader(msgId, "application/octet-stream"); + InputStream is = getUrlInputStream(smallKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, false); + assertTrue(item.hasError()); + } + + @Test + public void testNoSizeJpeg() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); + InputStream is = getUrlInputStream(smallKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, false); + assertEquals("image/jpeg", item.getMimeType()); + assertEquals("jpg", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testNoSizePng() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/png"); + InputStream is = getUrlInputStream(smallKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, false); + assertEquals("image/png", item.getMimeType()); + assertEquals("png", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testNoSizeGif() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); + InputStream is = getUrlInputStream(smallKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, false); + assertEquals("image/gif", item.getMimeType()); + assertEquals("gif", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testSmallJpegImage() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); + InputStream is = getUrlInputStream(smallKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(msgId, item.getMessageId()); + assertEquals(160, item.getWidth()); + assertEquals(240, item.getHeight()); + assertEquals(160, item.getThumbnailWidth()); + assertEquals(240, item.getThumbnailHeight()); + assertEquals("image/jpeg", item.getMimeType()); + assertEquals("jpg", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testSmallJpegImageHealsWrongMimeType() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/png"); + InputStream is = getUrlInputStream(smallKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(msgId, item.getMessageId()); + assertEquals(160, item.getWidth()); + assertEquals(240, item.getHeight()); + assertEquals(160, item.getThumbnailWidth()); + assertEquals(240, item.getThumbnailHeight()); + assertEquals("image/jpeg", item.getMimeType()); + assertEquals("jpg", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testBigJpegImage() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg"); + InputStream is = getUrlInputStream(originalKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(msgId, item.getMessageId()); + assertEquals(1728, item.getWidth()); + assertEquals(2592, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(dimensions.maxHeight, item.getThumbnailHeight()); + assertEquals("image/jpeg", item.getMimeType()); + assertEquals("jpg", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testSmallPngImage() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/png"); + InputStream is = getUrlInputStream(pngKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(msgId, item.getMessageId()); + assertEquals(737, item.getWidth()); + assertEquals(510, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(138, item.getThumbnailHeight()); + assertEquals("image/png", item.getMimeType()); + assertEquals("png", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testSmallPngImageHealsWrongMimeType() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getUrlInputStream(pngKitten); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(msgId, item.getMessageId()); + assertEquals(737, item.getWidth()); + assertEquals(510, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(138, item.getThumbnailHeight()); + assertEquals("image/png", item.getMimeType()); + assertEquals("png", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testUberGif() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getUrlInputStream(uberGif); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(1, item.getWidth()); + assertEquals(1, item.getHeight()); + assertEquals(dimensions.minHeight, item.getThumbnailWidth()); + assertEquals(dimensions.minHeight, item.getThumbnailHeight()); + assertEquals("image/gif", item.getMimeType()); + assertEquals("gif", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testLottaPixels() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getUrlInputStream(lottaPixel); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(64250, item.getWidth()); + assertEquals(64250, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(dimensions.maxWidth, item.getThumbnailHeight()); + assertEquals("image/jpeg", item.getMimeType()); + assertEquals("jpg", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testImageIoCrash() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getUrlInputStream(imageIoCrash); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(1184, item.getWidth()); + assertEquals(448, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(dimensions.minHeight, item.getThumbnailHeight()); + assertEquals("image/png", item.getMimeType()); + assertEquals("png", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testGimpCrash() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getUrlInputStream(gimpCrash); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(1, item.getWidth()); + assertEquals(1, item.getHeight()); + assertEquals(dimensions.minHeight, item.getThumbnailWidth()); + assertEquals(dimensions.minHeight, item.getThumbnailHeight()); + assertEquals("image/gif", item.getMimeType()); + assertEquals("gif", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testOptiPngAfl() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getUrlInputStream(optiPngAfl); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(32, item.getWidth()); + assertEquals(32, item.getHeight()); + assertEquals(dimensions.minHeight, item.getThumbnailWidth()); + assertEquals(dimensions.minHeight, item.getThumbnailHeight()); + assertEquals("image/gif", item.getMimeType()); + assertEquals("gif", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testLibrawError() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getUrlInputStream(librawError); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertTrue(item.hasError()); + } + + @Test + public void testSmallAnimatedGifMaxDimensions() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); + InputStream is = getAssetInputStream("animated.gif"); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(65535, item.getWidth()); + assertEquals(65535, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(dimensions.maxWidth, item.getThumbnailHeight()); + assertEquals("image/gif", item.getMimeType()); + assertEquals("gif", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testSmallAnimatedGifHugeDimensions() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); + InputStream is = getAssetInputStream("animated2.gif"); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(10000, item.getWidth()); + assertEquals(10000, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(dimensions.maxWidth, item.getThumbnailHeight()); + assertEquals("image/gif", item.getMimeType()); + assertEquals("gif", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testSmallGifLargeDimensions() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/gif"); + InputStream is = getAssetInputStream("error_large.gif"); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(16384, item.getWidth()); + assertEquals(16384, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(dimensions.maxWidth, item.getThumbnailHeight()); + assertEquals("image/gif", item.getMimeType()); + assertEquals("gif", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testHighError() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getAssetInputStream("error_high.jpg"); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(1, item.getWidth()); + assertEquals(10000, item.getHeight()); + assertEquals(dimensions.minWidth, item.getThumbnailWidth()); + assertEquals(dimensions.maxHeight, item.getThumbnailHeight()); + assertEquals("image/jpeg", item.getMimeType()); + assertEquals("jpg", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testWideError() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getAssetInputStream("error_wide.jpg"); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertEquals(1920, item.getWidth()); + assertEquals(1, item.getHeight()); + assertEquals(dimensions.maxWidth, item.getThumbnailWidth()); + assertEquals(dimensions.minHeight, item.getThumbnailHeight()); + assertEquals("image/jpeg", item.getMimeType()); + assertEquals("jpg", item.getExtension()); + assertFalse(item.hasError()); + } + + @Test + public void testZeroSizeError() throws Exception { + AttachmentHeader h = new AttachmentHeader(msgId, "image/jpg"); + InputStream is = getAssetInputStream("error_zero.jpg"); + Attachment a = new Attachment(is); + AttachmentItem item = controller.getAttachmentItem(h, a, true); + assertTrue(item.hasError()); + } + + private InputStream getUrlInputStream(String url) throws IOException { + return new URL(url).openStream(); + } + + private InputStream getAssetInputStream(String name) throws IOException { + AssetManager assets = InstrumentationRegistry.getContext().getAssets(); + return assets.open(name); + } + + public static byte[] getRandomBytes(int length) { + byte[] b = new byte[length]; + new Random().nextBytes(b); + return b; + } + + public static byte[] getRandomId() { + return getRandomBytes(UniqueId.LENGTH); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java index 3f1c55d6d..11af29438 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentController.java @@ -1,6 +1,5 @@ package org.briarproject.briar.android.conversation; -import android.content.res.Resources; import android.graphics.BitmapFactory; import android.support.annotation.Nullable; import android.support.media.ExifInterface; @@ -13,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.MessageId; -import org.briarproject.briar.R; import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.MessagingManager; @@ -56,18 +54,14 @@ class AttachmentController { private final Map> attachmentCache = new ConcurrentHashMap<>(); - AttachmentController(MessagingManager messagingManager, Resources res) { + AttachmentController(MessagingManager messagingManager, + AttachmentDimensions dimensions) { this.messagingManager = messagingManager; - defaultSize = - res.getDimensionPixelSize(R.dimen.message_bubble_image_default); - minWidth = res.getDimensionPixelSize( - R.dimen.message_bubble_image_min_width); - maxWidth = res.getDimensionPixelSize( - R.dimen.message_bubble_image_max_width); - minHeight = res.getDimensionPixelSize( - R.dimen.message_bubble_image_min_height); - maxHeight = res.getDimensionPixelSize( - R.dimen.message_bubble_image_max_height); + defaultSize = dimensions.defaultSize; + minWidth = dimensions.minWidth; + maxWidth = dimensions.maxWidth; + minHeight = dimensions.minHeight; + maxHeight = dimensions.maxHeight; } void put(MessageId messageId, List attachments) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentDimensions.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentDimensions.java new file mode 100644 index 000000000..081a6480b --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/AttachmentDimensions.java @@ -0,0 +1,39 @@ +package org.briarproject.briar.android.conversation; + +import android.content.res.Resources; +import android.support.annotation.VisibleForTesting; + +import org.briarproject.briar.R; + +class AttachmentDimensions { + + final int defaultSize; + final int minWidth, maxWidth; + final int minHeight, maxHeight; + + @VisibleForTesting + AttachmentDimensions(int defaultSize, int minWidth, int maxWidth, + int minHeight, int maxHeight) { + this.defaultSize = defaultSize; + this.minWidth = minWidth; + this.maxWidth = maxWidth; + this.minHeight = minHeight; + this.maxHeight = maxHeight; + } + + static AttachmentDimensions getAttachmentDimensions(Resources res) { + int defaultSize = + res.getDimensionPixelSize(R.dimen.message_bubble_image_default); + int minWidth = res.getDimensionPixelSize( + R.dimen.message_bubble_image_min_width); + int maxWidth = res.getDimensionPixelSize( + R.dimen.message_bubble_image_max_width); + int minHeight = res.getDimensionPixelSize( + R.dimen.message_bubble_image_min_height); + int maxHeight = res.getDimensionPixelSize( + R.dimen.message_bubble_image_max_height); + return new AttachmentDimensions(defaultSize, minWidth, maxWidth, + minHeight, minHeight); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java index 9c998e9eb..5f93fcd14 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/conversation/ConversationViewModel.java @@ -51,6 +51,7 @@ import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; +import static org.briarproject.briar.android.conversation.AttachmentDimensions.getAttachmentDimensions; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; @@ -114,7 +115,7 @@ public class ConversationViewModel extends AndroidViewModel { this.settingsManager = settingsManager; this.privateMessageFactory = privateMessageFactory; this.attachmentController = new AttachmentController(messagingManager, - application.getResources()); + getAttachmentDimensions(application.getResources())); contactDeleted.setValue(false); }