diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/ClassicDecryptingPartInputStream.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/ClassicDecryptingPartInputStream.java index 3fa7f63e01..061c844eb4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/ClassicDecryptingPartInputStream.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/ClassicDecryptingPartInputStream.java @@ -19,7 +19,7 @@ package org.thoughtcrime.securesms.crypto; import androidx.annotation.NonNull; import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.util.LimitedInputStream; +import org.signal.core.util.stream.TruncatingInputStream; import org.thoughtcrime.securesms.util.Util; import java.io.File; @@ -63,7 +63,7 @@ public class ClassicDecryptingPartInputStream { IvParameterSpec iv = new IvParameterSpec(ivBytes); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(attachmentSecret.getClassicCipherKey(), "AES"), iv); - return new CipherInputStreamWrapper(new LimitedInputStream(fileStream, file.length() - MAC_LENGTH - IV_LENGTH), cipher); + return new CipherInputStreamWrapper(new TruncatingInputStream(fileStream, file.length() - MAC_LENGTH - IV_LENGTH), cipher); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) { throw new AssertionError(e); } @@ -72,7 +72,7 @@ public class ClassicDecryptingPartInputStream { private static void verifyMac(AttachmentSecret attachmentSecret, File file) throws IOException { Mac mac = initializeMac(new SecretKeySpec(attachmentSecret.getClassicMacKey(), "HmacSHA1")); FileInputStream macStream = new FileInputStream(file); - InputStream dataStream = new LimitedInputStream(new FileInputStream(file), file.length() - MAC_LENGTH); + InputStream dataStream = new TruncatingInputStream(new FileInputStream(file), file.length() - MAC_LENGTH); byte[] theirMac = new byte[MAC_LENGTH]; if (macStream.skip(file.length() - MAC_LENGTH) != file.length() - MAC_LENGTH) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/LimitedInputStream.java b/app/src/main/java/org/thoughtcrime/securesms/util/LimitedInputStream.java deleted file mode 100644 index 9092b785fc..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/LimitedInputStream.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.thoughtcrime.securesms.util; - - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - - -/** - * An input stream, which limits its data size. This stream is - * used, if the content length is unknown. - */ -public class LimitedInputStream extends FilterInputStream { - - /** - * The maximum size of an item, in bytes. - */ - private long sizeMax; - - /** - * The current number of bytes. - */ - private long count; - - /** - * Whether this stream is already closed. - */ - private boolean closed; - - /** - * Creates a new instance. - * @param pIn The input stream, which shall be limited. - * @param pSizeMax The limit; no more than this number of bytes - * shall be returned by the source stream. - */ - public LimitedInputStream(InputStream pIn, long pSizeMax) { - super(pIn); - sizeMax = pSizeMax; - } - - /** - * Reads the next byte of data from this input stream. The value - * byte is returned as an int in the range - * 0 to 255. If no byte is available - * because the end of the stream has been reached, the value - * -1 is returned. This method blocks until input data - * is available, the end of the stream is detected, or an exception - * is thrown. - * - * This method - * simply performs in.read() and returns the result. - * - * @return the next byte of data, or -1 if the end of the - * stream is reached. - * @exception IOException if an I/O error occurs. - * @see java.io.FilterInputStream#in - */ - public int read() throws IOException { - if (count >= sizeMax) return -1; - - int res = super.read(); - if (res != -1) { - count++; - } - return res; - } - - /** - * Reads up to len bytes of data from this input stream - * into an array of bytes. If len is not zero, the method - * blocks until some input is available; otherwise, no - * bytes are read and 0 is returned. - * - * This method simply performs in.read(b, off, len) - * and returns the result. - * - * @param b the buffer into which the data is read. - * @param off The start offset in the destination array - * b. - * @param len the maximum number of bytes read. - * @return the total number of bytes read into the buffer, or - * -1 if there is no more data because the end of - * the stream has been reached. - * @exception NullPointerException If b is null. - * @exception IndexOutOfBoundsException If off is negative, - * len is negative, or len is greater than - * b.length - off - * @exception IOException if an I/O error occurs. - * @see java.io.FilterInputStream#in - */ - public int read(byte[] b, int off, int len) throws IOException { - if (count >= sizeMax) return -1; - - long correctLength = Math.min(len, sizeMax - count); - - int res = super.read(b, off, Util.toIntExact(correctLength)); - if (res > 0) { - count += res; - } - return res; - } - -} \ No newline at end of file diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherInputStream.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherInputStream.java index e81ab35963..2dae01b6d4 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherInputStream.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/crypto/AttachmentCipherInputStream.java @@ -6,12 +6,12 @@ package org.whispersystems.signalservice.api.crypto; +import org.signal.core.util.stream.TruncatingInputStream; import org.signal.libsignal.protocol.InvalidMessageException; import org.signal.libsignal.protocol.incrementalmac.ChunkSizeChoice; import org.signal.libsignal.protocol.incrementalmac.IncrementalMacInputStream; import org.signal.libsignal.protocol.kdf.HKDF; import org.whispersystems.signalservice.api.backup.BackupKey; -import org.whispersystems.signalservice.internal.util.ContentLengthInputStream; import org.whispersystems.signalservice.internal.util.Util; import java.io.ByteArrayInputStream; @@ -25,7 +25,6 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -118,7 +117,7 @@ public class AttachmentCipherInputStream extends FilterInputStream { InputStream inputStream = new AttachmentCipherInputStream(wrappedStream, parts[0], streamLength - BLOCK_SIZE - mac.getMacLength()); if (plaintextLength != 0) { - inputStream = new ContentLengthInputStream(inputStream, plaintextLength); + inputStream = new TruncatingInputStream(inputStream, plaintextLength); } return inputStream; @@ -143,7 +142,7 @@ public class AttachmentCipherInputStream extends FilterInputStream { InputStream inputStream = new AttachmentCipherInputStream(new FileInputStream(file), archivedMediaKeyMaterial.getCipherKey(), file.length() - BLOCK_SIZE - mac.getMacLength()); if (originalCipherTextLength != 0) { - inputStream = new ContentLengthInputStream(inputStream, originalCipherTextLength); + inputStream = new TruncatingInputStream(inputStream, originalCipherTextLength); } return inputStream; @@ -180,7 +179,7 @@ public class AttachmentCipherInputStream extends FilterInputStream { InputStream inputStream = new AttachmentCipherInputStream(wrappedStream, parts[0], file.length() - BLOCK_SIZE - mac.getMacLength()); if (plaintextLength != 0) { - inputStream = new ContentLengthInputStream(inputStream, plaintextLength); + inputStream = new TruncatingInputStream(inputStream, plaintextLength); } return inputStream; diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/ChunkedInputStream.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/ChunkedInputStream.java index bc906db004..0247926107 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/ChunkedInputStream.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/ChunkedInputStream.java @@ -26,83 +26,4 @@ public class ChunkedInputStream { } throw new IOException("Malformed varint!"); } - - protected static final class LimitedInputStream extends InputStream { - - private final InputStream in; - - private long left; - private long mark = -1; - - LimitedInputStream(InputStream in, long limit) { - this.in = in; - this.left = limit; - } - - @Override - public int available() throws IOException { - return (int) Math.min(in.available(), left); - } - - // it's okay to mark even if mark isn't supported, as reset won't work - @Override - public synchronized void mark(int readLimit) { - in.mark(readLimit); - mark = left; - } - - @Override - public int read() throws IOException { - if (left == 0) { - return -1; - } - - int result = in.read(); - if (result != -1) { - --left; - } - return result; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (left == 0) { - return -1; - } - - len = (int) Math.min(len, left); - int result = in.read(b, off, len); - if (result != -1) { - left -= result; - } - return result; - } - - @Override - public synchronized void reset() throws IOException { - if (!in.markSupported()) { - throw new IOException("Mark not supported"); - } - if (mark == -1) { - throw new IOException("Mark not set"); - } - - in.reset(); - left = mark; - } - - @Override - public long skip(long n) throws IOException { - n = Math.min(n, left); - long skipped = in.skip(n); - left -= skipped; - return skipped; - } - - @Override - public void close() throws IOException { - // do nothing - } - } - } diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStream.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStream.java index 46055b5040..12c11c0d29 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStream.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/api/messages/multidevice/DeviceContactsInputStream.java @@ -6,6 +6,7 @@ package org.whispersystems.signalservice.api.messages.multidevice; +import org.signal.core.util.stream.TruncatingInputStream; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidMessageException; @@ -61,7 +62,7 @@ public class DeviceContactsInputStream extends ChunkedInputStream { if (details.avatar != null && details.avatar.length != null) { long avatarLength = details.avatar.length; - InputStream avatarStream = new LimitedInputStream(in, avatarLength); + InputStream avatarStream = new TruncatingInputStream(in, avatarLength); String avatarContentType = details.avatar.contentType != null ? details.avatar.contentType : "image/*"; avatar = Optional.of(new DeviceContactAvatar(avatarStream, avatarLength, avatarContentType)); diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/util/ContentLengthInputStream.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/util/ContentLengthInputStream.java deleted file mode 100644 index f1a428c270..0000000000 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/util/ContentLengthInputStream.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.whispersystems.signalservice.internal.util; - - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -public class ContentLengthInputStream extends FilterInputStream { - - private long bytesRemaining; - - public ContentLengthInputStream(InputStream inputStream, long contentLength) { - super(inputStream); - this.bytesRemaining = contentLength; - } - - @Override - public int read() throws IOException { - if (bytesRemaining == 0) return -1; - int result = super.read(); - bytesRemaining--; - - return result; - } - - @Override - public int read(byte[] buffer) throws IOException { - return read(buffer, 0, buffer.length); - } - - @Override - public int read(byte[] buffer, int offset, int length) throws IOException { - if (bytesRemaining == 0) return -1; - - int result = super.read(buffer, offset, Math.min(length, Util.toIntExact(bytesRemaining))); - - bytesRemaining -= result; - return result; - } - -} diff --git a/video/lib/src/main/java/org/thoughtcrime/securesms/video/postprocessing/Mp4FaststartPostProcessor.kt b/video/lib/src/main/java/org/thoughtcrime/securesms/video/postprocessing/Mp4FaststartPostProcessor.kt index 127be2921d..7a4e5673bd 100644 --- a/video/lib/src/main/java/org/thoughtcrime/securesms/video/postprocessing/Mp4FaststartPostProcessor.kt +++ b/video/lib/src/main/java/org/thoughtcrime/securesms/video/postprocessing/Mp4FaststartPostProcessor.kt @@ -6,12 +6,11 @@ package org.thoughtcrime.securesms.video.postprocessing import org.signal.core.util.readLength +import org.signal.core.util.stream.TruncatingInputStream import org.signal.libsignal.media.Mp4Sanitizer import org.signal.libsignal.media.SanitizedMetadata import org.thoughtcrime.securesms.video.exceptions.VideoPostProcessingException import java.io.ByteArrayInputStream -import java.io.FilterInputStream -import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.io.SequenceInputStream @@ -35,7 +34,7 @@ class Mp4FaststartPostProcessor(private val inputStreamFactory: InputStreamFacto } val inputStream = inputStreamFactory.create() inputStream.skip(metadata.dataOffset) - return SequenceInputStream(ByteArrayInputStream(metadata.sanitizedMetadata), LimitedInputStream(inputStream, metadata.dataLength)) + return SequenceInputStream(ByteArrayInputStream(metadata.sanitizedMetadata), TruncatingInputStream(inputStream, metadata.dataLength)) } fun processAndWriteTo(outputStream: OutputStream, inputLength: Long = calculateStreamLength(inputStreamFactory.create())): Long { @@ -65,72 +64,4 @@ class Mp4FaststartPostProcessor(private val inputStreamFactory: InputStreamFacto } } } - - private class LimitedInputStream(innerStream: InputStream, limit: Long) : FilterInputStream(innerStream) { - private var left: Long = limit - private var mark: Long = -1 - - init { - if (limit < 0) { - throw IllegalArgumentException("Limit must be non-negative!") - } - } - - @Throws(IOException::class) - override fun available(): Int { - return `in`.available().toLong().coerceAtMost(left).toInt() - } - - @Synchronized - override fun mark(readLimit: Int) { - `in`.mark(readLimit) - mark = left - } - - @Throws(IOException::class) - override fun read(): Int { - if (left == 0L) { - return -1 - } - val result = `in`.read() - if (result != -1) { - --left - } - return result - } - - @Throws(IOException::class) - override fun read(b: ByteArray, off: Int, len: Int): Int { - if (left == 0L) { - return -1 - } - val toRead = len.toLong().coerceAtMost(left).toInt() - val result = `in`.read(b, off, toRead) - if (result != -1) { - left -= result.toLong() - } - return result - } - - @Synchronized - @Throws(IOException::class) - override fun reset() { - if (!`in`.markSupported()) { - throw IOException("Mark not supported") - } - if (mark == -1L) { - throw IOException("Mark not set") - } - `in`.reset() - left = mark - } - - @Throws(IOException::class) - override fun skip(n: Long): Long { - val toSkip = n.coerceAtMost(left) - val skipped = `in`.skip(toSkip) - left -= skipped - return skipped - } - } }