Use system CookieManager as CookieJar
This commit is contained in:
@@ -1,23 +1,19 @@
|
|||||||
package org.koitharu.kotatsu.browser.cloudflare
|
package org.koitharu.kotatsu.browser.cloudflare
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.webkit.CookieManager
|
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import okhttp3.CookieJar
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import org.koitharu.kotatsu.core.network.AndroidCookieJar
|
||||||
|
import org.koitharu.kotatsu.core.network.WebViewClientCompat
|
||||||
|
|
||||||
class CloudFlareClient(
|
class CloudFlareClient(
|
||||||
private val cookieJar: CookieJar,
|
private val cookieJar: AndroidCookieJar,
|
||||||
private val callback: CloudFlareCallback,
|
private val callback: CloudFlareCallback,
|
||||||
private val targetUrl: String
|
private val targetUrl: String
|
||||||
) : WebViewClient() {
|
) : WebViewClientCompat() {
|
||||||
|
|
||||||
private val cookieManager = CookieManager.getInstance()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
cookieManager.removeAllCookies(null)
|
cookieJar.remove(targetUrl, CF_UID, CF_CLEARANCE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
|
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
|
||||||
@@ -36,16 +32,11 @@ class CloudFlareClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkClearance() {
|
private fun checkClearance() {
|
||||||
val httpUrl = targetUrl.toHttpUrl()
|
val cookies = cookieJar.loadForRequest(targetUrl.toHttpUrl())
|
||||||
val rawCookie = cookieManager.getCookie(targetUrl) ?: return
|
if (cookies.any { it.name == CF_CLEARANCE }) {
|
||||||
val cookies = rawCookie.split(';').mapNotNull {
|
callback.onCheckPassed()
|
||||||
Cookie.parse(httpUrl, it)
|
|
||||||
}
|
}
|
||||||
if (cookies.none { it.name == CF_CLEARANCE }) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cookieJar.saveFromResponse(httpUrl, cookies)
|
|
||||||
callback.onCheckPassed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network
|
||||||
|
|
||||||
|
import android.webkit.CookieManager
|
||||||
|
import okhttp3.Cookie
|
||||||
|
import okhttp3.CookieJar
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
class AndroidCookieJar : CookieJar {
|
||||||
|
|
||||||
|
private val cookieManager = CookieManager.getInstance()
|
||||||
|
|
||||||
|
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||||
|
val rawCookie = cookieManager.getCookie(url.toString()) ?: return emptyList()
|
||||||
|
return rawCookie.split(';').mapNotNull {
|
||||||
|
Cookie.parse(url, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||||
|
if (cookies.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val urlString = url.toString()
|
||||||
|
for (cookie in cookies) {
|
||||||
|
cookieManager.setCookie(urlString, cookie.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun remove(url: String, vararg names: String) {
|
||||||
|
val cookies = cookieManager.getCookie(url) ?: return
|
||||||
|
val newCookies = cookies.split(";")
|
||||||
|
.filterNot { cookie ->
|
||||||
|
names.any { cookie.startsWith("$it=") }
|
||||||
|
}.joinToString(";")
|
||||||
|
cookieManager.setCookie(url, newCookies)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAsync() {
|
||||||
|
cookieManager.removeAllCookies(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun clear() = suspendCoroutine<Boolean> { continuation ->
|
||||||
|
cookieManager.removeAllCookies(continuation::resume)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,21 +6,12 @@ import org.koin.android.ext.koin.androidContext
|
|||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.koitharu.kotatsu.core.network.cookies.ClearableCookieJar
|
|
||||||
import org.koitharu.kotatsu.core.network.cookies.PersistentCookieJar
|
|
||||||
import org.koitharu.kotatsu.core.network.cookies.cache.SetCookieCache
|
|
||||||
import org.koitharu.kotatsu.core.network.cookies.persistence.SharedPrefsCookiePersistor
|
|
||||||
import org.koitharu.kotatsu.utils.CacheUtils
|
import org.koitharu.kotatsu.utils.CacheUtils
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
val networkModule
|
val networkModule
|
||||||
get() = module {
|
get() = module {
|
||||||
single<CookieJar> {
|
single { AndroidCookieJar() } bind CookieJar::class
|
||||||
PersistentCookieJar(
|
|
||||||
SetCookieCache(),
|
|
||||||
SharedPrefsCookiePersistor(androidContext())
|
|
||||||
)
|
|
||||||
} bind ClearableCookieJar::class
|
|
||||||
single(named(CacheUtils.QUALIFIER_HTTP)) { CacheUtils.createHttpCache(androidContext()) }
|
single(named(CacheUtils.QUALIFIER_HTTP)) { CacheUtils.createHttpCache(androidContext()) }
|
||||||
single {
|
single {
|
||||||
OkHttpClient.Builder().apply {
|
OkHttpClient.Builder().apply {
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package org.koitharu.kotatsu.core.network
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.os.Build
|
||||||
|
import android.webkit.*
|
||||||
|
|
||||||
|
@Suppress("OverridingDeprecatedMember")
|
||||||
|
abstract class WebViewClientCompat : WebViewClient() {
|
||||||
|
|
||||||
|
open fun shouldOverrideUrlCompat(view: WebView, url: String): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun shouldInterceptRequestCompat(view: WebView, url: String): WebResourceResponse? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onReceivedErrorCompat(
|
||||||
|
view: WebView,
|
||||||
|
errorCode: Int,
|
||||||
|
description: String?,
|
||||||
|
failingUrl: String,
|
||||||
|
isMainFrame: Boolean
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
final override fun shouldOverrideUrlLoading(
|
||||||
|
view: WebView,
|
||||||
|
request: WebResourceRequest
|
||||||
|
): Boolean = shouldOverrideUrlCompat(view, request.url.toString())
|
||||||
|
|
||||||
|
final override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||||
|
return shouldOverrideUrlCompat(view, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun shouldInterceptRequest(
|
||||||
|
view: WebView,
|
||||||
|
request: WebResourceRequest
|
||||||
|
): WebResourceResponse? = shouldInterceptRequestCompat(view, request.url.toString())
|
||||||
|
|
||||||
|
final override fun shouldInterceptRequest(
|
||||||
|
view: WebView,
|
||||||
|
url: String
|
||||||
|
): WebResourceResponse? = shouldInterceptRequestCompat(view, url)
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
final override fun onReceivedError(
|
||||||
|
view: WebView,
|
||||||
|
request: WebResourceRequest,
|
||||||
|
error: WebResourceError
|
||||||
|
) {
|
||||||
|
onReceivedErrorCompat(
|
||||||
|
view,
|
||||||
|
error.errorCode,
|
||||||
|
error.description?.toString(),
|
||||||
|
request.url.toString(),
|
||||||
|
request.isForMainFrame
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun onReceivedError(
|
||||||
|
view: WebView,
|
||||||
|
errorCode: Int,
|
||||||
|
description: String?,
|
||||||
|
failingUrl: String
|
||||||
|
) {
|
||||||
|
onReceivedErrorCompat(view, errorCode, description, failingUrl, failingUrl == view.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
final override fun onReceivedHttpError(
|
||||||
|
view: WebView,
|
||||||
|
request: WebResourceRequest,
|
||||||
|
error: WebResourceResponse
|
||||||
|
) {
|
||||||
|
onReceivedErrorCompat(
|
||||||
|
view,
|
||||||
|
error.statusCode,
|
||||||
|
error.reasonPhrase,
|
||||||
|
request.url
|
||||||
|
.toString(),
|
||||||
|
request.isForMainFrame
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Francisco José Montiel Navarro.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.koitharu.kotatsu.core.network.cookies
|
|
||||||
|
|
||||||
import okhttp3.CookieJar
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface extends [okhttp3.CookieJar] and adds methods to clear the cookies.
|
|
||||||
*/
|
|
||||||
interface ClearableCookieJar : CookieJar {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all the session cookies while maintaining the persisted ones.
|
|
||||||
*/
|
|
||||||
fun clearSession()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all the cookies from persistence and from the cache.
|
|
||||||
*/
|
|
||||||
fun clear()
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Francisco José Montiel Navarro.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.koitharu.kotatsu.core.network.cookies
|
|
||||||
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import org.koitharu.kotatsu.core.network.cookies.cache.CookieCache
|
|
||||||
import org.koitharu.kotatsu.core.network.cookies.persistence.CookiePersistor
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
class PersistentCookieJar(
|
|
||||||
private val cache: CookieCache,
|
|
||||||
private val persistor: CookiePersistor
|
|
||||||
) : ClearableCookieJar {
|
|
||||||
|
|
||||||
private var isLoaded = AtomicBoolean(false)
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
|
||||||
if (isLoaded.compareAndSet(false, true)) {
|
|
||||||
cache.addAll(persistor.loadAll())
|
|
||||||
}
|
|
||||||
cache.addAll(cookies)
|
|
||||||
persistor.saveAll(cookies.filter { it.persistent })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
|
||||||
if (isLoaded.compareAndSet(false, true)) {
|
|
||||||
cache.addAll(persistor.loadAll())
|
|
||||||
}
|
|
||||||
val cookiesToRemove: MutableList<Cookie> = ArrayList()
|
|
||||||
val validCookies: MutableList<Cookie> = ArrayList()
|
|
||||||
val it = cache.iterator()
|
|
||||||
while (it.hasNext()) {
|
|
||||||
val currentCookie = it.next()
|
|
||||||
when {
|
|
||||||
currentCookie.isExpired() -> {
|
|
||||||
cookiesToRemove.add(currentCookie)
|
|
||||||
it.remove()
|
|
||||||
}
|
|
||||||
currentCookie.matches(url) -> {
|
|
||||||
validCookies.add(currentCookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
persistor.removeAll(cookiesToRemove)
|
|
||||||
return validCookies
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun clearSession() {
|
|
||||||
cache.clear()
|
|
||||||
cache.addAll(persistor.loadAll())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun clear() {
|
|
||||||
cache.clear()
|
|
||||||
persistor.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
|
|
||||||
fun Cookie.isExpired(): Boolean {
|
|
||||||
return expiresAt < System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Francisco José Montiel Navarro.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.koitharu.kotatsu.core.network.cookies.cache
|
|
||||||
|
|
||||||
import okhttp3.Cookie
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A CookieCache handles the volatile cookie session storage.
|
|
||||||
*/
|
|
||||||
interface CookieCache : MutableIterable<Cookie> {
|
|
||||||
/**
|
|
||||||
* Add all the new cookies to the session, existing cookies will be overwritten.
|
|
||||||
*
|
|
||||||
* @param newCookies
|
|
||||||
*/
|
|
||||||
fun addAll(newCookies: Collection<Cookie>)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all the cookies from the session.
|
|
||||||
*/
|
|
||||||
fun clear()
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Francisco José Montiel Navarro.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.koitharu.kotatsu.core.network.cookies.cache
|
|
||||||
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class decorates a Cookie to re-implements equals() and hashcode() methods in order to identify
|
|
||||||
* the cookie by the following attributes: name, domain, path, secure & hostOnly.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* This new behaviour will be useful in determining when an already existing cookie in session must be overwritten.
|
|
||||||
*/
|
|
||||||
internal class IdentifiableCookie(val cookie: Cookie) {
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (other !is IdentifiableCookie) return false
|
|
||||||
return other.cookie.name == cookie.name && other.cookie.domain == cookie.domain
|
|
||||||
&& other.cookie.path == cookie.path && other.cookie.secure == cookie.secure
|
|
||||||
&& other.cookie.hostOnly == cookie.hostOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var hash = 17
|
|
||||||
hash = 31 * hash + cookie.name.hashCode()
|
|
||||||
hash = 31 * hash + cookie.domain.hashCode()
|
|
||||||
hash = 31 * hash + cookie.path.hashCode()
|
|
||||||
hash = 31 * hash + if (cookie.secure) 0 else 1
|
|
||||||
hash = 31 * hash + if (cookie.hostOnly) 0 else 1
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun decorateAll(cookies: Collection<Cookie>): List<IdentifiableCookie> {
|
|
||||||
val identifiableCookies: MutableList<IdentifiableCookie> = ArrayList(cookies.size)
|
|
||||||
for (cookie in cookies) {
|
|
||||||
identifiableCookies.add(IdentifiableCookie(cookie))
|
|
||||||
}
|
|
||||||
return identifiableCookies
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Francisco José Montiel Navarro.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.koitharu.kotatsu.core.network.cookies.cache
|
|
||||||
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import org.koitharu.kotatsu.core.network.cookies.cache.IdentifiableCookie.Companion.decorateAll
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
class SetCookieCache : CookieCache {
|
|
||||||
|
|
||||||
private val cookies: MutableSet<IdentifiableCookie> =
|
|
||||||
Collections.newSetFromMap(ConcurrentHashMap())
|
|
||||||
|
|
||||||
override fun addAll(newCookies: Collection<Cookie>) {
|
|
||||||
for (cookie in decorateAll(newCookies)) {
|
|
||||||
cookies.remove(cookie)
|
|
||||||
cookies.add(cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clear() {
|
|
||||||
cookies.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun iterator(): MutableIterator<Cookie> = SetCookieCacheIterator()
|
|
||||||
|
|
||||||
private inner class SetCookieCacheIterator : MutableIterator<Cookie> {
|
|
||||||
|
|
||||||
private val iterator = cookies.iterator()
|
|
||||||
|
|
||||||
override fun hasNext(): Boolean {
|
|
||||||
return iterator.hasNext()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun next(): Cookie {
|
|
||||||
return iterator.next().cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun remove() {
|
|
||||||
iterator.remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Francisco José Montiel Navarro.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.koitharu.kotatsu.core.network.cookies.persistence
|
|
||||||
|
|
||||||
import okhttp3.Cookie
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A CookiePersistor handles the persistent cookie storage.
|
|
||||||
*/
|
|
||||||
interface CookiePersistor {
|
|
||||||
|
|
||||||
fun loadAll(): List<Cookie>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persist all cookies, existing cookies will be overwritten.
|
|
||||||
*
|
|
||||||
* @param cookies cookies persist
|
|
||||||
*/
|
|
||||||
fun saveAll(cookies: Collection<Cookie>)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes indicated cookies from persistence.
|
|
||||||
*
|
|
||||||
* @param cookies cookies to remove from persistence
|
|
||||||
*/
|
|
||||||
fun removeAll(cookies: Collection<Cookie>)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all cookies from persistence.
|
|
||||||
*/
|
|
||||||
fun clear()
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Francisco José Montiel Navarro.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.koitharu.kotatsu.core.network.cookies.persistence
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import java.io.*
|
|
||||||
|
|
||||||
class SerializableCookie : Serializable {
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
private var cookie: Cookie? = null
|
|
||||||
|
|
||||||
fun encode(cookie: Cookie?): String? {
|
|
||||||
this.cookie = cookie
|
|
||||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
|
||||||
var objectOutputStream: ObjectOutputStream? = null
|
|
||||||
try {
|
|
||||||
objectOutputStream = ObjectOutputStream(byteArrayOutputStream)
|
|
||||||
objectOutputStream.writeObject(this)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.d(TAG, "IOException in encodeCookie", e)
|
|
||||||
return null
|
|
||||||
} finally {
|
|
||||||
if (objectOutputStream != null) {
|
|
||||||
try { // Closing a ByteArrayOutputStream has no effect, it can be used later (and is used in the return statement)
|
|
||||||
objectOutputStream.close()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.d(TAG, "Stream not closed in encodeCookie", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return byteArrayToHexString(byteArrayOutputStream.toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun decode(encodedCookie: String): Cookie? {
|
|
||||||
val bytes = hexStringToByteArray(encodedCookie)
|
|
||||||
val byteArrayInputStream = ByteArrayInputStream(
|
|
||||||
bytes
|
|
||||||
)
|
|
||||||
var cookie: Cookie? = null
|
|
||||||
var objectInputStream: ObjectInputStream? = null
|
|
||||||
try {
|
|
||||||
objectInputStream = ObjectInputStream(byteArrayInputStream)
|
|
||||||
cookie = (objectInputStream.readObject() as SerializableCookie).cookie
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.d(TAG, "IOException in decodeCookie", e)
|
|
||||||
} catch (e: ClassNotFoundException) {
|
|
||||||
Log.d(TAG, "ClassNotFoundException in decodeCookie", e)
|
|
||||||
} finally {
|
|
||||||
if (objectInputStream != null) {
|
|
||||||
try {
|
|
||||||
objectInputStream.close()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.d(TAG, "Stream not closed in decodeCookie", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
private fun writeObject(out: ObjectOutputStream) {
|
|
||||||
out.writeObject(cookie!!.name)
|
|
||||||
out.writeObject(cookie!!.value)
|
|
||||||
out.writeLong(if (cookie!!.persistent) cookie!!.expiresAt else NON_VALID_EXPIRES_AT)
|
|
||||||
out.writeObject(cookie!!.domain)
|
|
||||||
out.writeObject(cookie!!.path)
|
|
||||||
out.writeBoolean(cookie!!.secure)
|
|
||||||
out.writeBoolean(cookie!!.httpOnly)
|
|
||||||
out.writeBoolean(cookie!!.hostOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class, ClassNotFoundException::class)
|
|
||||||
private fun readObject(`in`: ObjectInputStream) {
|
|
||||||
val builder = Cookie.Builder()
|
|
||||||
builder.name((`in`.readObject() as String))
|
|
||||||
builder.value((`in`.readObject() as String))
|
|
||||||
val expiresAt = `in`.readLong()
|
|
||||||
if (expiresAt != NON_VALID_EXPIRES_AT) {
|
|
||||||
builder.expiresAt(expiresAt)
|
|
||||||
}
|
|
||||||
val domain = `in`.readObject() as String
|
|
||||||
builder.domain(domain)
|
|
||||||
builder.path((`in`.readObject() as String))
|
|
||||||
if (`in`.readBoolean()) builder.secure()
|
|
||||||
if (`in`.readBoolean()) builder.httpOnly()
|
|
||||||
if (`in`.readBoolean()) builder.hostOnlyDomain(domain)
|
|
||||||
cookie = builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
|
|
||||||
private val TAG = SerializableCookie::class.java.simpleName
|
|
||||||
|
|
||||||
const val serialVersionUID = -8594045714036645534L
|
|
||||||
private const val NON_VALID_EXPIRES_AT = -1L
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Using some super basic byte array <-> hex conversions so we don't
|
|
||||||
* have to rely on any large Base64 libraries. Can be overridden if you
|
|
||||||
* like!
|
|
||||||
*
|
|
||||||
* @param bytes byte array to be converted
|
|
||||||
* @return string containing hex values
|
|
||||||
*/
|
|
||||||
private fun byteArrayToHexString(bytes: ByteArray): String {
|
|
||||||
val sb = StringBuilder(bytes.size * 2)
|
|
||||||
for (element in bytes) {
|
|
||||||
val v: Int = element.toInt() and 0xff
|
|
||||||
if (v < 16) {
|
|
||||||
sb.append('0')
|
|
||||||
}
|
|
||||||
sb.append(Integer.toHexString(v))
|
|
||||||
}
|
|
||||||
return sb.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts hex values from strings to byte array
|
|
||||||
*
|
|
||||||
* @param hexString string of hex-encoded values
|
|
||||||
* @return decoded byte array
|
|
||||||
*/
|
|
||||||
private fun hexStringToByteArray(hexString: String): ByteArray {
|
|
||||||
val len = hexString.length
|
|
||||||
val data = ByteArray(len / 2)
|
|
||||||
var i = 0
|
|
||||||
while (i < len) {
|
|
||||||
data[i / 2] = ((Character.digit(hexString[i], 16) shl 4) + Character
|
|
||||||
.digit(hexString[i + 1], 16)).toByte()
|
|
||||||
i += 2
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Francisco José Montiel Navarro.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.koitharu.kotatsu.core.network.cookies.persistence
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import okhttp3.Cookie
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class SharedPrefsCookiePersistor(context: Context) : CookiePersistor {
|
|
||||||
|
|
||||||
private val sharedPreferences by lazy {
|
|
||||||
context.getSharedPreferences("cookies", Context.MODE_PRIVATE)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadAll(): List<Cookie> {
|
|
||||||
val cookies: MutableList<Cookie> = ArrayList(sharedPreferences.all.size)
|
|
||||||
for ((_, value) in sharedPreferences.all) {
|
|
||||||
val serializedCookie = value as? String
|
|
||||||
if (serializedCookie != null) {
|
|
||||||
val cookie = SerializableCookie().decode(serializedCookie)
|
|
||||||
if (cookie != null) {
|
|
||||||
cookies.add(cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cookies
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun saveAll(cookies: Collection<Cookie>) {
|
|
||||||
val editor = sharedPreferences.edit()
|
|
||||||
for (cookie in cookies) {
|
|
||||||
editor.putString(createCookieKey(cookie), SerializableCookie().encode(cookie))
|
|
||||||
}
|
|
||||||
editor.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeAll(cookies: Collection<Cookie>) {
|
|
||||||
val editor = sharedPreferences.edit()
|
|
||||||
for (cookie in cookies) {
|
|
||||||
editor.remove(createCookieKey(cookie))
|
|
||||||
}
|
|
||||||
editor.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clear() {
|
|
||||||
sharedPreferences.edit().clear().apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
|
|
||||||
fun createCookieKey(cookie: Cookie): String {
|
|
||||||
return (if (cookie.secure) "https" else "http") + "://" + cookie.domain + cookie.path + "|" + cookie.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ import org.koin.android.ext.android.get
|
|||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||||
import org.koitharu.kotatsu.core.network.cookies.ClearableCookieJar
|
import org.koitharu.kotatsu.core.network.AndroidCookieJar
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.local.data.Cache
|
import org.koitharu.kotatsu.local.data.Cache
|
||||||
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
|
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
|
||||||
@@ -75,10 +75,8 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
|||||||
}
|
}
|
||||||
AppSettings.KEY_COOKIES_CLEAR -> {
|
AppSettings.KEY_COOKIES_CLEAR -> {
|
||||||
viewLifecycleScope.launch {
|
viewLifecycleScope.launch {
|
||||||
val cookieJar = get<ClearableCookieJar>()
|
val cookieJar = get<AndroidCookieJar>()
|
||||||
withContext(Dispatchers.IO) {
|
cookieJar.clear()
|
||||||
cookieJar.clear()
|
|
||||||
}
|
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
listView ?: return@launch,
|
listView ?: return@launch,
|
||||||
R.string.cookies_cleared,
|
R.string.cookies_cleared,
|
||||||
|
|||||||
Reference in New Issue
Block a user