Implemented local cover encryption (#881)
* Implemented local cover encryption and made coil capable of reading encrypted cover archives * add check that the file is not an image before determining that it is a zip file
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
package eu.kanade.tachiyomi.core.security
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.core.R
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.preference.getEnum
|
||||
|
||||
class SecurityPreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
private val context: Context,
|
||||
) {
|
||||
|
||||
fun useAuthenticator() = preferenceStore.getBoolean("use_biometric_lock", false)
|
||||
@@ -23,7 +21,7 @@ class SecurityPreferences(
|
||||
|
||||
fun authenticatorDays() = this.preferenceStore.getInt("biometric_days", 0x7F)
|
||||
|
||||
fun encryptDatabase() = this.preferenceStore.getBoolean("encrypt_database", !context.getDatabasePath("tachiyomi.db").exists())
|
||||
fun encryptDatabase() = this.preferenceStore.getBoolean("encrypt_database", false)
|
||||
|
||||
fun sqlPassword() = this.preferenceStore.getString("sql_password", "")
|
||||
|
||||
@@ -32,9 +30,6 @@ class SecurityPreferences(
|
||||
fun encryptionType() = this.preferenceStore.getEnum("encryption_type", EncryptionType.AES_256)
|
||||
|
||||
fun cbzPassword() = this.preferenceStore.getString("cbz_password", "")
|
||||
|
||||
fun localCoverLocation() = this.preferenceStore.getEnum("local_cover_location", CoverCacheLocation.IN_MANGA_DIRECTORY)
|
||||
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
@@ -56,11 +51,5 @@ class SecurityPreferences(
|
||||
AES_128(R.string.aes_128),
|
||||
ZIP_STANDARD(R.string.standard_zip_encryption),
|
||||
}
|
||||
enum class CoverCacheLocation(val titleResId: Int) {
|
||||
IN_MANGA_DIRECTORY(R.string.save_in_manga_directory),
|
||||
INTERNAL(R.string.save_internally),
|
||||
NEVER(R.string.save_never),
|
||||
}
|
||||
|
||||
// SY <--
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package eu.kanade.tachiyomi.util.storage
|
||||
|
||||
import android.content.Context
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import eu.kanade.tachiyomi.core.R
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -20,7 +18,7 @@ import tachiyomi.core.util.system.logcat
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Cipher
|
||||
@@ -36,6 +34,7 @@ import javax.crypto.spec.IvParameterSpec
|
||||
*/
|
||||
object CbzCrypto {
|
||||
const val DATABASE_NAME = "tachiyomiEncrypted.db"
|
||||
const val DEFAULT_COVER_NAME = "cover.jpg"
|
||||
private val securityPreferences: SecurityPreferences by injectLazy()
|
||||
private val keyStore = KeyStore.getInstance(KEYSTORE).apply {
|
||||
load(null)
|
||||
@@ -210,23 +209,15 @@ object CbzCrypto {
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteLocalCoverCache(context: Context) {
|
||||
if (context.getExternalFilesDir(LOCAL_CACHE_DIR)?.exists() == true) {
|
||||
context.getExternalFilesDir(LOCAL_CACHE_DIR)?.deleteRecursively()
|
||||
fun detectCoverImageArchive(stream: InputStream): Boolean {
|
||||
val bytes = ByteArray(128)
|
||||
if (stream.markSupported()) {
|
||||
stream.mark(bytes.size)
|
||||
stream.read(bytes, 0, bytes.size).also { stream.reset() }
|
||||
} else {
|
||||
stream.read(bytes, 0, bytes.size)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteLocalCoverSystemFiles(context: Context) {
|
||||
val baseFolderLocation = "${context.getString(R.string.app_name)}${File.separator}local"
|
||||
|
||||
DiskUtil.getExternalStorages(context)
|
||||
.map { File(it.absolutePath, baseFolderLocation) }
|
||||
.asSequence()
|
||||
.flatMap { it.listFiles().orEmpty().toList() }
|
||||
.filter { it.isDirectory }
|
||||
.flatMap { it.listFiles().orEmpty().toList() }
|
||||
.filter { it.name == ".cacheCoverInternal" || it.name == ".nocover" }
|
||||
.forEach { it.delete() }
|
||||
return String(bytes).contains(DEFAULT_COVER_NAME, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +233,4 @@ private const val CRYPTO_SETTINGS = "$ALGORITHM/$BLOCK_MODE/$PADDING"
|
||||
private const val KEYSTORE = "AndroidKeyStore"
|
||||
private const val ALIAS_CBZ = "cbzPw"
|
||||
private const val ALIAS_SQL = "sqlPw"
|
||||
|
||||
private const val LOCAL_CACHE_DIR = "covers/local"
|
||||
|
||||
// SY <--
|
||||
|
||||
@@ -42,6 +42,10 @@ import kotlin.math.min
|
||||
object ImageUtil {
|
||||
|
||||
fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean {
|
||||
// SY -->
|
||||
if (File(name).extension.equals("cbi", ignoreCase = true)) return true
|
||||
// SY <--
|
||||
|
||||
val contentType = try {
|
||||
URLConnection.guessContentTypeFromName(name)
|
||||
} catch (e: Exception) {
|
||||
|
||||
Reference in New Issue
Block a user