Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/kotlin/com/wire/android/WireApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectFileUriExposure()

Check warning on line 157 in app/src/main/kotlin/com/wire/android/WireApplication.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/WireApplication.kt#L157

Added line #L157 was not covered by tests
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
saveToDeviceIfInvalid: Boolean = false,
specifiedMimeType: String? = null, // specify a particular mimetype, otherwise it will be taken from the uri / file extension
): Result = withContext(dispatchers.io()) {
if (!isValidUriSchema(uri)) {
return@withContext Result.Failure.Unknown

Check warning on line 44 in app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/HandleUriAssetUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/HandleUriAssetUseCase.kt#L44

Added line #L44 was not covered by tests
}

val tempAssetPath = kaliumFileSystem.tempFilePath(UUID.randomUUID().toString())
val assetBundle = fileManager.getAssetBundleFromUri(
attachmentUri = uri,
Expand Down Expand Up @@ -72,6 +76,19 @@
}
}

/**
* Handles the correctness of the supported schema of the URI.
*/
@Suppress("TooGenericExceptionCaught")
private fun isValidUriSchema(uri: Uri): Boolean {
return try {
fileManager.checkValidSchema(uri)
true
} catch (e: Exception) {
false

Check warning on line 88 in app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/HandleUriAssetUseCase.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/HandleUriAssetUseCase.kt#L87-L88

Added lines #L87 - L88 were not covered by tests
}
}

companion object {
private const val sizeOf1MB = 1024 * 1024
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wire.android.appLogger
Expand All @@ -37,6 +36,7 @@
import com.wire.android.util.SUPPORTED_AUDIO_MIME_TYPE
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.android.util.fileDateTime
import com.wire.android.util.fromNioPathToContentUri
import com.wire.android.util.getAudioLengthInMs
import com.wire.android.util.ui.UIText
import com.wire.kalium.logic.data.asset.KaliumFileSystem
Expand Down Expand Up @@ -321,12 +321,12 @@
onAudioRecorded(
UriAsset(
uri = if (didSucceed) {
audioMediaRecorder.mp4OutputPath!!.toFile().toUri()
context.fromNioPathToContentUri(nioPath = audioMediaRecorder.mp4OutputPath!!.toNioPath())

Check warning on line 324 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioViewModel.kt#L324

Added line #L324 was not covered by tests
} else {
if (state.shouldApplyEffects) {
state.effectsOutputFile!!.toUri()
context.fromNioPathToContentUri(nioPath = state.effectsOutputFile!!.toPath())

Check warning on line 327 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioViewModel.kt#L327

Added line #L327 was not covered by tests
} else {
state.originalOutputFile!!.toUri()
context.fromNioPathToContentUri(nioPath = state.originalOutputFile!!.toPath())

Check warning on line 329 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioViewModel.kt#L329

Added line #L329 was not covered by tests
}
},
mimeType = if (didSucceed) {
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/kotlin/com/wire/android/util/FileManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@
dispatcher: DispatcherProvider = DefaultDispatcherProvider(),
): AssetBundle? = withContext(dispatcher.io()) {
try {
// validate if the uri has a valid schema
checkValidSchema(attachmentUri)

Check warning on line 117 in app/src/main/kotlin/com/wire/android/util/FileManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/util/FileManager.kt#L117

Added line #L117 was not covered by tests
val assetKey = UUID.randomUUID().toString()
val assetFileName = context.getFileName(attachmentUri)
?: throw IOException("The selected asset has an invalid name")
Expand All @@ -137,8 +139,20 @@
}
}

/**
* Validates the schema of the given Uri.
* We are excluding file, as we don't process file URIs.
*
* If invalid schema is found, an [IllegalArgumentException] is thrown.
*/
fun checkValidSchema(uri: Uri) {
appLogger.d("Validating Uri schema for path: ${uri.path} with scheme: ${uri.scheme}")

Check warning on line 149 in app/src/main/kotlin/com/wire/android/util/FileManager.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/util/FileManager.kt#L149

Added line #L149 was not covered by tests
if (INVALID_SCHEMA.equals(uri.scheme, ignoreCase = true)) throw IllegalArgumentException("File URI is not supported")
}

companion object {
private const val TEMP_IMG_ATTACHMENT_FILENAME = "image_attachment.jpg"
private const val TEMP_VIDEO_ATTACHMENT_FILENAME = "video_attachment.mp4"
private const val INVALID_SCHEMA = "file"
}
}
3 changes: 3 additions & 0 deletions app/src/main/kotlin/com/wire/android/util/FileUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import okio.Path
import okio.Path.Companion.toOkioPath
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
Expand Down Expand Up @@ -179,6 +180,8 @@
return insertedUri
}

fun Context.fromNioPathToContentUri(nioPath: java.nio.file.Path): Uri = this.pathToUri(nioPath.toOkioPath(), null)

Check warning on line 183 in app/src/main/kotlin/com/wire/android/util/FileUtil.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/util/FileUtil.kt#L183

Added line #L183 was not covered by tests

fun Context.pathToUri(assetDataPath: Path, assetName: String?): Uri =
FileProvider.getUriForFile(this, getProviderAuthority(), assetDataPath.toFile(), assetName ?: assetDataPath.name)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
*/
package com.wire.android.ui.home.conversations.usecase

import android.app.Application
import android.net.Uri
import androidx.core.net.toUri
import com.wire.android.config.CoroutineTestExtension
import com.wire.android.config.TestDispatcherProvider
import com.wire.android.framework.FakeKaliumFileSystem
import com.wire.android.ui.home.conversations.model.AssetBundle
Expand All @@ -30,19 +31,40 @@ import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import okio.Path.Companion.toPath
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@OptIn(ExperimentalCoroutinesApi::class)
@ExtendWith(CoroutineTestExtension::class)
@RunWith(RobolectricTestRunner::class)
@Config(application = Application::class)
class HandleUriAssetUseCaseTest {

private val dispatcher = StandardTestDispatcher()

@Test
fun `given an invalid url schema, when invoked, then result should not succeed`() =
runTest(dispatcher) {
// Given
val limit = GetAssetSizeLimitUseCaseImpl.ASSET_SIZE_DEFAULT_LIMIT_BYTES
val (_, useCase) = Arrangement()
.withGetAssetSizeLimitUseCase(true, limit)
.withGetAssetBundleFromUri(null)
.arrange()

// When
val result = useCase.invoke(Uri.Builder().scheme("file").path("/data/asdasdasd.txt").build(), false)

// Then
assert(result is HandleUriAssetUseCase.Result.Failure.Unknown)
}

@Test
fun `given a user picks an image asset less than limit, when invoked, then result should succeed`() =
runTest {
runTest(dispatcher) {
// Given
val limit = GetAssetSizeLimitUseCaseImpl.ASSET_SIZE_DEFAULT_LIMIT_BYTES
val mockedAttachment = AssetBundle(
Expand All @@ -67,7 +89,7 @@ class HandleUriAssetUseCaseTest {

@Test
fun `given a user picks an image asset larger than limit, when invoked, then result is asset too large failure`() =
runTest {
runTest(dispatcher) {
// Given
val limit = GetAssetSizeLimitUseCaseImpl.ASSET_SIZE_DEFAULT_LIMIT_BYTES
val mockedAttachment = AssetBundle(
Expand All @@ -92,7 +114,7 @@ class HandleUriAssetUseCaseTest {

@Test
fun `given that a user picks too large asset that needs saving if invalid, when invoked, then saveToExternalMediaStorage is called`() =
runTest {
runTest(dispatcher) {
// Given
val limit = GetAssetSizeLimitUseCaseImpl.ASSET_SIZE_DEFAULT_LIMIT_BYTES
val mockedAttachment = AssetBundle(
Expand Down Expand Up @@ -127,7 +149,7 @@ class HandleUriAssetUseCaseTest {

@Test
fun `given that a user picks asset, when getting uri returns null, then it should return error`() =
runTest {
runTest(dispatcher) {
// Given
val limit = GetAssetSizeLimitUseCaseImpl.ASSET_SIZE_DEFAULT_LIMIT_BYTES
val (_, useCase) = Arrangement()
Expand Down
Loading