Fix potential crashes with icon downloader and backuper
This commit is contained in:
parent
fb4b20e6b4
commit
1fa72d9bfd
6 changed files with 34 additions and 15 deletions
|
|
@ -175,7 +175,7 @@ class Backuper(val context: Context) {
|
|||
} else {
|
||||
null
|
||||
}
|
||||
val icon = if (n.icon != null) {
|
||||
val icon = if (n.icon != null && !n.icon.url.isNullOrEmpty()) {
|
||||
io.heckel.ntfy.db.Icon(
|
||||
url = n.icon.url,
|
||||
contentUri = n.icon.contentUri,
|
||||
|
|
@ -331,7 +331,7 @@ class Backuper(val context: Context) {
|
|||
} else {
|
||||
null
|
||||
}
|
||||
val icon = if (n.icon != null) {
|
||||
val icon = if (n.icon != null && n.icon.hasValidUrl()) {
|
||||
Icon(
|
||||
url = n.icon.url,
|
||||
contentUri = n.icon.contentUri,
|
||||
|
|
@ -479,7 +479,7 @@ data class Attachment(
|
|||
)
|
||||
|
||||
data class Icon(
|
||||
val url: String, // URL (mandatory, see ntfy server)
|
||||
val url: String?, // URL (nullable to handle corrupt backup files)
|
||||
val contentUri: String?, // After it's downloaded, the content:// location
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -137,11 +137,13 @@ const val ATTACHMENT_PROGRESS_DONE = 100
|
|||
|
||||
@Entity
|
||||
data class Icon(
|
||||
@ColumnInfo(name = "url") val url: String, // URL (mandatory, see ntfy server)
|
||||
@ColumnInfo(name = "url") val url: String?, // URL (nullable to handle corrupt data from backup restore)
|
||||
@ColumnInfo(name = "contentUri") val contentUri: String?, // After it's downloaded, the content:// location
|
||||
) {
|
||||
@Ignore constructor(url:String) :
|
||||
@Ignore constructor(url: String) :
|
||||
this(url, null)
|
||||
|
||||
fun hasValidUrl(): Boolean = !url.isNullOrEmpty()
|
||||
}
|
||||
|
||||
@Entity
|
||||
|
|
@ -222,7 +224,7 @@ data class LogEntry(
|
|||
}
|
||||
|
||||
@androidx.room.Database(
|
||||
version = 16,
|
||||
version = 17,
|
||||
entities = [
|
||||
Subscription::class,
|
||||
Notification::class,
|
||||
|
|
@ -266,6 +268,7 @@ abstract class Database : RoomDatabase() {
|
|||
.addMigrations(MIGRATION_13_14)
|
||||
.addMigrations(MIGRATION_14_15)
|
||||
.addMigrations(MIGRATION_15_16)
|
||||
.addMigrations(MIGRATION_16_17)
|
||||
.fallbackToDestructiveMigration(true)
|
||||
.build()
|
||||
this.instance = instance
|
||||
|
|
@ -390,6 +393,15 @@ abstract class Database : RoomDatabase() {
|
|||
db.execSQL("CREATE TABLE ClientCertificate (baseUrl TEXT NOT NULL, p12Base64 TEXT NOT NULL, password TEXT NOT NULL, PRIMARY KEY(baseUrl))")
|
||||
}
|
||||
}
|
||||
|
||||
// Fix corrupt icon data where icon_url is NULL but icon_contentUri is not NULL
|
||||
// This caused IllegalStateException in CursorWindow.nativeGetString when Room tried to
|
||||
// construct an Icon object with a null URL (Icon.url is non-nullable in Kotlin)
|
||||
private val MIGRATION_16_17 = object : Migration(16, 17) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("UPDATE Notification SET icon_contentUri = NULL WHERE icon_url IS NULL AND icon_contentUri IS NOT NULL")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
|
|||
notification = repository.getNotification(notificationId) ?: return Result.failure()
|
||||
subscription = repository.getSubscription(notification.subscriptionId) ?: return Result.failure()
|
||||
icon = notification.icon ?: return Result.failure()
|
||||
if (!icon.hasValidUrl()) {
|
||||
Log.w(TAG, "Icon has no valid URL, skipping download")
|
||||
return Result.failure()
|
||||
}
|
||||
try {
|
||||
val iconFile = createIconFile(icon)
|
||||
val yesterdayTimestamp = Date().time - MAX_CACHE_MILLIS
|
||||
|
|
@ -58,12 +62,13 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
|
|||
}
|
||||
|
||||
private suspend fun downloadIcon(iconFile: File) {
|
||||
Log.d(TAG, "Downloading icon from ${icon.url}")
|
||||
val iconUrl = icon.url!! // Validated in doWork()
|
||||
Log.d(TAG, "Downloading icon from $iconUrl")
|
||||
try {
|
||||
val user = repository.getUser(extractBaseUrl(icon.url))
|
||||
val customHeaders = repository.getCustomHeaders(extractBaseUrl(icon.url))
|
||||
val request = HttpUtil.requestBuilder(icon.url, user, customHeaders).build()
|
||||
val client = HttpUtil.defaultClient(context, extractBaseUrl(icon.url))
|
||||
val user = repository.getUser(extractBaseUrl(iconUrl))
|
||||
val customHeaders = repository.getCustomHeaders(extractBaseUrl(iconUrl))
|
||||
val request = HttpUtil.requestBuilder(iconUrl, user, customHeaders).build()
|
||||
val client = HttpUtil.defaultClient(context, extractBaseUrl(iconUrl))
|
||||
client.newCall(request).execute().use { response ->
|
||||
Log.d(TAG, "Headers received: $response, Content-Length: ${response.headers["Content-Length"]}")
|
||||
if (!response.isSuccessful) {
|
||||
|
|
@ -142,7 +147,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
|
|||
if (!iconDir.exists() && !iconDir.mkdirs()) {
|
||||
throw Exception("Cannot create cache directory for icons: $iconDir")
|
||||
}
|
||||
val hash = icon.url.sha256()
|
||||
val hash = icon.url!!.sha256() // URL validated in doWork()
|
||||
return File(iconDir, hash)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
|
|||
}
|
||||
}
|
||||
private fun shouldDownloadIcon(notification: Notification): Boolean {
|
||||
return notification.icon != null
|
||||
return notification.icon?.hasValidUrl() == true
|
||||
}
|
||||
|
||||
private fun shouldNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean {
|
||||
|
|
|
|||
|
|
@ -115,11 +115,12 @@ class SubscriberService : Service() {
|
|||
notificationManager = createNotificationChannel()
|
||||
serviceNotification = createNotification(title, text)
|
||||
|
||||
val notification = serviceNotification ?: return
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(NOTIFICATION_SERVICE_ID, serviceNotification!!, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
startForeground(NOTIFICATION_SERVICE_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
} else {
|
||||
startForeground(NOTIFICATION_SERVICE_ID, serviceNotification)
|
||||
startForeground(NOTIFICATION_SERVICE_ID, notification)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// On Android 12+, starting a foreground service from the background is restricted.
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ Maintenance + bug fixes:
|
|||
* Use server-specific user for attachment downloads (#1529, thanks to @ManInDark for reporting)
|
||||
* Fix crash in sharing dialog (thanks to @rogeliodh)
|
||||
* Fix crash when exiting multi-delete in detail view
|
||||
* Fix potential crashes with icon downloader and backuper
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue