CookieJar implementation for non-WebView environment
parent
f115031846
commit
1493aa39a3
@ -0,0 +1,84 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network.cookies
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import okhttp3.Cookie
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
import java.io.ObjectOutputStream
|
||||||
|
|
||||||
|
|
||||||
|
class CookieWrapper(
|
||||||
|
val cookie: Cookie,
|
||||||
|
) {
|
||||||
|
|
||||||
|
constructor(encodedString: String) : this(
|
||||||
|
ObjectInputStream(ByteArrayInputStream(Base64.decode(encodedString, Base64.NO_WRAP))).use {
|
||||||
|
val name = it.readUTF()
|
||||||
|
val value = it.readUTF()
|
||||||
|
val expiresAt = it.readLong()
|
||||||
|
val domain = it.readUTF()
|
||||||
|
val path = it.readUTF()
|
||||||
|
val secure = it.readBoolean()
|
||||||
|
val httpOnly = it.readBoolean()
|
||||||
|
val persistent = it.readBoolean()
|
||||||
|
val hostOnly = it.readBoolean()
|
||||||
|
Cookie.Builder().also { c ->
|
||||||
|
c.name(name)
|
||||||
|
c.value(value)
|
||||||
|
if (persistent) {
|
||||||
|
c.expiresAt(expiresAt)
|
||||||
|
}
|
||||||
|
if (hostOnly) {
|
||||||
|
c.hostOnlyDomain(domain)
|
||||||
|
} else {
|
||||||
|
c.domain(domain)
|
||||||
|
}
|
||||||
|
c.path(path)
|
||||||
|
if (secure) {
|
||||||
|
c.secure()
|
||||||
|
}
|
||||||
|
if (httpOnly) {
|
||||||
|
c.httpOnly()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fun encode(): String {
|
||||||
|
val output = ByteArrayOutputStream()
|
||||||
|
ObjectOutputStream(output).use {
|
||||||
|
it.writeUTF(cookie.name)
|
||||||
|
it.writeUTF(cookie.value)
|
||||||
|
it.writeLong(cookie.expiresAt)
|
||||||
|
it.writeUTF(cookie.domain)
|
||||||
|
it.writeUTF(cookie.path)
|
||||||
|
it.writeBoolean(cookie.secure)
|
||||||
|
it.writeBoolean(cookie.httpOnly)
|
||||||
|
it.writeBoolean(cookie.persistent)
|
||||||
|
it.writeBoolean(cookie.hostOnly)
|
||||||
|
}
|
||||||
|
return Base64.encodeToString(output.toByteArray(), Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isExpired() = cookie.expiresAt < System.currentTimeMillis()
|
||||||
|
|
||||||
|
fun key(): String {
|
||||||
|
return (if (cookie.secure) "https" else "http") + "://" + cookie.domain + cookie.path + "|" + cookie.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as CookieWrapper
|
||||||
|
|
||||||
|
if (cookie != other.cookie) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return cookie.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network.cookies
|
||||||
|
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import okhttp3.Cookie
|
||||||
|
import okhttp3.CookieJar
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
|
||||||
|
interface MutableCookieJar : CookieJar {
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun loadForRequest(url: HttpUrl): List<Cookie>
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)
|
||||||
|
|
||||||
|
suspend fun clear(): Boolean
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network.cookies
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.collection.ArrayMap
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.Cookie
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
|
|
||||||
|
private const val PREFS_NAME = "cookies"
|
||||||
|
|
||||||
|
class PreferencesCookieJar(
|
||||||
|
context: Context,
|
||||||
|
) : MutableCookieJar {
|
||||||
|
|
||||||
|
private val cache = ArrayMap<String, CookieWrapper>()
|
||||||
|
private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
private var isLoaded = false
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||||
|
loadPersistent()
|
||||||
|
val expired = HashSet<String>()
|
||||||
|
val result = ArrayList<Cookie>()
|
||||||
|
for ((key, cookie) in cache) {
|
||||||
|
if (cookie.isExpired()) {
|
||||||
|
expired += key
|
||||||
|
} else if (cookie.cookie.matches(url)) {
|
||||||
|
result += cookie.cookie
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expired.isNotEmpty()) {
|
||||||
|
cache.removeAll(expired)
|
||||||
|
removePersistent(expired)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||||
|
val wrapped = cookies.map { CookieWrapper(it) }
|
||||||
|
prefs.edit(commit = true) {
|
||||||
|
for (cookie in wrapped) {
|
||||||
|
val key = cookie.key()
|
||||||
|
cache[key] = cookie
|
||||||
|
if (cookie.cookie.persistent) {
|
||||||
|
putString(key, cookie.encode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear(): Boolean {
|
||||||
|
cache.clear()
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
prefs.edit(commit = true) { clear() }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun loadPersistent() {
|
||||||
|
if (!isLoaded) {
|
||||||
|
val map = prefs.all
|
||||||
|
cache.ensureCapacity(map.size)
|
||||||
|
for ((k, v) in map) {
|
||||||
|
val cookie = try {
|
||||||
|
CookieWrapper(v as String)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTraceDebug()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cache[k] = cookie
|
||||||
|
}
|
||||||
|
isLoaded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removePersistent(keys: Collection<String>) {
|
||||||
|
prefs.edit(commit = true) {
|
||||||
|
for (key in keys) {
|
||||||
|
remove(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue