Skip to content

Commit

Permalink
Minor changes, enable cache support again
Browse files Browse the repository at this point in the history
  • Loading branch information
rfc2822 committed Jan 19, 2025
1 parent 0f019fa commit 628643d
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 108 deletions.
62 changes: 30 additions & 32 deletions app/src/main/kotlin/at/bitfire/davdroid/network/HttpClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationService
import okhttp3.Authenticator
import okhttp3.Cache
import okhttp3.ConnectionSpec
import okhttp3.CookieJar
import okhttp3.Interceptor
Expand All @@ -26,6 +27,7 @@ import okhttp3.Protocol
import okhttp3.brotli.BrotliInterceptor
import okhttp3.internal.tls.OkHostnameVerifier
import okhttp3.logging.HttpLoggingInterceptor
import java.io.File
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
Expand All @@ -38,7 +40,7 @@ import javax.net.ssl.SSLContext

class HttpClient(
val okHttpClient: OkHttpClient,
val authorizationService: AuthorizationService? = null
private val authorizationService: AuthorizationService? = null
): AutoCloseable {

override fun close() {
Expand Down Expand Up @@ -74,9 +76,9 @@ class HttpClient(
return this
}

private var loggerLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY
fun setLoggerLevel(level: HttpLoggingInterceptor.Level): Builder {
loggerLevel = level
private var loggerInterceptorLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY
fun loggerInterceptorLevel(level: HttpLoggingInterceptor.Level): Builder {
loggerInterceptorLevel = level
return this
}

Expand All @@ -92,17 +94,17 @@ class HttpClient(
private var authorizationService: AuthorizationService? = null
private var certificateAlias: String? = null
fun authenticate(host: String?, credentials: Credentials, authStateCallback: BearerAuthInterceptor.AuthStateUpdateCallback? = null): Builder {
if (credentials.username != null && credentials.password != null) {
// basic/digest auth
val authHandler = BasicDigestAuthHandler(UrlUtils.hostToDomain(host), credentials.username, credentials.password, insecurePreemptive = true)
authenticationInterceptor = authHandler
authenticator = authHandler

} else if (credentials.authState != null) {
if (credentials.authState != null) {
// OAuth
val authService = authorizationServiceProvider.get()
authenticationInterceptor = BearerAuthInterceptor.fromAuthState(authService, credentials.authState, authStateCallback)
authorizationService = authService

} else if (credentials.username != null && credentials.password != null) {
// basic/digest auth
val authHandler = BasicDigestAuthHandler(UrlUtils.hostToDomain(host), credentials.username, credentials.password, insecurePreemptive = true)
authenticationInterceptor = authHandler
authenticator = authHandler
}

// client certificate
Expand All @@ -124,17 +126,18 @@ class HttpClient(
return this
}

private var cache: Cache? = null
@Suppress("unused")
fun withDiskCache(): Builder {
/*for (dir in arrayOf(context.externalCacheDir, context.cacheDir).filterNotNull()) {
fun withDiskCache(maxSize: Long = 10*1024*1024): Builder {
for (dir in arrayOf(context.externalCacheDir, context.cacheDir).filterNotNull()) {
if (dir.exists() && dir.canWrite()) {
val cacheDir = File(dir, "HttpClient")
cacheDir.mkdir()
logger.fine("Using disk cache: $cacheDir")
orig.cache(Cache(cacheDir, DISK_CACHE_MAX_SIZE))
cache = Cache(cacheDir, maxSize)
break
}
}*/
}
return this
}

Expand Down Expand Up @@ -171,7 +174,7 @@ class HttpClient(
.readTimeout(120, TimeUnit.SECONDS)
.pingInterval(45, TimeUnit.SECONDS) // avoid cancellation because of missing traffic; only works for HTTP/2

// don't allow redirects by default, because it would break PROPFIND handling
// don't allow redirects by default because it would break PROPFIND handling
.followRedirects(false)

// add User-Agent to every request
Expand All @@ -189,6 +192,9 @@ class HttpClient(
// offer Brotli and gzip compression (can be disabled per request with `Accept-Encoding: identity`)
.addInterceptor(BrotliInterceptor)

// add cache, if requested
.cache(cache)

// app-wide custom proxy support
buildProxy(okBuilder)

Expand All @@ -198,7 +204,7 @@ class HttpClient(
// add network logging, if requested
if (logger.isLoggable(Level.FINEST)) {
val loggingInterceptor = HttpLoggingInterceptor { message -> logger.finest(message) }
loggingInterceptor.level = loggerLevel
loggingInterceptor.level = loggerInterceptorLevel
okBuilder.addNetworkInterceptor(loggingInterceptor)
}

Expand Down Expand Up @@ -236,16 +242,16 @@ class HttpClient(
trustSystemCerts = !settingsManager.getBoolean(Settings.DISTRUST_SYSTEM_CERTIFICATES),
appInForeground = appInForeground
)
val hostnameVerifier = certManager.HostnameVerifier(OkHostnameVerifier)

val sslContext = SSLContext.getInstance("TLS")
sslContext.init(
if (keyManager != null) arrayOf(keyManager) else null,
arrayOf(certManager),
null)

okBuilder.sslSocketFactory(sslContext.socketFactory, certManager)
okBuilder.hostnameVerifier(hostnameVerifier)
/* km = */ if (keyManager != null) arrayOf(keyManager) else null,
/* tm = */ arrayOf(certManager),

Check failure

Code scanning / CodeQL

`TrustManager` that accepts all certificates High

This uses
TrustManager
, which is defined in
CustomCertManager
and trusts any certificate.
/* random = */ null
)
okBuilder
.sslSocketFactory(sslContext.socketFactory, certManager)
.hostnameVerifier(certManager.HostnameVerifier(OkHostnameVerifier))
}

private fun buildProxy(okBuilder: OkHttpClient.Builder) {
Expand Down Expand Up @@ -274,14 +280,6 @@ class HttpClient(
}
}


companion object {

/** max. size of disk cache (10 MB) */
const val DISK_CACHE_MAX_SIZE: Long = 10*1024*1024

}

}

}
112 changes: 56 additions & 56 deletions app/src/main/kotlin/at/bitfire/davdroid/push/PushRegistrationWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,54 +68,54 @@ class PushRegistrationWorker @AssistedInject constructor(
}

private suspend fun registerPushSubscription(collection: Collection, account: Account, endpoint: String) {
val httpClient = httpClientBuilder.get()
httpClientBuilder.get()
.fromAccount(account)
.inForeground(true)
.build()
runInterruptible {
httpClient.use { client ->
val httpClient = client.okHttpClient

// requested expiration time: 3 days
val requestedExpiration = Instant.now() + Duration.ofDays(3)

val serializer = XmlUtils.newSerializer()
val writer = StringWriter()
serializer.setOutput(writer)
serializer.startDocument("UTF-8", true)
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "push-register")) {
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "subscription")) {
// subscription URL
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "web-push-subscription")) {
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "push-resource")) {
text(endpoint)
.use { client ->
runInterruptible {
val httpClient = client.okHttpClient

// requested expiration time: 3 days
val requestedExpiration = Instant.now() + Duration.ofDays(3)

val serializer = XmlUtils.newSerializer()
val writer = StringWriter()
serializer.setOutput(writer)
serializer.startDocument("UTF-8", true)
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "push-register")) {
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "subscription")) {
// subscription URL
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "web-push-subscription")) {
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "push-resource")) {
text(endpoint)
}
}
}
// requested expiration
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "expires")) {
text(HttpUtils.formatDate(requestedExpiration))
}
}
// requested expiration
serializer.insertTag(Property.Name(NS_WEBDAV_PUSH, "expires")) {
text(HttpUtils.formatDate(requestedExpiration))
serializer.endDocument()

val xml = writer.toString().toRequestBody(DavResource.MIME_XML)
DavCollection(httpClient, collection.url).post(xml) { response ->
if (response.isSuccessful) {
val subscriptionUrl = response.header("Location")
val expires = response.header("Expires")?.let { expiresDate ->
HttpUtils.parseDate(expiresDate)
} ?: requestedExpiration
collectionRepository.updatePushSubscription(
id = collection.id,
subscriptionUrl = subscriptionUrl,
expires = expires?.epochSecond
)
} else
logger.warning("Couldn't register push for ${collection.url}: $response")
}
}
serializer.endDocument()

val xml = writer.toString().toRequestBody(DavResource.MIME_XML)
DavCollection(httpClient, collection.url).post(xml) { response ->
if (response.isSuccessful) {
val subscriptionUrl = response.header("Location")
val expires = response.header("Expires")?.let { expiresDate ->
HttpUtils.parseDate(expiresDate)
} ?: requestedExpiration
collectionRepository.updatePushSubscription(
id = collection.id,
subscriptionUrl = subscriptionUrl,
expires = expires?.epochSecond
)
} else
logger.warning("Couldn't register push for ${collection.url}: $response")
}
}
}
}

private suspend fun registerSyncable() {
Expand Down Expand Up @@ -149,30 +149,30 @@ class PushRegistrationWorker @AssistedInject constructor(
}

private suspend fun unregisterPushSubscription(collection: Collection, account: Account, url: HttpUrl) {
val httpClient = httpClientBuilder.get()
httpClientBuilder.get()
.fromAccount(account)
.inForeground(true)
.build()
runInterruptible {
httpClient.use { client ->
val httpClient = client.okHttpClient
.use { httpClient ->
runInterruptible {
val httpClient = httpClient.okHttpClient

try {
DavResource(httpClient, url).delete {
// deleted
try {
DavResource(httpClient, url).delete {
// deleted
}
} catch (e: DavException) {
logger.log(Level.WARNING, "Couldn't unregister push for ${collection.url}", e)
}
} catch (e: DavException) {
logger.log(Level.WARNING, "Couldn't unregister push for ${collection.url}", e)
}

// remove registration URL from DB in any case
collectionRepository.updatePushSubscription(
id = collection.id,
subscriptionUrl = null,
expires = null
)
// remove registration URL from DB in any case
collectionRepository.updatePushSubscription(
id = collection.id,
subscriptionUrl = null,
expires = null
)
}
}
}
}

private suspend fun unregisterNotSyncable() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import dagger.hilt.components.SingletonComponent
import dagger.multibindings.Multibinds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import net.fortuna.ical4j.model.Calendar
import net.fortuna.ical4j.model.Component
import net.fortuna.ical4j.model.ComponentList
Expand Down Expand Up @@ -181,12 +180,10 @@ class DavCollectionRepository @Inject constructor(
.inForeground(true)
.build()
.use { httpClient ->
withContext(Dispatchers.IO) {
runInterruptible {
DavResource(httpClient.okHttpClient, collection.url).delete {
// success, otherwise an exception would have been thrown → delete locally, too
delete(collection)
}
runInterruptible(Dispatchers.IO) {
DavResource(httpClient.okHttpClient, collection.url).delete {
// success, otherwise an exception would have been thrown → delete locally, too
delete(collection)
}
}
}
Expand Down Expand Up @@ -297,14 +294,12 @@ class DavCollectionRepository @Inject constructor(
.inForeground(true)
.build()
.use { httpClient ->
withContext(Dispatchers.IO) {
runInterruptible {
DavResource(httpClient.okHttpClient, url).mkCol(
xmlBody = xmlBody,
method = method
) {
// success, otherwise an exception would have been thrown
}
runInterruptible(Dispatchers.IO) {
DavResource(httpClient.okHttpClient, url).mkCol(
xmlBody = xmlBody,
method = method
) {
// success, otherwise an exception would have been thrown
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package at.bitfire.davdroid.sync

import android.accounts.Account
import android.accounts.AccountManager
import android.content.ContentProviderClient
import android.provider.ContactsContract
import at.bitfire.davdroid.db.Collection
Expand Down Expand Up @@ -80,11 +81,12 @@ class AddressBookSyncer @AssistedInject constructor(
collection: Collection
) {
try {
val accountSettings = accountSettingsFactory.create(account)

// handle group method change
val accountSettings = accountSettingsFactory.create(account)
val groupMethod = accountSettings.getGroupMethod().name
accountSettings.accountManager.getUserData(addressBook.addressBookAccount, PREVIOUS_GROUP_METHOD)?.let { previousGroupMethod ->

val accountManager = AccountManager.get(context)
accountManager.getUserData(addressBook.addressBookAccount, PREVIOUS_GROUP_METHOD)?.let { previousGroupMethod ->
if (previousGroupMethod != groupMethod) {
logger.info("Group method changed, deleting all local contacts/groups")

Expand All @@ -96,7 +98,7 @@ class AddressBookSyncer @AssistedInject constructor(
addressBook.syncState = null
}
}
accountSettings.accountManager.setAndVerifyUserData(addressBook.addressBookAccount, PREVIOUS_GROUP_METHOD, groupMethod)
accountManager.setAndVerifyUserData(addressBook.addressBookAccount, PREVIOUS_GROUP_METHOD, groupMethod)

val syncManager = contactsSyncManagerFactory.contactsSyncManager(account, httpClient.value, extras, authority, syncResult, provider, addressBook, collection)
syncManager.performSync()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ class DavDocumentsProvider: DocumentsProvider() {
*/
internal fun httpClient(mountId: Long, logBody: Boolean = true): HttpClient {
val builder = httpClientBuilder.get()
.setLoggerLevel(if (logBody) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.HEADERS)
.loggerInterceptorLevel(if (logBody) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.HEADERS)
.setCookieStore(
cookieStores.getOrPut(mountId) { MemoryCookieStore() }
)
Expand Down

0 comments on commit 628643d

Please sign in to comment.