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 @@ -60,8 +60,6 @@ abstract class BaseJsoupSource(
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 @@ -86,7 +84,6 @@ abstract class BaseJsoupSource(
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
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,13 @@ class NovelFireSource @Inject constructor(
val url = "$baseUrl/ajax/searchLive?inputContent=$encodedQuery"

runCatching {
val response = connect(url)
.ignoreContentType(true)
.execute()
.body()
val request = okhttp3.Request.Builder()
.url(url)
.header("User-Agent", userAgent)
.header("Referer", baseUrl)
.build()

val response = okHttpClient.newCall(request).execute().use { it.body?.string() ?: "" }

val json = JSONObject(response)
val data = json.getJSONArray("data")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.aatricks.novelscraper.data.repository.source

import io.aatricks.novelscraper.data.local.PreferencesManager
import io.aatricks.novelscraper.data.model.ExploreItem
import okhttp3.OkHttpClient
import org.junit.Assert.assertFalse
import org.junit.Assert.fail
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLSession

class BaseJsoupSourceSecurityTest {

class TestSource(
preferencesManager: PreferencesManager,
okHttpClient: OkHttpClient?
) : BaseJsoupSource(preferencesManager, okHttpClient) {
override val name = "Test"
override val baseUrl = "https://example.com"
override suspend fun getPopularNovels(page: Int, tags: List<String>) = emptyList<ExploreItem>()
override suspend fun searchNovels(query: String, page: Int) = emptyList<ExploreItem>()
override suspend fun getNovelDetails(url: String) = ExploreItem(title = "", url = "", source = "Test")

fun testConnect(url: String) = connect(url)
// Helper to access protected getDocument
fun testGetDocument(url: String) = getDocument(url)
}

@Test
fun `connect does NOT set global hostname verifier when ignoreSslErrors is true`() {
val originalVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
try {
val prefs = mock(PreferencesManager::class.java)
`when`(prefs.ignoreSslErrors).thenReturn(true)

val source = TestSource(prefs, null)

// Trigger the code path
source.testConnect("https://example.com")

val currentVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
val mockSession = mock(SSLSession::class.java)

// A secure verifier should reject this
val accepted = currentVerifier.verify("evil.com", mockSession)

assertFalse("Global HostnameVerifier was modified to accept invalid hostnames!", accepted)

} finally {
HttpsURLConnection.setDefaultHostnameVerifier(originalVerifier)
}
}
}