From 9be55eda078428200bb14d55570f0591946c562f Mon Sep 17 00:00:00 2001 From: davotoula Date: Sat, 6 Sep 2025 16:31:22 +0200 Subject: [PATCH 1/5] close FileInputStreams to prevent resource leak --- .../amethyst/service/crashreports/CrashReportCache.kt | 6 ++++-- .../vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/crashreports/CrashReportCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/crashreports/CrashReportCache.kt index cef894441..cdf050b32 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/crashreports/CrashReportCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/crashreports/CrashReportCache.kt @@ -50,8 +50,10 @@ class CrashReportCache( suspend fun loadAndDelete(): String? = withContext(Dispatchers.IO) { val stack = - inputStreamOrNull()?.let { inStream -> - InputStreamReader(inStream).readText() + inputStreamOrNull()?.use { inStream -> + InputStreamReader(inStream).use { reader -> + reader.readText() + } } deleteReport() stack diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt index a54907c44..888a6e8d7 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt @@ -73,7 +73,7 @@ abstract class OpCrypto internal constructor() : OpUnary() { } @Throws(IOException::class, NoSuchAlgorithmException::class) - fun hashFd(file: File?): ByteArray = hashFd(FileInputStream(file)) + fun hashFd(file: File?): ByteArray = FileInputStream(file).use { inputStream -> hashFd(inputStream) } @Throws(IOException::class, NoSuchAlgorithmException::class) fun hashFd(bytes: ByteArray): ByteArray { From e66e71a05cf37e180c479f3226a56ee84eba5a65 Mon Sep 17 00:00:00 2001 From: davotoula Date: Sat, 6 Sep 2025 16:33:03 +0200 Subject: [PATCH 2/5] clener code --- .../amethyst/service/crashreports/CrashReportCache.kt | 2 +- .../com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/crashreports/CrashReportCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/crashreports/CrashReportCache.kt index cdf050b32..64d4c2795 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/crashreports/CrashReportCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/crashreports/CrashReportCache.kt @@ -41,7 +41,7 @@ class CrashReportCache( null } - suspend fun writeReport(report: String) { + fun writeReport(report: String) { val trace = outputStream() trace.write(report.toByteArray()) trace.close() diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt index 888a6e8d7..e44a3053e 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt @@ -62,7 +62,7 @@ abstract class OpCrypto internal constructor() : OpUnary() { val digest = MessageDigest.getInstance(this.hashLibName()) var chunk = ctx.read(1048576) - while (chunk != null && chunk.size > 0) { + while (chunk.isNotEmpty()) { digest.update(chunk) chunk = ctx.read(1048576) } From ec362ea0eeda1d3e5a16e4b7c207d7ee34296a8f Mon Sep 17 00:00:00 2001 From: davotoula Date: Sat, 6 Sep 2025 16:44:29 +0200 Subject: [PATCH 3/5] Use .use for streams cleaner code --- .../amethyst/model/LocalCache.kt | 20 ++++++------- .../uploads/blossom/BlossomUploader.kt | 28 ++++++++++--------- .../service/uploads/nip96/Nip96Uploader.kt | 26 +++++++++-------- .../quartz/nip03Timestamp/ots/http/Request.kt | 8 +++--- .../nip03Timestamp/ots/http/Response.kt | 8 +++++- 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 977b027bd..28cd1483f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -1842,15 +1842,15 @@ object LocalCache : ILocalCache { note.addRelay(relay) } - var isVerified = + val isVerified = try { val cachePath = Amethyst.instance.nip95cache cachePath.mkdirs() val file = File(cachePath, event.id) if (!file.exists() && (wasVerified || justVerify(event))) { - val stream = FileOutputStream(file) - stream.write(event.decode()) - stream.close() + FileOutputStream(file).use { stream -> + stream.write(event.decode()) + } Log.i( "FileStorageEvent", "NIP95 File received from $relay and saved to disk as $file", @@ -2134,7 +2134,7 @@ object LocalCache : ILocalCache { } } - suspend fun findStatusesForUser(user: User): ImmutableList { + fun findStatusesForUser(user: User): ImmutableList { checkNotInMainThread() return addressables @@ -2151,7 +2151,7 @@ object LocalCache : ILocalCache { .toImmutableList() } - suspend fun findEarliestOtsForNote( + fun findEarliestOtsForNote( note: Note, resolverBuilder: OtsResolverBuilder, ): Long? { @@ -2178,7 +2178,7 @@ object LocalCache : ILocalCache { fun cachedModificationEventsForNote(note: Note): List? = modificationCache[note.idHex] - suspend fun findLatestModificationForNote(note: Note): List { + fun findLatestModificationForNote(note: Note): List { checkNotInMainThread() val noteAuthor = note.author ?: return emptyList() @@ -2340,9 +2340,9 @@ object LocalCache : ILocalCache { val children = if (noteEvent is WrappedEvent) { noteEvent.host?.id?.let { - getNoteIfExists(it)?.let { - removeFromCache(it) - it.removeAllChildNotes() + getNoteIfExists(it)?.let { it2 -> + removeFromCache(it2) + it2.removeAllChildNotes() } } } else { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt index 7979093cb..f65550733 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt @@ -96,19 +96,21 @@ class BlossomUploader { checkNotNull(imageInputStream) { "Can't open the image input stream" } - return upload( - imageInputStream, - hash, - payload.size, - fileName, - myContentType, - alt, - sensitiveContent, - serverBaseUrl, - okHttpClient, - httpAuth, - context, - ) + return imageInputStream.use { stream -> + upload( + stream, + hash, + payload.size, + fileName, + myContentType, + alt, + sensitiveContent, + serverBaseUrl, + okHttpClient, + httpAuth, + context, + ) + } } fun encodeAuth(event: BlossomAuthorizationEvent): String { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt index b6acdfde8..ca9814468 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt @@ -109,18 +109,20 @@ class Nip96Uploader { checkNotNull(imageInputStream) { "Can't open the image input stream" } - return upload( - imageInputStream, - length, - myContentType, - alt, - sensitiveContent, - server, - okHttpClient, - onProgress, - httpAuth, - context, - ) + return imageInputStream.use { stream -> + upload( + stream, + length, + myContentType, + alt, + sensitiveContent, + server, + okHttpClient, + onProgress, + httpAuth, + context, + ) + } } suspend fun upload( diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt index 341b5a0d4..cfc02fa5d 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt @@ -67,10 +67,10 @@ class Request( "Content-Length", "" + this.data!!.size.toString(), ) - val wr = DataOutputStream(httpURLConnection.getOutputStream()) - wr.write(this.data, 0, this.data!!.size) - wr.flush() - wr.close() + DataOutputStream(httpURLConnection.getOutputStream()).use { wr -> + wr.write(this.data, 0, this.data!!.size) + wr.flush() + } } else { httpURLConnection.setRequestMethod("GET") } diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt index dc44a417f..8f50c6a78 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip03Timestamp.ots.http import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.json.JsonMapper import java.io.ByteArrayOutputStream +import java.io.Closeable import java.io.IOException import java.io.InputStream import java.nio.charset.StandardCharsets @@ -30,7 +31,7 @@ import java.nio.charset.StandardCharsets /** * Holds the response from an HTTP request. */ -class Response { +class Response : Closeable { private var stream: InputStream? = null var fromUrl: String? = null @@ -79,4 +80,9 @@ class Response { JsonMapper.builder().build() return builder.readTree(jsonString) } + + override fun close() { + stream?.close() + stream = null + } } From adc4aa7906d22188bdfcffffd2d70f76ca3e97c9 Mon Sep 17 00:00:00 2001 From: davotoula Date: Sun, 7 Sep 2025 20:14:58 +0200 Subject: [PATCH 4/5] cleaner code --- .../quartz/nip03Timestamp/ots/http/Request.kt | 10 +++++----- .../quartz/nip03Timestamp/ots/http/Response.kt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt index cfc02fa5d..025d4d7c1 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt @@ -50,8 +50,8 @@ class Request( try { val httpURLConnection = url.openConnection() as HttpURLConnection - httpURLConnection.setReadTimeout(10000) - httpURLConnection.setConnectTimeout(10000) + httpURLConnection.readTimeout = 10000 + httpURLConnection.connectTimeout = 10000 httpURLConnection.setRequestProperty("User-Agent", "OpenTimestamps Java") httpURLConnection.setRequestProperty("Accept", "application/json") httpURLConnection.setRequestProperty("Accept-Encoding", "gzip") @@ -62,7 +62,7 @@ class Request( if (data != null) { httpURLConnection.setDoOutput(true) - httpURLConnection.setRequestMethod("POST") + httpURLConnection.requestMethod = "POST" httpURLConnection.setRequestProperty( "Content-Length", "" + this.data!!.size.toString(), @@ -72,7 +72,7 @@ class Request( wr.flush() } } else { - httpURLConnection.setRequestMethod("GET") + httpURLConnection.requestMethod = "GET" } httpURLConnection.connect() @@ -84,7 +84,7 @@ class Request( response.status = responseCode response.fromUrl = url.toString() var `is` = httpURLConnection.getInputStream() - if ("gzip" == httpURLConnection.getContentEncoding()) { + if ("gzip" == httpURLConnection.contentEncoding) { `is` = GZIPInputStream(`is`) } response.setStream(`is`) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt index 8f50c6a78..403f8bbfc 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt @@ -54,7 +54,7 @@ class Response : Closeable { @get:Throws(IOException::class) val string: String - get() = kotlin.text.String(this.bytes, StandardCharsets.UTF_8) + get() = String(this.bytes, StandardCharsets.UTF_8) @get:Throws(IOException::class) val bytes: ByteArray From 8f623ebb91322e65d15c9dfbcb8bb34b12d6025c Mon Sep 17 00:00:00 2001 From: davotoula Date: Mon, 8 Sep 2025 16:52:24 +0200 Subject: [PATCH 5/5] add TODO to investigate. Double closing of stream most often is ignored. --- .../com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt index e44a3053e..9675a8fe1 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt @@ -93,6 +93,7 @@ abstract class OpCrypto internal constructor() : OpUnary() { count = inputStream.read(chunk, 0, 1048576) } + // TODO: Is this needed? Closing of stream should be callers responsibility? inputStream.close() val hash = digest.digest()