Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ class PreferencesManager @Inject constructor(
?: io.aatricks.novelscraper.data.model.ReaderTheme.DARK.name
set(value) = prefs.edit().putString(KEY_READER_THEME, value).apply()

var ignoreSslErrors: Boolean
get() = prefs.getBoolean(KEY_IGNORE_SSL_ERRORS, false)
set(value) = prefs.edit().putBoolean(KEY_IGNORE_SSL_ERRORS, value).apply()

// Clear all preferences
fun clearAll() {
prefs.edit().clear().apply()
Expand Down Expand Up @@ -158,6 +154,5 @@ class PreferencesManager @Inject constructor(
private const val KEY_MARGINS = "reader_margins"
private const val KEY_PARAGRAPH_SPACING = "reader_paragraph_spacing"
private const val KEY_READER_THEME = "reader_theme"
private const val KEY_IGNORE_SSL_ERRORS = "ignore_ssl_errors"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ import org.jsoup.Connection
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

abstract class BaseJsoupSource(
protected open val preferencesManager: PreferencesManager? = null,
Expand Down Expand Up @@ -49,21 +43,6 @@ abstract class BaseJsoupSource(
.timeout(timeout.toInt())
.followRedirects(true)

if (preferencesManager?.ignoreSslErrors == true) {
val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
connection.sslSocketFactory(sslContext.socketFactory)
// Note: setDefaultHostnameVerifier is global and might affect other parts of the app
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier { _, _ -> true }
}

return connection.get()
}

Expand All @@ -75,20 +54,6 @@ abstract class BaseJsoupSource(
.timeout(timeout.toInt())
.followRedirects(true)

if (preferencesManager?.ignoreSslErrors == true) {
val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
connection.sslSocketFactory(sslContext.socketFactory)
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier { _, _ -> true }
}

return connection
}

Expand Down
62 changes: 1 addition & 61 deletions app/src/main/java/io/aatricks/novelscraper/di/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,9 @@ import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import io.aatricks.novelscraper.data.local.PreferencesManager
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.inject.Singleton
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager

@Module
@InstallIn(SingletonComponent::class)
Expand All @@ -31,7 +25,7 @@ object NetworkModule {

@Provides
@Singleton
fun provideOkHttpClient(preferencesManager: PreferencesManager): OkHttpClient {
fun provideOkHttpClient(): OkHttpClient {
val builder = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS // Reduced verbosity for performance
Expand All @@ -41,60 +35,6 @@ object NetworkModule {
.followSslRedirects(true)
.followRedirects(true)

try {
val trustManagerFactory = javax.net.ssl.TrustManagerFactory.getInstance(
javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm()
)
trustManagerFactory.init(null as java.security.KeyStore?)
val defaultTrustManager = trustManagerFactory.trustManagers.first { tm -> tm is X509TrustManager } as X509TrustManager

val dynamicTrustManager = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
if (!preferencesManager.ignoreSslErrors) {
defaultTrustManager.checkClientTrusted(chain, authType)
}
}

override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
if (preferencesManager.ignoreSslErrors) return

try {
defaultTrustManager.checkServerTrusted(chain, authType)
} catch (e: Exception) {
// Double check in case of race condition or update
if (!preferencesManager.ignoreSslErrors) throw e
}
}

override fun getAcceptedIssuers(): Array<X509Certificate> = defaultTrustManager.getAcceptedIssuers()
}

val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(dynamicTrustManager), SecureRandom())

builder.sslSocketFactory(sslContext.socketFactory, dynamicTrustManager)

builder.hostnameVerifier { hostname, session ->
if (preferencesManager.ignoreSslErrors) true
else okhttp3.internal.tls.OkHostnameVerifier.verify(hostname, session)
}
} catch (e: Exception) {
// Fallback to a basic trust-all if something fails during setup and user wants to ignore errors
if (preferencesManager.ignoreSslErrors) {
try {
val trustAllCerts = object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(trustAllCerts), SecureRandom())
builder.sslSocketFactory(sslContext.socketFactory, trustAllCerts)
builder.hostnameVerifier { _, _ -> true }
} catch (inner: Exception) {}
}
}

return builder.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ fun LibraryDrawerContent(

var urlInput by remember { mutableStateOf("") }
var isAddSectionVisible by remember { mutableStateOf(false) }
var isSettingsVisible by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()

LaunchedEffect(Unit) {
Expand All @@ -68,18 +67,9 @@ fun LibraryDrawerContent(
onExploreClick = {
onCloseDrawer()
navController.navigate(ExploreRoute)
},
onSettingsClick = { isSettingsVisible = true }
}
)

if (isSettingsVisible) {
SettingsDialog(
ignoreSslErrors = libraryUiState.ignoreSslErrors,
onIgnoreSslErrorsChange = { libraryViewModel.ignoreSslErrors = it },
onDismiss = { isSettingsVisible = false }
)
}

androidx.compose.animation.AnimatedVisibility(visible = isAddSectionVisible) {
AddNovelSection(
urlInput = urlInput,
Expand Down Expand Up @@ -130,8 +120,7 @@ fun LibraryDrawerContent(
private fun LibraryHeader(
isAddVisible: Boolean,
onToggleAdd: () -> Unit,
onExploreClick: () -> Unit,
onSettingsClick: () -> Unit
onExploreClick: () -> Unit
): Unit {
Row(
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),
Expand All @@ -145,13 +134,6 @@ private fun LibraryHeader(
color = MaterialTheme.colorScheme.onSurface
)
Row {
IconButton(onClick = onSettingsClick) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = "Settings",
tint = MaterialTheme.colorScheme.onSurface
)
}
IconButton(onClick = onExploreClick) {
Icon(
imageVector = Icons.Default.Image,
Expand Down Expand Up @@ -638,45 +620,6 @@ private fun NovelChapterList(
}
}

@Composable
private fun SettingsDialog(
ignoreSslErrors: Boolean,
onIgnoreSslErrorsChange: (Boolean) -> Unit,
onDismiss: () -> Unit
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text("App Settings") },
text = {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text("Ignore SSL Errors", style = MaterialTheme.typography.bodyLarge)
Text(
"Enable if you encounter certificate errors on public Wi-Fi. USE WITH CAUTION.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Switch(
checked = ignoreSslErrors,
onCheckedChange = onIgnoreSslErrorsChange
)
}
}
},
confirmButton = {
TextButton(onClick = onDismiss) {
Text("Close")
}
}
)
}

@Composable
private fun EmptyLibraryState() {
Box(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,7 @@ fun ReaderScreen(
onRetry = {
showCloudflareWebView = false
readerViewModel.retryLoad()
},
ignoreSslErrors = libraryViewModel.ignoreSslErrors
}
)
}

Expand Down Expand Up @@ -231,8 +230,7 @@ fun ReaderScreen(
private fun CloudflareDialog(
url: String,
onDismiss: () -> Unit,
onRetry: () -> Unit,
ignoreSslErrors: Boolean = false
onRetry: () -> Unit
): Unit {
val context = LocalContext.current
var webViewError by remember { mutableStateOf<String?>(null) }
Expand Down Expand Up @@ -314,15 +312,6 @@ private fun CloudflareDialog(
userAgentString = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView?,
handler: android.webkit.SslErrorHandler?,
error: android.net.http.SslError?
) {
if (ignoreSslErrors) handler?.proceed()
else super.onReceivedSslError(view, handler, error)
}

override fun onReceivedError(
view: WebView?,
request: android.webkit.WebResourceRequest?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package io.aatricks.novelscraper.ui.viewmodel

import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import io.aatricks.novelscraper.data.local.PreferencesManager
import io.aatricks.novelscraper.data.model.ContentType
import io.aatricks.novelscraper.data.model.LibraryItem
import io.aatricks.novelscraper.data.model.SortMode
Expand All @@ -19,19 +18,11 @@ import android.util.Log
class LibraryViewModel @Inject constructor(
val repository: LibraryRepository,
private val contentRepository: ContentRepository,
private val exploreRepository: ExploreRepository,
private val preferencesManager: PreferencesManager
private val exploreRepository: ExploreRepository
) : BaseViewModel<LibraryViewModel.LibraryUiState>(LibraryUiState()) {

private val TAG = "LibraryViewModel"

var ignoreSslErrors: Boolean
get() = preferencesManager.ignoreSslErrors
set(value) {
preferencesManager.ignoreSslErrors = value
updateState { it.copy(ignoreSslErrors = value) }
}

private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()

Expand All @@ -57,8 +48,7 @@ class LibraryViewModel @Inject constructor(
val selectedIds: Set<String> = emptySet(),
val selectedCount: Int = 0,
val isEmpty: Boolean = true,
val currentlyReading: LibraryItem? = null,
val ignoreSslErrors: Boolean = false
val currentlyReading: LibraryItem? = null
)

private fun observeLibraryChanges(): Unit {
Expand Down Expand Up @@ -91,8 +81,7 @@ class LibraryViewModel @Inject constructor(
selectedIds = selectedIds,
selectedCount = selectedIds.size,
isEmpty = items.isEmpty(),
currentlyReading = items.find { it.isCurrentlyReading },
ignoreSslErrors = preferencesManager.ignoreSslErrors
currentlyReading = items.find { it.isCurrentlyReading }
)
}.collect { newState ->
updateState { newState }
Expand Down
Loading