From 5c7458f16d76b6bebee452d4fccf972713ce096b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 16:01:07 +0000 Subject: [PATCH 1/5] Update dependency com.squareup.okhttp3:okhttp-bom to v5 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f8f166eca..b900e9ab99 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -131,7 +131,7 @@ accompanist_permission = { module = "com.google.accompanist:accompanist-permissi squareup_seismic = "com.squareup:seismic:1.0.3" # network -network_okhttp_bom = "com.squareup.okhttp3:okhttp-bom:4.12.0" +network_okhttp_bom = "com.squareup.okhttp3:okhttp-bom:5.0.0" network_okhttp_logging = { module = "com.squareup.okhttp3:logging-interceptor" } network_okhttp_okhttp = { module = "com.squareup.okhttp3:okhttp" } network_okhttp = { module = "com.squareup.okhttp3:okhttp" } From f87597fe9ccb6d0b851f48d603b525b987c98063 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Jul 2025 16:40:15 +0200 Subject: [PATCH 2/5] Fix compilation issue. --- .../rageshake/impl/reporter/BugReporterMultipartBody.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/features/rageshake/impl/src/main/java/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.java b/features/rageshake/impl/src/main/java/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.java index da198e431a..25b857aa9d 100755 --- a/features/rageshake/impl/src/main/java/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.java +++ b/features/rageshake/impl/src/main/java/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.java @@ -15,7 +15,6 @@ import java.util.UUID; import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.RequestBody; -import okhttp3.internal.Util; import okio.Buffer; import okio.BufferedSink; import okio.ByteString; @@ -56,7 +55,7 @@ public class BugReporterMultipartBody extends RequestBody { private BugReporterMultipartBody(ByteString boundary, List parts) { mBoundary = boundary; mContentType = MediaType.parse(FORM + "; boundary=" + boundary.utf8()); - mParts = Util.toImmutableList(parts); + mParts = parts; } @Override From 0bcb3a704b2f202d6d57684e1e3eaa4697314d9d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Jul 2025 11:10:45 +0200 Subject: [PATCH 3/5] Ensure big log does not crash the application --- .../libraries/network/interceptors/FormattedJsonHttpLogger.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt index 936662fb54..e2fad575f4 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.network.interceptors +import io.element.android.libraries.core.extensions.ellipsize import okhttp3.logging.HttpLoggingInterceptor import org.json.JSONArray import org.json.JSONException @@ -28,7 +29,7 @@ internal class FormattedJsonHttpLogger( */ @Synchronized override fun log(message: String) { - Timber.v(message) + Timber.v(message.ellipsize(200_000)) // Try to log formatted Json only if there is a chance that [message] contains Json. // It can be only the case if we log the bodies of Http requests. From 772e060c0f0f81c47b05330fa3304423379c9ff8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Jul 2025 10:45:17 +0200 Subject: [PATCH 4/5] Convert BugReporterMultipartBody to Kotlin, and update using latest version of okhttp3.MultipartBody. Adapt to Element code --- .../reporter/BugReporterMultipartBody.java | 289 ------------ .../impl/reporter/BugReporterMultipartBody.kt | 420 ++++++++++++++++++ .../BugReporterMultipartBodyListener.kt | 18 + 3 files changed, 438 insertions(+), 289 deletions(-) delete mode 100755 features/rageshake/impl/src/main/java/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.java create mode 100755 features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt create mode 100644 features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBodyListener.kt diff --git a/features/rageshake/impl/src/main/java/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.java b/features/rageshake/impl/src/main/java/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.java deleted file mode 100755 index 25b857aa9d..0000000000 --- a/features/rageshake/impl/src/main/java/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.rageshake.impl.reporter; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import okhttp3.Headers; -import okhttp3.MediaType; -import okhttp3.RequestBody; -import okio.Buffer; -import okio.BufferedSink; -import okio.ByteString; - -// simplified version of MultipartBody (OkHttp 3.6.0) -public class BugReporterMultipartBody extends RequestBody { - - /** - * Listener - */ - public interface WriteListener { - /** - * Upload listener - * - * @param totalWritten total written bytes - * @param contentLength content length - */ - void onWrite(long totalWritten, long contentLength); - } - - private static final MediaType FORM = MediaType.parse("multipart/form-data"); - - private static final byte[] COLONSPACE = {':', ' '}; - private static final byte[] CRLF = {'\r', '\n'}; - private static final byte[] DASHDASH = {'-', '-'}; - - private final ByteString mBoundary; - private final MediaType mContentType; - private final List mParts; - private long mContentLength = -1L; - - // listener - private WriteListener mWriteListener; - - // - private List mContentLengthSize = null; - - private BugReporterMultipartBody(ByteString boundary, List parts) { - mBoundary = boundary; - mContentType = MediaType.parse(FORM + "; boundary=" + boundary.utf8()); - mParts = parts; - } - - @Override - public MediaType contentType() { - return mContentType; - } - - @Override - public long contentLength() throws IOException { - long result = mContentLength; - if (result != -1L) return result; - return mContentLength = writeOrCountBytes(null, true); - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - writeOrCountBytes(sink, false); - } - - /** - * Set the listener - * - * @param listener the - */ - public void setWriteListener(WriteListener listener) { - mWriteListener = listener; - } - - /** - * Warn the listener that some bytes have been written - * - * @param totalWrittenBytes the total written bytes - */ - private void onWrite(long totalWrittenBytes) { - if ((null != mWriteListener) && (mContentLength > 0)) { - mWriteListener.onWrite(totalWrittenBytes, mContentLength); - } - } - - /** - * Either writes this request to {@code sink} or measures its content length. We have one method - * do double-duty to make sure the counting and content are consistent, particularly when it comes - * to awkward operations like measuring the encoded length of header strings, or the - * length-in-digits of an encoded integer. - */ - private long writeOrCountBytes(BufferedSink sink, boolean countBytes) throws IOException { - long byteCount = 0L; - - Buffer byteCountBuffer = null; - if (countBytes) { - sink = byteCountBuffer = new Buffer(); - mContentLengthSize = new ArrayList<>(); - } - - for (int p = 0, partCount = mParts.size(); p < partCount; p++) { - Part part = mParts.get(p); - Headers headers = part.headers; - RequestBody body = part.body; - - sink.write(DASHDASH); - sink.write(mBoundary); - sink.write(CRLF); - - if (headers != null) { - for (int h = 0, headerCount = headers.size(); h < headerCount; h++) { - sink.writeUtf8(headers.name(h)) - .write(COLONSPACE) - .writeUtf8(headers.value(h)) - .write(CRLF); - } - } - - MediaType contentType = body.contentType(); - if (contentType != null) { - sink.writeUtf8("Content-Type: ") - .writeUtf8(contentType.toString()) - .write(CRLF); - } - - int contentLength = (int) body.contentLength(); - if (contentLength != -1) { - sink.writeUtf8("Content-Length: ") - .writeUtf8(contentLength + "") - .write(CRLF); - } else if (countBytes) { - // We can't measure the body's size without the sizes of its components. - byteCountBuffer.clear(); - return -1L; - } - - sink.write(CRLF); - - if (countBytes) { - byteCount += contentLength; - mContentLengthSize.add(byteCount); - } else { - body.writeTo(sink); - - // warn the listener of upload progress - // sink.buffer().size() does not give the right value - // assume that some data are popped - if ((null != mContentLengthSize) && (p < mContentLengthSize.size())) { - onWrite(mContentLengthSize.get(p)); - } - } - sink.write(CRLF); - } - - sink.write(DASHDASH); - sink.write(mBoundary); - sink.write(DASHDASH); - sink.write(CRLF); - - if (countBytes) { - byteCount += byteCountBuffer.size(); - byteCountBuffer.clear(); - } - - return byteCount; - } - - private static void appendQuotedString(StringBuilder target, String key) { - target.append('"'); - for (int i = 0, len = key.length(); i < len; i++) { - char ch = key.charAt(i); - switch (ch) { - case '\n': - target.append("%0A"); - break; - case '\r': - target.append("%0D"); - break; - case '"': - target.append("%22"); - break; - default: - target.append(ch); - break; - } - } - target.append('"'); - } - - public static final class Part { - public static Part create(Headers headers, RequestBody body) { - if (body == null) { - throw new NullPointerException("body == null"); - } - if (headers != null && headers.get("Content-Type") != null) { - throw new IllegalArgumentException("Unexpected header: Content-Type"); - } - if (headers != null && headers.get("Content-Length") != null) { - throw new IllegalArgumentException("Unexpected header: Content-Length"); - } - return new Part(headers, body); - } - - public static Part createFormData(String name, String value) { - return createFormData(name, null, RequestBody.create(value, null)); - } - - public static Part createFormData(String name, String filename, RequestBody body) { - if (name == null) { - throw new NullPointerException("name == null"); - } - StringBuilder disposition = new StringBuilder("form-data; name="); - appendQuotedString(disposition, name); - - if (filename != null) { - disposition.append("; filename="); - appendQuotedString(disposition, filename); - } - - return create(Headers.of("Content-Disposition", disposition.toString()), body); - } - - final Headers headers; - final RequestBody body; - - private Part(Headers headers, RequestBody body) { - this.headers = headers; - this.body = body; - } - } - - public static final class Builder { - private final ByteString boundary; - private final List parts = new ArrayList<>(); - - public Builder() { - this(UUID.randomUUID().toString()); - } - - public Builder(String boundary) { - this.boundary = ByteString.encodeUtf8(boundary); - } - - /** - * Add a form data part to the body. - */ - public Builder addFormDataPart(String name, String value) { - return addPart(Part.createFormData(name, value)); - } - - /** - * Add a form data part to the body. - */ - public Builder addFormDataPart(String name, String filename, RequestBody body) { - return addPart(Part.createFormData(name, filename, body)); - } - - /** - * Add a part to the body. - */ - public Builder addPart(Part part) { - if (part == null) throw new NullPointerException("part == null"); - parts.add(part); - return this; - } - - /** - * Assemble the specified parts into a request body. - */ - public BugReporterMultipartBody build() { - if (parts.isEmpty()) { - throw new IllegalStateException("Multipart body must have at least one part."); - } - return new BugReporterMultipartBody(boundary, parts); - } - } -} diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt new file mode 100755 index 0000000000..29a8e4bb27 --- /dev/null +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2014 Square, Inc. + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ +@file:Suppress( + "unused", + "KDocUnresolvedReference", + "SpellCheckingInspection", +) + +package io.element.android.features.rageshake.impl.reporter + +import kotlinx.collections.immutable.toImmutableList +import okhttp3.Headers +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody +import okio.Buffer +import okio.BufferedSink +import okio.ByteString +import okio.ByteString.Companion.encodeUtf8 +import java.io.IOException +import java.util.UUID + +/** + * Copy of [okhttp3.MultipartBody] with addition of a listener to track progress (Last imported from OkHttp 5.0.0). + * Patches are surrounded by ELEMENT-START and ELEMENT-END + */ + +/** + * An [RFC 2387][rfc_2387]-compliant request body. + * + * [rfc_2387]: http://www.ietf.org/rfc/rfc2387.txt + */ +@Suppress("NAME_SHADOWING") +class BugReporterMultipartBody internal constructor( + private val boundaryByteString: ByteString, + @get:JvmName("type") val type: MediaType, + @get:JvmName("parts") val parts: List, +) : RequestBody() { + // ELEMENT-START + private var listener: BugReporterMultipartBodyListener? = null + + private fun onWrite(totalWrittenBytes: Long) { + listener + ?.takeIf { contentLength > 0 } + ?.onWrite(totalWrittenBytes, contentLength) + } + + private val contentLengthSize = mutableListOf() + + fun setWriteListener(listener: BugReporterMultipartBodyListener?) { + this.listener = listener + } + // ELEMENT-END + + private val contentType: MediaType = "$type; boundary=$boundary".toMediaType() + private var contentLength = -1L + + @get:JvmName("boundary") + val boundary: String + get() = boundaryByteString.utf8() + + /** The number of parts in this multipart body. */ + @get:JvmName("size") + val size: Int + get() = parts.size + + fun part(index: Int): Part = parts[index] + + override fun isOneShot(): Boolean = parts.any { it.body.isOneShot() } + + /** A combination of [type] and [boundaryByteString]. */ + override fun contentType(): MediaType = contentType + + @JvmName("-deprecated_type") + @Deprecated( + message = "moved to val", + replaceWith = ReplaceWith(expression = "type"), + level = DeprecationLevel.ERROR, + ) + fun type(): MediaType = type + + @JvmName("-deprecated_boundary") + @Deprecated( + message = "moved to val", + replaceWith = ReplaceWith(expression = "boundary"), + level = DeprecationLevel.ERROR, + ) + fun boundary(): String = boundary + + @JvmName("-deprecated_size") + @Deprecated( + message = "moved to val", + replaceWith = ReplaceWith(expression = "size"), + level = DeprecationLevel.ERROR, + ) + fun size(): Int = size + + @JvmName("-deprecated_parts") + @Deprecated( + message = "moved to val", + replaceWith = ReplaceWith(expression = "parts"), + level = DeprecationLevel.ERROR, + ) + fun parts(): List = parts + + @Throws(IOException::class) + override fun contentLength(): Long { + var result = contentLength + if (result == -1L) { + result = writeOrCountBytes(null, true) + contentLength = result + } + return result + } + + @Throws(IOException::class) + override fun writeTo(sink: BufferedSink) { + writeOrCountBytes(sink, false) + } + + /** + * Either writes this request to [sink] or measures its content length. We have one method do + * double-duty to make sure the counting and content are consistent, particularly when it comes + * to awkward operations like measuring the encoded length of header strings, or the + * length-in-digits of an encoded integer. + */ + @Throws(IOException::class) + private fun writeOrCountBytes( + sink: BufferedSink?, + countBytes: Boolean, + ): Long { + var sink = sink + var byteCount = 0L + + var byteCountBuffer: Buffer? = null + if (countBytes) { + byteCountBuffer = Buffer() + sink = byteCountBuffer + // ELEMENT-START + contentLengthSize.clear() + // ELEMENT-END + } + + for (p in 0 until parts.size) { + val part = parts[p] + val headers = part.headers + val body = part.body + + sink!!.write(DASHDASH) + sink.write(boundaryByteString) + sink.write(CRLF) + + if (headers != null) { + for (h in 0 until headers.size) { + sink + .writeUtf8(headers.name(h)) + .write(COLONSPACE) + .writeUtf8(headers.value(h)) + .write(CRLF) + } + } + + val contentType = body.contentType() + if (contentType != null) { + sink + .writeUtf8("Content-Type: ") + .writeUtf8(contentType.toString()) + .write(CRLF) + } + + // We can't measure the body's size without the sizes of its components. + val contentLength = body.contentLength() + if (contentLength == -1L && countBytes) { + byteCountBuffer!!.clear() + return -1L + } + + sink.write(CRLF) + + if (countBytes) { + byteCount += contentLength + // ELEMENT-START + contentLengthSize.add(byteCount) + // ELEMENT-END + } else { + body.writeTo(sink) + // ELEMENT-START + // warn the listener of upload progress + // sink.buffer().size() does not give the right value + // assume that some data are popped + contentLengthSize.getOrNull(p)?.let { writtenByte -> + onWrite(writtenByte) + } + // ELEMENT-END + } + + sink.write(CRLF) + } + + sink!!.write(DASHDASH) + sink.write(boundaryByteString) + sink.write(DASHDASH) + sink.write(CRLF) + + if (countBytes) { + byteCount += byteCountBuffer!!.size + byteCountBuffer.clear() + } + + return byteCount + } + + class Part private constructor( + @get:JvmName("headers") val headers: Headers?, + @get:JvmName("body") val body: RequestBody, + ) { + @JvmName("-deprecated_headers") + @Deprecated( + message = "moved to val", + replaceWith = ReplaceWith(expression = "headers"), + level = DeprecationLevel.ERROR, + ) + fun headers(): Headers? = headers + + @JvmName("-deprecated_body") + @Deprecated( + message = "moved to val", + replaceWith = ReplaceWith(expression = "body"), + level = DeprecationLevel.ERROR, + ) + fun body(): RequestBody = body + + companion object { + @JvmStatic + fun create(body: RequestBody): Part = create(null, body) + + @JvmStatic + fun create( + headers: Headers?, + body: RequestBody, + ): Part { + require(headers?.get("Content-Type") == null) { "Unexpected header: Content-Type" } + require(headers?.get("Content-Length") == null) { "Unexpected header: Content-Length" } + return Part(headers, body) + } + + @JvmStatic + fun createFormData( + name: String, + value: String, + ): Part = createFormData(name, null, value.toRequestBody()) + + @JvmStatic + fun createFormData( + name: String, + filename: String?, + body: RequestBody, + ): Part { + val disposition = + buildString { + append("form-data; name=") + appendQuotedString(name) + + if (filename != null) { + append("; filename=") + appendQuotedString(filename) + } + } + + val headers = + Headers + .Builder() + .addUnsafeNonAscii("Content-Disposition", disposition) + .build() + + return create(headers, body) + } + } + } + + class Builder + @JvmOverloads + constructor( + boundary: String = UUID.randomUUID().toString(), + ) { + private val boundary: ByteString = boundary.encodeUtf8() + + // ELEMENT-START + // Element: use FORM as default type + private var type = FORM + // ELEMENT-END + private val parts = mutableListOf() + + /** + * Set the MIME type. Expected values for `type` are [MIXED] (the default), [ALTERNATIVE], + * [DIGEST], [PARALLEL] and [FORM]. + */ + fun setType(type: MediaType) = + apply { + require(type.type == "multipart") { "multipart != $type" } + this.type = type + } + + /** Add a part to the body. */ + fun addPart(body: RequestBody) = + apply { + addPart(Part.create(body)) + } + + /** Add a part to the body. */ + fun addPart( + headers: Headers?, + body: RequestBody, + ) = apply { + addPart(Part.create(headers, body)) + } + + /** Add a form data part to the body. */ + fun addFormDataPart( + name: String, + value: String, + ) = apply { + addPart(Part.createFormData(name, value)) + } + + /** Add a form data part to the body. */ + fun addFormDataPart( + name: String, + filename: String?, + body: RequestBody, + ) = apply { + addPart(Part.createFormData(name, filename, body)) + } + + /** Add a part to the body. */ + fun addPart(part: Part) = + apply { + parts += part + } + + /** Assemble the specified parts into a request body. */ + fun build(): BugReporterMultipartBody { + check(parts.isNotEmpty()) { "Multipart body must have at least one part." } + return BugReporterMultipartBody(boundary, type, parts.toImmutableList()) + } + } + + companion object { + /** + * The "mixed" subtype of "multipart" is intended for use when the body parts are independent + * and need to be bundled in a particular order. Any "multipart" subtypes that an implementation + * does not recognize must be treated as being of subtype "mixed". + */ + @JvmField + val MIXED = "multipart/mixed".toMediaType() + + /** + * The "multipart/alternative" type is syntactically identical to "multipart/mixed", but the + * semantics are different. In particular, each of the body parts is an "alternative" version of + * the same information. + */ + @JvmField + val ALTERNATIVE = "multipart/alternative".toMediaType() + + /** + * This type is syntactically identical to "multipart/mixed", but the semantics are different. + * In particular, in a digest, the default `Content-Type` value for a body part is changed from + * "text/plain" to "message/rfc822". + */ + @JvmField + val DIGEST = "multipart/digest".toMediaType() + + /** + * This type is syntactically identical to "multipart/mixed", but the semantics are different. + * In particular, in a parallel entity, the order of body parts is not significant. + */ + @JvmField + val PARALLEL = "multipart/parallel".toMediaType() + + /** + * The media-type multipart/form-data follows the rules of all multipart MIME data streams as + * outlined in RFC 2046. In forms, there are a series of fields to be supplied by the user who + * fills out the form. Each field has a name. Within a given form, the names are unique. + */ + @JvmField + val FORM = "multipart/form-data".toMediaType() + + private val COLONSPACE = byteArrayOf(':'.code.toByte(), ' '.code.toByte()) + private val CRLF = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte()) + private val DASHDASH = byteArrayOf('-'.code.toByte(), '-'.code.toByte()) + + /** + * Appends a quoted-string to a StringBuilder. + * + * RFC 2388 is rather vague about how one should escape special characters in form-data + * parameters, and as it turns out Firefox and Chrome actually do rather different things, and + * both say in their comments that they're not really sure what the right approach is. We go + * with Chrome's behavior (which also experimentally seems to match what IE does), but if you + * actually want to have a good chance of things working, please avoid double-quotes, newlines, + * percent signs, and the like in your field names. + */ + internal fun StringBuilder.appendQuotedString(key: String) { + append('"') + for (i in 0 until key.length) { + when (val ch = key[i]) { + '\n' -> append("%0A") + '\r' -> append("%0D") + '"' -> append("%22") + else -> append(ch) + } + } + append('"') + } + } +} diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBodyListener.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBodyListener.kt new file mode 100644 index 0000000000..f6fe406666 --- /dev/null +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBodyListener.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rageshake.impl.reporter + +fun interface BugReporterMultipartBodyListener { + /** + * Upload listener. + * + * @param totalWritten total written bytes + * @param contentLength content length + */ + fun onWrite(totalWritten: Long, contentLength: Long) +} From fdcde8cac884122074f16ae92446392082d436f9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 8 Jul 2025 12:41:45 +0200 Subject: [PATCH 5/5] Fix quality issue. --- .../rageshake/impl/reporter/BugReporterMultipartBody.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt index 29a8e4bb27..0fe0bfdc86 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt @@ -28,9 +28,7 @@ import java.util.UUID /** * Copy of [okhttp3.MultipartBody] with addition of a listener to track progress (Last imported from OkHttp 5.0.0). * Patches are surrounded by ELEMENT-START and ELEMENT-END - */ - -/** + * * An [RFC 2387][rfc_2387]-compliant request body. * * [rfc_2387]: http://www.ietf.org/rfc/rfc2387.txt @@ -294,6 +292,7 @@ class BugReporterMultipartBody internal constructor( // Element: use FORM as default type private var type = FORM // ELEMENT-END + private val parts = mutableListOf() /**