Add upload/download size restrictions for attachments based on remote config.
This commit is contained in:
parent
87d4dba32b
commit
f4a082584c
10 changed files with 90 additions and 45 deletions
|
@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.s3.S3;
|
|||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.ByteUnit;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
|
@ -58,9 +57,6 @@ public final class AttachmentDownloadJob extends BaseJob {
|
|||
|
||||
private static final String TAG = Log.tag(AttachmentDownloadJob.class);
|
||||
|
||||
/** A little extra allowed size to account for any adjustments made by other clients */
|
||||
private static final int MAX_ATTACHMENT_SIZE_BUFFER = 25 * 1024 * 1024;
|
||||
|
||||
private static final String KEY_MESSAGE_ID = "message_id";
|
||||
private static final String KEY_PART_ROW_ID = "part_row_id";
|
||||
private static final String KEY_PAR_UNIQUE_ID = "part_unique_id";
|
||||
|
@ -188,16 +184,20 @@ public final class AttachmentDownloadJob extends BaseJob {
|
|||
final Attachment attachment)
|
||||
throws IOException, RetryLaterException
|
||||
{
|
||||
long maxReceiveSize = FeatureFlags.maxAttachmentReceiveSizeBytes();
|
||||
|
||||
AttachmentTable database = SignalDatabase.attachments();
|
||||
File attachmentFile = database.getOrCreateTransferFile(attachmentId);
|
||||
|
||||
try {
|
||||
if (attachment.getSize() > maxReceiveSize) {
|
||||
throw new MmsException("Attachment too large, failing download");
|
||||
}
|
||||
SignalServiceMessageReceiver messageReceiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
SignalServiceAttachmentPointer pointer = createAttachmentPointer(attachment);
|
||||
InputStream stream = messageReceiver.retrieveAttachment(pointer,
|
||||
attachmentFile,
|
||||
ByteUnit.MEGABYTES.toBytes(FeatureFlags.maxAttachmentSizeMb()) + MAX_ATTACHMENT_SIZE_BUFFER,
|
||||
maxReceiveSize,
|
||||
(total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress)));
|
||||
|
||||
database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream);
|
||||
|
@ -269,6 +269,9 @@ public final class AttachmentDownloadJob extends BaseJob {
|
|||
try (Response response = S3.getObject(Objects.requireNonNull(attachment.getFileName()))) {
|
||||
ResponseBody body = response.body();
|
||||
if (body != null) {
|
||||
if (body.contentLength() > FeatureFlags.maxAttachmentReceiveSizeBytes()) {
|
||||
throw new MmsException("Attachment too large, failing download");
|
||||
}
|
||||
SignalDatabase.attachments().insertAttachmentsForPlaceholder(messageId, attachmentId, Okio.buffer(body.source()).inputStream());
|
||||
}
|
||||
} catch (MmsException e) {
|
||||
|
|
|
@ -26,13 +26,16 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
|
|||
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.NotificationController;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResumableUploadResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException;
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
|
||||
import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -64,6 +67,12 @@ public final class AttachmentUploadJob extends BaseJob {
|
|||
*/
|
||||
private static final int FOREGROUND_LIMIT = 10 * 1024 * 1024;
|
||||
|
||||
public static long getMaxPlaintextSize() {
|
||||
long maxCipherTextSize = FeatureFlags.maxAttachmentSizeBytes();
|
||||
long maxPaddedSize = AttachmentCipherOutputStream.getPlaintextLength(maxCipherTextSize);
|
||||
return PaddingInputStream.getMaxUnpaddedSize(maxPaddedSize);
|
||||
}
|
||||
|
||||
private final AttachmentId attachmentId;
|
||||
|
||||
private boolean forceV2;
|
||||
|
|
|
@ -11,11 +11,14 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MemoryFileDescriptor;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream;
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -50,31 +53,39 @@ public abstract class MediaConstraints {
|
|||
*/
|
||||
public abstract int[] getImageDimensionTargets(Context context);
|
||||
|
||||
public abstract int getGifMaxSize(Context context);
|
||||
public abstract int getVideoMaxSize(Context context);
|
||||
public abstract long getGifMaxSize(Context context);
|
||||
public abstract long getVideoMaxSize(Context context);
|
||||
|
||||
public @IntRange(from = 0, to = 100) int getImageCompressionQualitySetting(@NonNull Context context) {
|
||||
return 70;
|
||||
}
|
||||
|
||||
public int getUncompressedVideoMaxSize(Context context) {
|
||||
public long getUncompressedVideoMaxSize(Context context) {
|
||||
return getVideoMaxSize(context);
|
||||
}
|
||||
|
||||
public int getCompressedVideoMaxSize(Context context) {
|
||||
public long getCompressedVideoMaxSize(Context context) {
|
||||
return getVideoMaxSize(context);
|
||||
}
|
||||
|
||||
public abstract int getAudioMaxSize(Context context);
|
||||
public abstract int getDocumentMaxSize(Context context);
|
||||
public abstract long getAudioMaxSize(Context context);
|
||||
public abstract long getDocumentMaxSize(Context context);
|
||||
|
||||
public long getMaxAttachmentSize() {
|
||||
return AttachmentUploadJob.getMaxPlaintextSize();
|
||||
}
|
||||
|
||||
public boolean isSatisfied(@NonNull Context context, @NonNull Attachment attachment) {
|
||||
try {
|
||||
return (MediaUtil.isGif(attachment) && attachment.getSize() <= getGifMaxSize(context) && isWithinBounds(context, attachment.getUri())) ||
|
||||
(MediaUtil.isImage(attachment) && attachment.getSize() <= getImageMaxSize(context) && isWithinBounds(context, attachment.getUri())) ||
|
||||
(MediaUtil.isAudio(attachment) && attachment.getSize() <= getAudioMaxSize(context)) ||
|
||||
(MediaUtil.isVideo(attachment) && attachment.getSize() <= getVideoMaxSize(context)) ||
|
||||
(MediaUtil.isFile(attachment) && attachment.getSize() <= getDocumentMaxSize(context));
|
||||
long size = attachment.getSize();
|
||||
if (size > getMaxAttachmentSize()) {
|
||||
return false;
|
||||
}
|
||||
return (MediaUtil.isGif(attachment) && size <= getGifMaxSize(context) && isWithinBounds(context, attachment.getUri())) ||
|
||||
(MediaUtil.isImage(attachment) && size <= getImageMaxSize(context) && isWithinBounds(context, attachment.getUri())) ||
|
||||
(MediaUtil.isAudio(attachment) && size <= getAudioMaxSize(context)) ||
|
||||
(MediaUtil.isVideo(attachment) && size <= getVideoMaxSize(context)) ||
|
||||
(MediaUtil.isFile(attachment) && size <= getDocumentMaxSize(context));
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, "Failed to determine if media's constraints are satisfied.", ioe);
|
||||
return false;
|
||||
|
@ -83,6 +94,9 @@ public abstract class MediaConstraints {
|
|||
|
||||
public boolean isSatisfied(@NonNull Context context, @NonNull Uri uri, @NonNull String contentType, long size) {
|
||||
try {
|
||||
if (size > getMaxAttachmentSize()) {
|
||||
return false;
|
||||
}
|
||||
return (MediaUtil.isGif(contentType) && size <= getGifMaxSize(context) && isWithinBounds(context, uri)) ||
|
||||
(MediaUtil.isImageType(contentType) && size <= getImageMaxSize(context) && isWithinBounds(context, uri)) ||
|
||||
(MediaUtil.isAudioType(contentType) && size <= getAudioMaxSize(context)) ||
|
||||
|
|
|
@ -43,27 +43,27 @@ final class MmsMediaConstraints extends MediaConstraints {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getGifMaxSize(Context context) {
|
||||
public long getGifMaxSize(Context context) {
|
||||
return getMaxMessageSize(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVideoMaxSize(Context context) {
|
||||
public long getVideoMaxSize(Context context) {
|
||||
return getMaxMessageSize(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUncompressedVideoMaxSize(Context context) {
|
||||
public long getUncompressedVideoMaxSize(Context context) {
|
||||
return Math.max(getVideoMaxSize(context), 15 * 1024 * 1024);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioMaxSize(Context context) {
|
||||
public long getAudioMaxSize(Context context) {
|
||||
return getMaxMessageSize(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocumentMaxSize(Context context) {
|
||||
public long getDocumentMaxSize(Context context) {
|
||||
return getMaxMessageSize(context);
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ public class PushMediaConstraints extends MediaConstraints {
|
|||
|
||||
@Override
|
||||
public int getImageMaxSize(Context context) {
|
||||
return currentConfig.maxImageFileSize;
|
||||
return (int) Math.min(currentConfig.maxImageFileSize, getMaxAttachmentSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,35 +49,35 @@ public class PushMediaConstraints extends MediaConstraints {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getGifMaxSize(Context context) {
|
||||
return 25 * MB;
|
||||
public long getGifMaxSize(Context context) {
|
||||
return Math.min(25 * MB, getMaxAttachmentSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVideoMaxSize(Context context) {
|
||||
return 100 * MB;
|
||||
public long getVideoMaxSize(Context context) {
|
||||
return getMaxAttachmentSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUncompressedVideoMaxSize(Context context) {
|
||||
public long getUncompressedVideoMaxSize(Context context) {
|
||||
return isVideoTranscodeAvailable() ? 500 * MB
|
||||
: getVideoMaxSize(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompressedVideoMaxSize(Context context) {
|
||||
public long getCompressedVideoMaxSize(Context context) {
|
||||
return Util.isLowMemory(context) ? 30 * MB
|
||||
: 50 * MB;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioMaxSize(Context context) {
|
||||
return 100 * MB;
|
||||
public long getAudioMaxSize(Context context) {
|
||||
return getMaxAttachmentSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocumentMaxSize(Context context) {
|
||||
return 100 * MB;
|
||||
public long getDocumentMaxSize(Context context) {
|
||||
return getMaxAttachmentSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,22 +27,22 @@ public class ProfileMediaConstraints extends MediaConstraints {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getGifMaxSize(Context context) {
|
||||
public long getGifMaxSize(Context context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVideoMaxSize(Context context) {
|
||||
public long getVideoMaxSize(Context context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioMaxSize(Context context) {
|
||||
public long getAudioMaxSize(Context context) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocumentMaxSize(Context context) {
|
||||
public long getDocumentMaxSize(Context context) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.groups.SelectionLimits;
|
|||
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
@ -106,7 +107,8 @@ public final class FeatureFlags {
|
|||
private static final String AD_HOC_CALLING = "android.calling.ad.hoc.2";
|
||||
private static final String EDIT_MESSAGE_SEND = "android.editMessage.send.3";
|
||||
private static final String MAX_ATTACHMENT_COUNT = "android.attachments.maxCount";
|
||||
private static final String MAX_ATTACHMENT_SIZE_MB = "android.attachments.maxSize";
|
||||
private static final String MAX_ATTACHMENT_RECEIVE_SIZE_BYTES = "global.attachments.maxReceiveBytes";
|
||||
private static final String MAX_ATTACHMENT_SIZE_BYTES = "global.attachments.maxBytes";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
|
@ -165,7 +167,8 @@ public final class FeatureFlags {
|
|||
ANY_ADDRESS_PORTS_KILL_SWITCH,
|
||||
EDIT_MESSAGE_SEND,
|
||||
MAX_ATTACHMENT_COUNT,
|
||||
MAX_ATTACHMENT_SIZE_MB,
|
||||
MAX_ATTACHMENT_RECEIVE_SIZE_BYTES,
|
||||
MAX_ATTACHMENT_SIZE_BYTES,
|
||||
AD_HOC_CALLING
|
||||
);
|
||||
|
||||
|
@ -231,7 +234,8 @@ public final class FeatureFlags {
|
|||
CDS_HARD_LIMIT,
|
||||
EDIT_MESSAGE_SEND,
|
||||
MAX_ATTACHMENT_COUNT,
|
||||
MAX_ATTACHMENT_SIZE_MB
|
||||
MAX_ATTACHMENT_RECEIVE_SIZE_BYTES,
|
||||
MAX_ATTACHMENT_SIZE_BYTES
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -594,9 +598,16 @@ public final class FeatureFlags {
|
|||
return getInteger(MAX_ATTACHMENT_COUNT, 32);
|
||||
}
|
||||
|
||||
/** Maximum attachment size, in mebibytes. */
|
||||
public static int maxAttachmentSizeMb() {
|
||||
return getInteger(MAX_ATTACHMENT_SIZE_MB, 100);
|
||||
/** Maximum attachment size for ciphertext in bytes. */
|
||||
public static long maxAttachmentReceiveSizeBytes() {
|
||||
long maxAttachmentSize = maxAttachmentSizeBytes();
|
||||
long maxReceiveSize = getLong(MAX_ATTACHMENT_RECEIVE_SIZE_BYTES, (int) (maxAttachmentSize * 1.25));
|
||||
return Math.max(maxAttachmentSize, maxReceiveSize);
|
||||
}
|
||||
|
||||
/** Maximum attachment ciphertext size when sending in bytes */
|
||||
public static long maxAttachmentSizeBytes() {
|
||||
return getLong(MAX_ATTACHMENT_SIZE_BYTES, ByteUnit.MEGABYTES.toBytes(100));
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
|
|
|
@ -41,8 +41,8 @@ public final class VideoUtil {
|
|||
}
|
||||
|
||||
public static int getMaxVideoRecordDurationInSeconds(@NonNull Context context, @NonNull MediaConstraints mediaConstraints) {
|
||||
int allowedSize = mediaConstraints.getCompressedVideoMaxSize(context);
|
||||
int duration = (int) Math.floor((float) allowedSize / TOTAL_BYTES_PER_SECOND);
|
||||
long allowedSize = mediaConstraints.getCompressedVideoMaxSize(context);
|
||||
int duration = (int) Math.floor((float) allowedSize / TOTAL_BYTES_PER_SECOND);
|
||||
|
||||
return Math.min(duration, VIDEO_MAX_RECORD_LENGTH_S);
|
||||
}
|
||||
|
|
|
@ -89,7 +89,11 @@ public class AttachmentCipherOutputStream extends DigestingOutputStream {
|
|||
}
|
||||
|
||||
public static long getCiphertextLength(long plaintextLength) {
|
||||
return 16 + (((plaintextLength / 16) +1) * 16) + 32;
|
||||
return 16 + (((plaintextLength / 16) + 1) * 16) + 32;
|
||||
}
|
||||
|
||||
public static long getPlaintextLength(long ciphertextLength) {
|
||||
return (((ciphertextLength - 16 - 32) / 16) - 1) * 16;
|
||||
}
|
||||
|
||||
private Mac initializeMac() {
|
||||
|
|
|
@ -56,4 +56,8 @@ public class PaddingInputStream extends FilterInputStream {
|
|||
public static long getPaddedSize(long size) {
|
||||
return (int) Math.max(541, Math.floor(Math.pow(1.05, Math.ceil(Math.log(size) / Math.log(1.05)))));
|
||||
}
|
||||
|
||||
public static long getMaxUnpaddedSize(long maxPaddedSize) {
|
||||
return (int) Math.floor(Math.pow(1.05, Math.floor(Math.log(maxPaddedSize) / Math.log(1.05))));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue