From f461caa0691bcceb47abb586f43c18e83e8d331d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kry=C5=A1tof=20Wold=C5=99ich?= <31292499+krystofwoldrich@users.noreply.github.com> Date: Fri, 17 Nov 2023 09:58:39 +0100 Subject: [PATCH] fix(fileutils): Add `readBytesFromFile` for use in Hybrid SDKs (#3052) --- CHANGELOG.md | 1 + sentry/api/sentry.api | 1 + .../java/io/sentry/SentryEnvelopeItem.java | 45 +--------------- .../main/java/io/sentry/util/FileUtils.java | 51 +++++++++++++++++++ .../test/java/io/sentry/JsonSerializerTest.kt | 3 +- .../java/io/sentry/SentryEnvelopeItemTest.kt | 17 ++++--- 6 files changed, 64 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63c50d748c..e9264bf740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Fix SIGSEV, SIGABRT and SIGBUS crashes happening after/around the August Google Play System update, see [#2955](https://github.com/getsentry/sentry-java/issues/2955) for more details (fix provided by Native SDK bump) - (Internal) Extract Android Profiler and Measurements for Hybrid SDKs ([#3016](https://github.com/getsentry/sentry-java/pull/3016)) - Ensure DSN uses http/https protocol ([#3044](https://github.com/getsentry/sentry-java/pull/3044)) +- (Internal) Add `readBytesFromFile` for use in Hybrid SDKs ([#3052](https://github.com/getsentry/sentry-java/pull/3052)) ### Dependencies diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index d82c53853b..a4402bf318 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -4366,6 +4366,7 @@ public final class io/sentry/util/ExceptionUtils { public final class io/sentry/util/FileUtils { public fun ()V public static fun deleteRecursively (Ljava/io/File;)Z + public static fun readBytesFromFile (Ljava/lang/String;J)[B public static fun readText (Ljava/io/File;)Ljava/lang/String; } diff --git a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java index 61a7819942..acd6b36aa9 100644 --- a/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java +++ b/sentry/src/main/java/io/sentry/SentryEnvelopeItem.java @@ -1,5 +1,6 @@ package io.sentry; +import static io.sentry.util.FileUtils.readBytesFromFile; import static io.sentry.vendor.Base64.NO_PADDING; import static io.sentry.vendor.Base64.NO_WRAP; @@ -9,13 +10,11 @@ import io.sentry.util.JsonSerializationUtils; import io.sentry.util.Objects; import io.sentry.vendor.Base64; -import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; @@ -304,48 +303,6 @@ private static void ensureAttachmentSizeLimit( return new SentryEnvelopeItem(itemHeader, () -> cachedItem.getBytes()); } - private static byte[] readBytesFromFile(String pathname, long maxFileLength) - throws SentryEnvelopeException { - try { - File file = new File(pathname); - - if (!file.isFile()) { - throw new SentryEnvelopeException( - String.format( - "Reading the item %s failed, because the file located at the path is not a file.", - pathname)); - } - - if (!file.canRead()) { - throw new SentryEnvelopeException( - String.format("Reading the item %s failed, because can't read the file.", pathname)); - } - - if (file.length() > maxFileLength) { - throw new SentryEnvelopeException( - String.format( - "Dropping item, because its size located at '%s' with %d bytes is bigger " - + "than the maximum allowed size of %d bytes.", - pathname, file.length(), maxFileLength)); - } - - try (FileInputStream fileInputStream = new FileInputStream(pathname); - BufferedInputStream inputStream = new BufferedInputStream(fileInputStream); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - byte[] bytes = new byte[1024]; - int length; - int offset = 0; - while ((length = inputStream.read(bytes)) != -1) { - outputStream.write(bytes, offset, length); - } - return outputStream.toByteArray(); - } - } catch (IOException | SecurityException exception) { - throw new SentryEnvelopeException( - String.format("Reading the item %s failed.\n%s", pathname, exception.getMessage())); - } - } - public static @NotNull SentryEnvelopeItem fromClientReport( final @NotNull ISerializer serializer, final @NotNull ClientReport clientReport) throws IOException { diff --git a/sentry/src/main/java/io/sentry/util/FileUtils.java b/sentry/src/main/java/io/sentry/util/FileUtils.java index b5d4df8c16..73b83713c7 100644 --- a/sentry/src/main/java/io/sentry/util/FileUtils.java +++ b/sentry/src/main/java/io/sentry/util/FileUtils.java @@ -1,7 +1,10 @@ package io.sentry.util; +import java.io.BufferedInputStream; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import org.jetbrains.annotations.ApiStatus; @@ -60,4 +63,52 @@ public static boolean deleteRecursively(@Nullable File file) { } return contentBuilder.toString(); } + + /** + * Reads the content of a path into a byte array. If the path is does not exists, it's not a file, + * can't be read or is larger than max size allowed IOException is thrown. Do not use with large + * files, as the byte array is kept in memory! + * + * @param pathname file to read + * @return a byte array containing all the content of the file + * @throws IOException In case of error reading the file + */ + public static byte[] readBytesFromFile(String pathname, long maxFileLength) + throws IOException, SecurityException { + File file = new File(pathname); + + if (!file.exists()) { + throw new IOException(String.format("File '%s' doesn't exists", file.getName())); + } + + if (!file.isFile()) { + throw new IOException( + String.format("Reading path %s failed, because it's not a file.", pathname)); + } + + if (!file.canRead()) { + throw new IOException( + String.format("Reading the item %s failed, because can't read the file.", pathname)); + } + + if (file.length() > maxFileLength) { + throw new IOException( + String.format( + "Reading file failed, because size located at '%s' with %d bytes is bigger " + + "than the maximum allowed size of %d bytes.", + pathname, file.length(), maxFileLength)); + } + + try (FileInputStream fileInputStream = new FileInputStream(pathname); + BufferedInputStream inputStream = new BufferedInputStream(fileInputStream); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] bytes = new byte[1024]; + int length; + int offset = 0; + while ((length = inputStream.read(bytes)) != -1) { + outputStream.write(bytes, offset, length); + } + return outputStream.toByteArray(); + } + } } diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index 3d07b3e37c..93fb64c5b0 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -1,6 +1,5 @@ package io.sentry -import io.sentry.exception.SentryEnvelopeException import io.sentry.profilemeasurements.ProfileMeasurement import io.sentry.profilemeasurements.ProfileMeasurementValue import io.sentry.protocol.Device @@ -1037,7 +1036,7 @@ class JsonSerializerTest { .log( eq(SentryLevel.ERROR), eq("Failed to create envelope item. Dropping it."), - any() + any() ) } diff --git a/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt b/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt index 926277751a..9817897651 100644 --- a/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt +++ b/sentry/src/test/java/io/sentry/SentryEnvelopeItemTest.kt @@ -13,6 +13,7 @@ import org.mockito.kotlin.whenever import java.io.BufferedWriter import java.io.ByteArrayOutputStream import java.io.File +import java.io.IOException import java.io.OutputStreamWriter import java.nio.charset.Charset import kotlin.test.AfterTest @@ -120,7 +121,7 @@ class SentryEnvelopeItemTest { val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) - assertFailsWith( + assertFailsWith( "Reading the attachment ${attachment.pathname} failed, because the file located at " + "the path is not a file." ) { @@ -140,7 +141,7 @@ class SentryEnvelopeItemTest { val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) - assertFailsWith( + assertFailsWith( "Reading the attachment ${attachment.pathname} failed, " + "because can't read the file." ) { @@ -163,7 +164,7 @@ class SentryEnvelopeItemTest { val item = SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize) - assertFailsWith("Reading the attachment ${attachment.pathname} failed.") { + assertFailsWith("Reading the attachment ${attachment.pathname} failed.") { item.data } @@ -244,12 +245,12 @@ class SentryEnvelopeItemTest { file.writeBytes(fixture.bytesTooBig) val attachment = Attachment(file.path) - val exception = assertFailsWith { + val exception = assertFailsWith { SentryEnvelopeItem.fromAttachment(fixture.serializer, fixture.options.logger, attachment, fixture.maxAttachmentSize).data } assertEquals( - "Dropping item, because its size located at " + + "Reading file failed, because size located at " + "'${fixture.pathname}' with ${file.length()} bytes is bigger than the maximum " + "allowed size of ${fixture.maxAttachmentSize} bytes.", exception.message @@ -317,7 +318,7 @@ class SentryEnvelopeItemTest { } file.writeBytes(fixture.bytes) file.setReadable(false) - assertFailsWith("Dropping profiling trace data, because the file ${file.path} doesn't exists") { + assertFailsWith("Dropping profiling trace data, because the file ${file.path} doesn't exists") { SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()).data } } @@ -344,12 +345,12 @@ class SentryEnvelopeItemTest { whenever(it.traceFile).thenReturn(file) } - val exception = assertFailsWith { + val exception = assertFailsWith { SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, fixture.maxAttachmentSize, mock()).data } assertEquals( - "Dropping item, because its size located at " + + "Reading file failed, because size located at " + "'${fixture.pathname}' with ${file.length()} bytes is bigger than the maximum " + "allowed size of ${fixture.maxAttachmentSize} bytes.", exception.message