NewPipe/app/src/main/java/org/schabi/newpipe/settings/export/ImportExportManager.kt

181 lines
6.5 KiB
Kotlin
Raw Normal View History

package org.schabi.newpipe.settings.export
import android.content.SharedPreferences
2024-03-27 11:31:16 +00:00
import com.grack.nanojson.JsonArray
import com.grack.nanojson.JsonParser
import com.grack.nanojson.JsonParserException
import com.grack.nanojson.JsonWriter
2020-06-13 15:29:57 +00:00
import org.schabi.newpipe.streams.io.SharpOutputStream
2020-06-15 14:26:52 +00:00
import org.schabi.newpipe.streams.io.StoredFileHelper
import org.schabi.newpipe.util.ZipHelper
import java.io.FileNotFoundException
import java.io.IOException
import java.io.ObjectOutputStream
import java.util.zip.ZipOutputStream
2024-03-27 11:31:16 +00:00
class ImportExportManager(private val fileLocator: BackupFileLocator) {
companion object {
2024-03-27 11:31:16 +00:00
const val TAG = "ImportExportManager"
}
/**
* Exports given [SharedPreferences] to the file in given outputPath.
* It also creates the file.
*/
@Throws(Exception::class)
2020-06-13 15:29:57 +00:00
fun exportDatabase(preferences: SharedPreferences, file: StoredFileHelper) {
file.create()
2024-03-27 11:31:16 +00:00
ZipOutputStream(SharpOutputStream(file.stream).buffered()).use { outZip ->
// add the database
ZipHelper.addFileToZip(
outZip,
BackupFileLocator.FILE_NAME_DB,
fileLocator.db.path,
)
// add the legacy vulnerable serialized preferences (will be removed in the future)
ZipHelper.addFileToZip(
outZip,
BackupFileLocator.FILE_NAME_SERIALIZED_PREFS
) { byteOutput ->
ObjectOutputStream(byteOutput).use { output ->
output.writeObject(preferences.all)
output.flush()
2020-12-19 15:54:42 +00:00
}
}
// add the JSON preferences
ZipHelper.addFileToZip(
outZip,
BackupFileLocator.FILE_NAME_JSON_PREFS
) { byteOutput ->
JsonWriter
.indent("")
.on(byteOutput)
.`object`(preferences.all)
.done()
}
2024-03-27 11:31:16 +00:00
}
2020-12-19 15:35:30 +00:00
}
/**
* Tries to create database directory if it does not exist.
*
* @return Whether the directory exists afterwards.
*/
fun ensureDbDirectoryExists(): Boolean {
2020-12-19 15:53:11 +00:00
return fileLocator.dbDir.exists() || fileLocator.dbDir.mkdir()
}
2024-04-04 09:38:57 +00:00
/**
* Extracts the database from the given file to the app's database directory.
* The current app's database will be overwritten.
* @param file the .zip file to extract the database from
* @return true if the database was successfully extracted, false otherwise
*/
2020-06-13 15:29:57 +00:00
fun extractDb(file: StoredFileHelper): Boolean {
2024-03-27 11:31:16 +00:00
val success = ZipHelper.extractFileFromZip(
2024-04-23 18:16:04 +00:00
file,
BackupFileLocator.FILE_NAME_DB,
fileLocator.db.path,
2024-03-27 11:31:16 +00:00
)
if (success) {
fileLocator.dbJournal.delete()
fileLocator.dbWal.delete()
fileLocator.dbShm.delete()
}
return success
}
2024-03-27 11:31:16 +00:00
@Deprecated(
"Serializing preferences with Java's ObjectOutputStream is vulnerable to injections",
replaceWith = ReplaceWith("exportHasJsonPrefs")
)
fun exportHasSerializedPrefs(zipFile: StoredFileHelper): Boolean {
return ZipHelper.zipContainsFile(zipFile, BackupFileLocator.FILE_NAME_SERIALIZED_PREFS)
}
fun exportHasJsonPrefs(zipFile: StoredFileHelper): Boolean {
return ZipHelper.zipContainsFile(zipFile, BackupFileLocator.FILE_NAME_JSON_PREFS)
}
/**
* Remove all shared preferences from the app and load the preferences supplied to the manager.
*/
2024-03-27 11:31:16 +00:00
@Deprecated(
"Serializing preferences with Java's ObjectOutputStream is vulnerable to injections",
replaceWith = ReplaceWith("loadJsonPrefs")
)
@Throws(IOException::class, ClassNotFoundException::class)
2024-03-27 11:31:16 +00:00
fun loadSerializedPrefs(zipFile: StoredFileHelper, preferences: SharedPreferences) {
ZipHelper.extractFileFromZip(zipFile, BackupFileLocator.FILE_NAME_SERIALIZED_PREFS) {
PreferencesObjectInputStream(it).use { input ->
@Suppress("UNCHECKED_CAST")
val entries = input.readObject() as Map<String, *>
val editor = preferences.edit()
editor.clear()
2024-03-27 11:31:16 +00:00
for ((key, value) in entries) {
when (value) {
is Boolean -> editor.putBoolean(key, value)
is Float -> editor.putFloat(key, value)
is Int -> editor.putInt(key, value)
is Long -> editor.putLong(key, value)
is String -> editor.putString(key, value)
is Set<*> -> {
// There are currently only Sets with type String possible
@Suppress("UNCHECKED_CAST")
editor.putStringSet(key, value as Set<String>?)
}
}
2024-03-27 11:31:16 +00:00
}
if (!editor.commit()) {
throw IOException("Unable to commit loadSerializedPrefs")
2024-03-27 11:31:16 +00:00
}
}
}.let { fileExists ->
if (!fileExists) {
throw FileNotFoundException(BackupFileLocator.FILE_NAME_SERIALIZED_PREFS)
}
2024-03-27 11:31:16 +00:00
}
}
/**
* Remove all shared preferences from the app and load the preferences supplied to the manager.
*/
@Throws(IOException::class, JsonParserException::class)
2024-03-27 11:31:16 +00:00
fun loadJsonPrefs(zipFile: StoredFileHelper, preferences: SharedPreferences) {
ZipHelper.extractFileFromZip(zipFile, BackupFileLocator.FILE_NAME_JSON_PREFS) {
val jsonObject = JsonParser.`object`().from(it)
2024-03-27 11:31:16 +00:00
val editor = preferences.edit()
editor.clear()
for ((key, value) in jsonObject) {
when (value) {
is Boolean -> editor.putBoolean(key, value)
is Float -> editor.putFloat(key, value)
is Int -> editor.putInt(key, value)
is Long -> editor.putLong(key, value)
is String -> editor.putString(key, value)
is JsonArray -> {
editor.putStringSet(key, value.mapNotNull { e -> e as? String }.toSet())
}
}
2023-08-03 09:55:18 +00:00
}
2024-03-27 11:31:16 +00:00
if (!editor.commit()) {
throw IOException("Unable to commit loadJsonPrefs")
2024-03-27 11:31:16 +00:00
}
}.let { fileExists ->
if (!fileExists) {
throw FileNotFoundException(BackupFileLocator.FILE_NAME_JSON_PREFS)
}
}
}
}