22 * Nextcloud Android Library
33 *
44 * SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
5+ * SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
56 * SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
67 * SPDX-License-Identifier: MIT
78 */
89package com.nextcloud.common
910
11+ import android.os.Build
1012import androidx.annotation.VisibleForTesting
1113import com.nextcloud.android.lib.core.Clock
1214import com.nextcloud.android.lib.core.ClockImpl
@@ -15,6 +17,7 @@ import java.net.Inet4Address
1517import java.net.Inet6Address
1618import java.net.InetAddress
1719import java.net.UnknownHostException
20+ import java.util.concurrent.ConcurrentHashMap
1821
1922/* *
2023 * DNS Cache which prefers IPv6 unless otherwise specified
@@ -24,12 +27,15 @@ object DNSCache {
2427
2528 // 30 seconds is the Java default. Let's keep it.
2629 @VisibleForTesting
30+ @Volatile
2731 var ttlMillis: Long = DEFAULT_TTL
2832
2933 @VisibleForTesting
34+ @Volatile
3035 var clock: Clock = ClockImpl ()
3136
3237 @VisibleForTesting
38+ @Volatile
3339 var dns: Dns = Dns .SYSTEM
3440
3541 data class DNSInfo (
@@ -40,49 +46,47 @@ object DNSCache {
4046 fun isExpired (): Boolean = clock.currentTimeMillis - timestamp > ttlMillis
4147 }
4248
43- private val cache: MutableMap <String , DNSInfo > = HashMap ()
49+ private val cache: ConcurrentHashMap <String , DNSInfo > = ConcurrentHashMap ()
4450
4551 @Throws(UnknownHostException ::class )
46- @Synchronized
4752 @JvmStatic
4853 fun lookup (hostname : String ): List <InetAddress > {
4954 val entry = cache[hostname]
5055 if (entry?.addresses?.isNotEmpty() == true && ! entry.isExpired()) {
5156 return entry.addresses
5257 }
53- val preferIPV4 =
54- when (entry) {
55- null -> false
56- else -> entry.preferIPV4
57- }
5858
5959 val addresses = dns.lookup(hostname).toMutableList()
6060 if (addresses.isEmpty()) {
6161 throw UnknownHostException (" Unknown host $hostname " )
6262 }
63- val sortedAddresses = sortAddresses(addresses, preferIPV4)
6463
65- val newEntry = DNSInfo (sortedAddresses, preferIPV4)
66- cache[hostname] = newEntry
64+ val preferIPV4 = entry?.preferIPV4 ? : false
65+ val sortedAddresses = sortAddresses(addresses, preferIPV4)
66+ cache[hostname] = DNSInfo (sortedAddresses, preferIPV4)
6767
6868 return sortedAddresses
6969 }
7070
7171 /* *
7272 * Set IP version preference for a hostname, and re-sort addresses if needed
7373 */
74- @Synchronized
7574 @JvmStatic
7675 fun setIPVersionPreference (
7776 hostname : String ,
7877 preferIPV4 : Boolean
7978 ) {
80- val entry = cache[hostname]
81- if (entry != null ) {
82- val addresses = sortAddresses(entry.addresses, preferIPV4)
83- cache[hostname] = DNSInfo (addresses, preferIPV4)
79+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N ) {
80+ cache.compute(hostname) { _, old ->
81+ val addresses =
82+ old?.addresses?.let {
83+ sortAddresses(it, preferIPV4)
84+ } ? : emptyList()
85+ DNSInfo (addresses, preferIPV4)
86+ }
8487 } else {
85- cache[hostname] = DNSInfo (emptyList(), preferIPV4)
88+ val addresses = cache[hostname]?.addresses?.let { sortAddresses(it, preferIPV4) } ? : emptyList()
89+ cache[hostname] = DNSInfo (addresses, preferIPV4)
8690 }
8791 }
8892
@@ -92,7 +96,6 @@ object DNSCache {
9296 * - The first address is an IPv6 address
9397 * - There are IPv4 addresses available too
9498 */
95- @Synchronized
9699 @JvmStatic
97100 fun isIPV6First (hostname : String ): Boolean {
98101 val firstV6 = cache[hostname]?.addresses?.firstOrNull() is Inet6Address
@@ -103,7 +106,6 @@ object DNSCache {
103106 /* *
104107 * Clears the cache
105108 */
106- @Synchronized
107109 @JvmStatic
108110 fun clear () {
109111 cache.clear()
0 commit comments