diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt index 195bb9dab..815290238 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt @@ -16,6 +16,7 @@ import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.util.ext.configureForParser import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug +import org.koitharu.kotatsu.core.util.ext.sanitizeHeaderValue import org.koitharu.kotatsu.core.util.ext.toList import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.config.MangaSourceConfig @@ -41,7 +42,7 @@ class MangaLoaderContextImpl @Inject constructor( private val userAgentLazy = SuspendLazy { withContext(Dispatchers.Main) { obtainWebView().settings.userAgentString - } + }.sanitizeHeaderValue() } @SuppressLint("SetJavaScriptEnabled") diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt index 482de71a9..f6c2f4d28 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt @@ -3,9 +3,11 @@ package org.koitharu.kotatsu.core.prefs import android.content.Context import android.content.SharedPreferences.OnSharedPreferenceChangeListener import androidx.core.content.edit +import okhttp3.internal.isSensitiveHeader import org.koitharu.kotatsu.core.util.ext.getEnumValue import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty import org.koitharu.kotatsu.core.util.ext.putEnumValue +import org.koitharu.kotatsu.core.util.ext.sanitizeHeaderValue import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.model.MangaSource @@ -25,7 +27,10 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig @Suppress("UNCHECKED_CAST") override fun get(key: ConfigKey): T { return when (key) { - is ConfigKey.UserAgent -> prefs.getString(key.key, key.defaultValue).ifNullOrEmpty { key.defaultValue } + is ConfigKey.UserAgent -> prefs.getString(key.key, key.defaultValue) + .ifNullOrEmpty { key.defaultValue } + .sanitizeHeaderValue() + is ConfigKey.Domain -> prefs.getString(key.key, key.defaultValue).ifNullOrEmpty { key.defaultValue } is ConfigKey.ShowSuspiciousContent -> prefs.getBoolean(key.key, key.defaultValue) is ConfigKey.SplitByTranslations -> prefs.getBoolean(key.key, key.defaultValue) @@ -36,7 +41,7 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig when (key) { is ConfigKey.Domain -> putString(key.key, value as String?) is ConfigKey.ShowSuspiciousContent -> putBoolean(key.key, value as Boolean) - is ConfigKey.UserAgent -> putString(key.key, value as String?) + is ConfigKey.UserAgent -> putString(key.key, (value as String?)?.sanitizeHeaderValue()) is ConfigKey.SplitByTranslations -> putBoolean(key.key, value as Boolean) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Http.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Http.kt index 988e06920..a95e7508b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Http.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Http.kt @@ -1,11 +1,13 @@ package org.koitharu.kotatsu.core.util.ext import okhttp3.Cookie +import okhttp3.Headers import okhttp3.HttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import okhttp3.internal.closeQuietly +import okhttp3.internal.isSensitiveHeader import okio.IOException import org.json.JSONObject import org.jsoup.HttpStatusException @@ -59,3 +61,16 @@ fun Cookie.newBuilder(): Cookie.Builder = Cookie.Builder().also { c -> c.httpOnly() } } + +fun String.sanitizeHeaderValue(): String { + return if (all(Char::isValidForHeaderValue)) { + this // fast path + } else { + filter(Char::isValidForHeaderValue) + } +} + +private fun Char.isValidForHeaderValue(): Boolean { + // from okhttp3.Headers$Companion.checkValue + return this == '\t' || this in '\u0020'..'\u007e' +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/MangaDirectoriesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/MangaDirectoriesActivity.kt index d4f3283f9..7e2d35eec 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/MangaDirectoriesActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/storage/directories/MangaDirectoriesActivity.kt @@ -13,13 +13,16 @@ import androidx.core.graphics.Insets import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding +import com.google.android.material.snackbar.Snackbar import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import dagger.hilt.android.AndroidEntryPoint +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent +import org.koitharu.kotatsu.core.util.ext.tryLaunch import org.koitharu.kotatsu.databinding.ActivityMangaDirectoriesBinding import org.koitharu.kotatsu.settings.storage.DirectoryDiffCallback import org.koitharu.kotatsu.settings.storage.DirectoryModel @@ -42,7 +45,11 @@ class MangaDirectoriesActivity : BaseActivity() ) { if (it) { viewModel.updateList() - pickFileTreeLauncher.launch(null) + if (!pickFileTreeLauncher.tryLaunch(null)) { + Snackbar.make( + viewBinding.recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT, + ).show() + } } } @@ -68,7 +75,11 @@ class MangaDirectoriesActivity : BaseActivity() } override fun onClick(v: View?) { - permissionRequestLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) + if (!permissionRequestLauncher.tryLaunch(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + Snackbar.make( + viewBinding.recyclerView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT, + ).show() + } } override fun onWindowInsetsChanged(insets: Insets) {