Skip to content

Commit

Permalink
Closes issue mozilla-mobile#7983: Generate a file name when the conte…
Browse files Browse the repository at this point in the history
…nt provider doesn't provide one.
  • Loading branch information
Amejia481 committed Aug 13, 2020
1 parent 147bfed commit b9e694a
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

package mozilla.components.browser.engine.gecko.prompt

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.storage.Login
Expand All @@ -18,7 +16,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlin.sanitizeFileName
import mozilla.components.support.ktx.android.net.getFileName
import mozilla.components.support.ktx.kotlin.toDate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
Expand Down Expand Up @@ -550,17 +548,6 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
}
return Uri.parse("file:///${temporalFile.absolutePath}")
}

private fun Uri.getFileName(contentResolver: ContentResolver): String {
val returnUri = this
var fileName = ""
contentResolver.query(returnUri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
fileName = cursor.getString(nameIndex)
}
return fileName.sanitizeFileName()
}
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.spy
import org.mockito.Mockito.doReturn
import org.mozilla.gecko.util.GeckoBundle
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoSession
Expand All @@ -35,8 +38,7 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.WEE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.ANY
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.NONE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.USER
import org.robolectric.Shadows.shadowOf
import java.io.FileInputStream
import java.io.InputStream
import java.security.InvalidParameterException
import java.util.Calendar
import java.util.Calendar.YEAR
Expand Down Expand Up @@ -549,17 +551,17 @@ class GeckoPromptDelegateTest {

@Test
fun `Calling onFilePrompt must provide a FilePicker PromptRequest`() {
val context = testContext

val context = spy(testContext)
val contentResolver = spy(context.contentResolver)
val mockSession = GeckoEngineSession(runtime)
var onSingleFileSelectedWasCalled = false
var onMultipleFilesSelectedWasCalled = false
var onDismissWasCalled = false
val mockUri: Uri = mock()
val mockFileInput: FileInputStream = mock()
val shadowContentResolver = shadowOf(context.contentResolver)

shadowContentResolver.registerInputStream(mockUri, mockFileInput)
doReturn(contentResolver).`when`(context).contentResolver
doReturn(mock<InputStream>()).`when`(contentResolver).openInputStream(any())

var filePickerRequest: PromptRequest.File = mock()

val promptDelegate = GeckoPromptDelegate(mockSession)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

package mozilla.components.browser.engine.gecko.prompt

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.storage.Login
Expand All @@ -18,7 +16,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlin.sanitizeFileName
import mozilla.components.support.ktx.android.net.getFileName
import mozilla.components.support.ktx.kotlin.toDate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
Expand Down Expand Up @@ -550,17 +548,6 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
}
return Uri.parse("file:///${temporalFile.absolutePath}")
}

private fun Uri.getFileName(contentResolver: ContentResolver): String {
val returnUri = this
var fileName = ""
contentResolver.query(returnUri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
fileName = cursor.getString(nameIndex)
}
return fileName.sanitizeFileName()
}
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.spy
import org.mockito.Mockito.doReturn
import org.mozilla.gecko.util.GeckoBundle
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoSession
Expand All @@ -35,8 +38,7 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.WEE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.ANY
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.NONE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.USER
import org.robolectric.Shadows.shadowOf
import java.io.FileInputStream
import java.io.InputStream
import java.security.InvalidParameterException
import java.util.Calendar
import java.util.Calendar.YEAR
Expand Down Expand Up @@ -549,17 +551,17 @@ class GeckoPromptDelegateTest {

@Test
fun `Calling onFilePrompt must provide a FilePicker PromptRequest`() {
val context = testContext

val context = spy(testContext)
val contentResolver = spy(context.contentResolver)
val mockSession = GeckoEngineSession(runtime)
var onSingleFileSelectedWasCalled = false
var onMultipleFilesSelectedWasCalled = false
var onDismissWasCalled = false
val mockUri: Uri = mock()
val mockFileInput: FileInputStream = mock()
val shadowContentResolver = shadowOf(context.contentResolver)

shadowContentResolver.registerInputStream(mockUri, mockFileInput)
doReturn(contentResolver).`when`(context).contentResolver
doReturn(mock<InputStream>()).`when`(contentResolver).openInputStream(any())

var filePickerRequest: PromptRequest.File = mock()

val promptDelegate = GeckoPromptDelegate(mockSession)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

package mozilla.components.browser.engine.gecko.prompt

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.storage.Login
Expand All @@ -18,7 +16,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlin.sanitizeFileName
import mozilla.components.support.ktx.android.net.getFileName
import mozilla.components.support.ktx.kotlin.toDate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.GeckoResult
Expand Down Expand Up @@ -550,17 +548,6 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
}
return Uri.parse("file:///${temporalFile.absolutePath}")
}

private fun Uri.getFileName(contentResolver: ContentResolver): String {
val returnUri = this
var fileName = ""
contentResolver.query(returnUri, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
cursor.moveToFirst()
fileName = cursor.getString(nameIndex)
}
return fileName.sanitizeFileName()
}
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.spy
import org.mockito.Mockito.doReturn
import org.mozilla.gecko.util.GeckoBundle
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoSession
Expand All @@ -35,8 +38,7 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.WEE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.ANY
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.NONE
import org.mozilla.geckoview.GeckoSession.PromptDelegate.FilePrompt.Capture.USER
import org.robolectric.Shadows.shadowOf
import java.io.FileInputStream
import java.io.InputStream
import java.security.InvalidParameterException
import java.util.Calendar
import java.util.Calendar.YEAR
Expand Down Expand Up @@ -549,17 +551,16 @@ class GeckoPromptDelegateTest {

@Test
fun `Calling onFilePrompt must provide a FilePicker PromptRequest`() {
val context = testContext

val context = spy(testContext)
val contentResolver = spy(context.contentResolver)
val mockSession = GeckoEngineSession(runtime)
var onSingleFileSelectedWasCalled = false
var onMultipleFilesSelectedWasCalled = false
var onDismissWasCalled = false
val mockUri: Uri = mock()
val mockFileInput: FileInputStream = mock()
val shadowContentResolver = shadowOf(context.contentResolver)

shadowContentResolver.registerInputStream(mockUri, mockFileInput)
doReturn(contentResolver).`when`(context).contentResolver
doReturn(mock<InputStream>()).`when`(contentResolver).openInputStream(any())
var filePickerRequest: PromptRequest.File = mock()

val promptDelegate = GeckoPromptDelegate(mockSession)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import androidx.annotation.VisibleForTesting
import mozilla.components.support.ktx.kotlin.sanitizeFileName
import java.io.File
import java.io.IOException
import java.util.UUID

private val commonPrefixes = listOf("www.", "mobile.", "m.")

Expand Down Expand Up @@ -85,3 +90,55 @@ fun Uri.isUnderPrivateAppDirectory(context: Context): Boolean {
else -> false
}
}

/**
* Return a file name for [this] give Uri.
* @return A file name for the content, or generated file name if the URL is invalid or the type is unknown
*/
fun Uri.getFileName(contentResolver: ContentResolver): String {
return when (this.scheme) {
ContentResolver.SCHEME_FILE -> File(path ?: "").name.sanitizeFileName()
ContentResolver.SCHEME_CONTENT -> getFileNameForContentUris(contentResolver)
else -> {
generateFileName(getFileExtension(contentResolver))
}
}
}

/**
* Return a file extension for [this] give Uri (only supports content:// schemes).
* @return A file extension for the content, or empty string if the URL is invalid or the type is unknown
*/
fun Uri.getFileExtension(contentResolver: ContentResolver): String {
return MimeTypeMap.getSingleton().getExtensionFromMimeType(contentResolver.getType(this)) ?: ""
}

@VisibleForTesting
internal fun Uri.getFileNameForContentUris(contentResolver: ContentResolver): String {
var fileName = ""
contentResolver.query(this, null, null, null, null)?.use { cursor ->
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val fileExtension = getFileExtension(contentResolver)
fileName = if (nameIndex == -1) {
generateFileName(fileExtension)
} else {
cursor.moveToFirst()
cursor.getString(nameIndex) ?: generateFileName(fileExtension)
}
}
return fileName.sanitizeFileName()
}

/**
* Generate a file name using a randomUUID + the current timestamp.
*/
@VisibleForTesting
internal fun generateFileName(fileExtension: String = ""): String {
val randomId = UUID.randomUUID().toString().removePrefix("-").trim()
val timeStamp = System.currentTimeMillis()
return if (fileExtension.isNotEmpty()) {
"$randomId$timeStamp.$fileExtension"
} else {
"$randomId$timeStamp"
}
}
Loading

0 comments on commit b9e694a

Please sign in to comment.