Skip to content

Commit

Permalink
fix: Validate imported presets to prevent crashes
Browse files Browse the repository at this point in the history
  • Loading branch information
timschneeb committed Oct 29, 2022
1 parent e2a3fab commit 128dcd3
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,11 @@ class FileLibraryDialogFragment : ListPreferenceDialogFragmentCompat() {
builder.setAdapter(createPresetAdapter()) { _, position ->
val name = fileLibPreference.entries[position]
val path = fileLibPreference.entryValues[position]
Preset(File(path.toString()).name).load()
showMessage(getString(R.string.filelibrary_preset_loaded, name))
val result = Preset(File(path.toString()).name).load() != null
if(result)
showMessage(getString(R.string.filelibrary_preset_loaded, name))
else
showMessage(getString(R.string.filelibrary_preset_load_failed, name))
this.dismiss()
}
}
Expand Down Expand Up @@ -295,11 +298,21 @@ class FileLibraryDialogFragment : ListPreferenceDialogFragmentCompat() {
return
}

StorageUtils.openInputStreamSafe(requireContext(), uri)?.use {
if(!fileLibPreference.hasValidContent(it)) {
Timber.e("File rejected due to invalid content")
requireContext().showAlert(R.string.filelibrary_corrupted_title,
R.string.filelibrary_corrupted)
return@also
}
}

val file = StorageUtils.importFile(requireContext(),
fileLibPreference.directory?.absolutePath ?: "", uri)
if(file == null)
{
Timber.e("Failed to import file")
return
}

CoroutineScope(Dispatchers.Main).launch {
Expand Down
104 changes: 80 additions & 24 deletions app/src/main/java/me/timschneeberger/rootlessjamesdsp/model/Preset.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ class Preset(val name: String): KoinComponent {
return file().renameTo(File(externalPath, newName))
}

fun load(): PresetMetadata {
fun validate(): Boolean {
return Companion.validate(FileInputStream(file()))
}

fun load(): PresetMetadata? {

val file = file()
Timber.d("Loading preset from ${file.path}")

Expand All @@ -37,27 +42,39 @@ class Preset(val name: String): KoinComponent {
targetFolder.mkdir()

val metadataBytes = ByteArrayOutputStream()
TarInputStream(BufferedInputStream(FileInputStream(file))).use { tis ->
var entry: TarEntry?
while (tis.nextEntry.also { entry = it } != null) {
val entryName = entry?.name
entryName ?: break

var count: Int
val data = ByteArray(2048)
BufferedOutputStream(FileOutputStream(
targetFolder.absolutePath + "/" + entryName
)).use { dest ->
while (tis.read(data).also { count = it } != -1) {
if(entryName == "metadata")
metadataBytes.write(data)
else
dest.write(data, 0, count)
try {
TarInputStream(BufferedInputStream(FileInputStream(file))).use { tis ->
var entry: TarEntry?
while (tis.nextEntry.also { entry = it } != null) {
val entryName = entry?.name
entryName ?: break

if (!isKnownEntry(entryName)) {
Timber.w("Unknown entry name: $entryName")
continue
}

var count: Int
val data = ByteArray(2048)
BufferedOutputStream(FileOutputStream(
targetFolder.absolutePath + "/" + entryName
)).use { dest ->
while (tis.read(data).also { count = it } != -1) {
if (entryName == "metadata")
metadataBytes.write(data)
else
dest.write(data, 0, count)
}
dest.flush()
}
dest.flush()
}
metadataBytes.flush()
}
metadataBytes.flush()
}
catch(ex: IOException) {
Timber.e("Preset extraction failed.")
Timber.w(ex)
return null
}

val metadata = mutableMapOf<String, String>()
Expand All @@ -70,12 +87,15 @@ class Preset(val name: String): KoinComponent {

Timber.d("Loaded preset file version ${metadata[META_VERSION]}")

targetFolder.listFiles()?.forEach next@ { f ->
if(!f.name.startsWith("dsp_") || f.extension != "xml") {
if (f.name != "metadata")
Timber.w("load: Unknown file in archive ${f.name}")
val files = targetFolder.listFiles()
if(files == null || files.isEmpty()) {
Timber.e("Preset archive did not contain any useful data")
return null
}

files.forEach next@ { f ->
if(!isKnownEntry(f.name) || f.name == "metadata")
return@next
}

val target = File(currentPath, f.name)
f.copyTo(target, overwrite = true)
Expand Down Expand Up @@ -135,6 +155,42 @@ class Preset(val name: String): KoinComponent {
const val META_VERSION = "version"
const val META_APP_VERSION = "app_version"
const val META_APP_FLAVOR = "app_flavor"

private fun isKnownEntry(n: String) = (n.startsWith("dsp_") && n.endsWith("xml")) || n == "metadata"

fun validate(stream: InputStream): Boolean {
Timber.d("Validating preset")

var knownCount = 0
try {
TarInputStream(BufferedInputStream(stream)).use { tis ->
var entry: TarEntry?
while (tis.nextEntry.also { entry = it } != null) {
val entryName = entry?.name
entryName ?: break

if (!isKnownEntry(entryName)) {
Timber.w("Unknown entry name: $entryName")
continue
}

knownCount++
}
}
}
catch(ex: IOException) {
Timber.e("Preset validation failed.")
Timber.w(ex)
return false
}

if (knownCount < 1) {
Timber.e("Preset archive did not contain any useful data")
return false
}

return true
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import androidx.preference.ListPreference
import androidx.preference.Preference.SummaryProvider
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.fragment.FileLibraryDialogFragment
import me.timschneeberger.rootlessjamesdsp.model.Preset
import java.io.File
import java.io.InputStream


class FileLibraryPreference(context: Context, attrs: AttributeSet?) :
Expand Down Expand Up @@ -93,6 +95,13 @@ class FileLibraryPreference(context: Context, attrs: AttributeSet?) :
(isPreset() && hasPresetExtension(it))
}

fun hasValidContent(stream: InputStream): Boolean {
return if (isPreset())
Preset.validate(stream)
else
true
}

fun isLiveprog(): Boolean {
return type.lowercase() == "liveprog"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ object StorageUtils {
return destinationFilename
}

fun openInputStreamSafe(context: Context, uri: Uri): InputStream? {
return try {
context.contentResolver.openInputStream(uri)
} catch (ex: Exception) {
Timber.e(ex.message)
ex.printStackTrace()
null
}
}

private fun createFileFromStream(ins: InputStream, destination: File?): Boolean {
try {
FileOutputStream(destination).use { os ->
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@
<string name="filelibrary_context_new_preset_long">New preset</string>
<string name="filelibrary_unsupported_format_title">Unsupported file type</string>
<string name="filelibrary_unsupported_format">The selected file has not have the correct file extension.</string>
<string name="filelibrary_corrupted_title">Unsupported or corrupted file</string>
<string name="filelibrary_corrupted">The selected file contains no useable data or is corrupted.</string>
<string name="filelibrary_access_fail">Failed to access directory</string>
<string name="filelibrary_no_file_selected">No file selected</string>
<string name="filelibrary_new_file_name">New file name</string>
Expand All @@ -345,6 +347,7 @@
<string name="filelibrary_renamed">Renamed to \'%1$s\'</string>
<string name="filelibrary_deleted">\'%1$s\' deleted</string>
<string name="filelibrary_preset_loaded">Preset \'%1$s\' loaded</string>
<string name="filelibrary_preset_load_failed">File \'%1$s\' is not compatible with this app</string>
<string name="filelibrary_no_presets">No presets saved. Tap \'Add\' to create a new one.</string>

<!-- JamesDSP engine messages -->
Expand Down

0 comments on commit 128dcd3

Please sign in to comment.