This commit is contained in:
Horis
2023-05-12 20:26:36 +08:00
parent c94b2acb2c
commit ecf93801f9
11 changed files with 309 additions and 66 deletions
@@ -43,4 +43,8 @@ object AppPattern {
val notReadAloudRegex = Regex("^(\\s|\\p{C}|\\p{P}|\\p{Z}|\\p{S})+$")
val xmlContentTypeRegex = "(application|text)/\\w*\\+?xml.*".toRegex()
val semicolonRegex = ";".toRegex()
val equalsRegex = "=".toRegex()
}
@@ -13,6 +13,7 @@ import io.legado.app.constant.AppLog
import io.legado.app.data.entities.BaseSource
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.http.BackstageWebView
import io.legado.app.help.http.CookieManager
import io.legado.app.help.http.CookieStore
import io.legado.app.help.http.SSLHelper
import io.legado.app.help.http.StrResponse
@@ -286,10 +287,8 @@ interface JsExtensions : JsEncodeUtils {
.headers(headers)
.method(Connection.Method.GET)
.execute()
val cookies = response.cookies()
CookieStore.mapToCookie(cookies)?.let {
val domain = NetworkUtils.getSubDomain(urlStr)
CacheManager.putMemory("${domain}_cookieJar", it)
if (getSource()?.enabledCookieJar == true) {
CookieManager.saveResponse(response)
}
return response
}
@@ -305,10 +304,8 @@ interface JsExtensions : JsEncodeUtils {
.headers(headers)
.method(Connection.Method.HEAD)
.execute()
val cookies = response.cookies()
CookieStore.mapToCookie(cookies)?.let {
val domain = NetworkUtils.getSubDomain(urlStr)
CacheManager.putMemory("${domain}_cookieJar", it)
if (getSource()?.enabledCookieJar == true) {
CookieManager.saveResponse(response)
}
return response
}
@@ -325,10 +322,8 @@ interface JsExtensions : JsEncodeUtils {
.headers(headers)
.method(Connection.Method.POST)
.execute()
val cookies = response.cookies()
CookieStore.mapToCookie(cookies)?.let {
val domain = NetworkUtils.getSubDomain(urlStr)
CacheManager.putMemory("${domain}_cookieJar", it)
if (getSource()?.enabledCookieJar == true) {
CookieManager.saveResponse(response)
}
return response
}
@@ -0,0 +1,148 @@
package io.legado.app.help.http
import io.legado.app.data.appDb
import io.legado.app.help.CacheManager
import io.legado.app.utils.NetworkUtils
import okhttp3.Cookie
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Connection
object CookieManager {
/**
* <domain>_session_cookie 会话期 cookie,应用重启后失效
* <domain>_cookie cookies 缓存
*/
const val cookieJarHeader = "CookieJar"
/**
* 从响应中保存Cookies
*/
fun saveResponse(response: Response) {
val url = response.request.url
val headers = response.headers
saveCookiesFromHeaders(url, headers)
}
fun saveResponse(response: Connection.Response) {
val url = response.url().toHttpUrlOrNull() ?: return
val headers = response.multiHeaders().toHeaders()
saveCookiesFromHeaders(url, headers)
}
private fun saveCookiesFromHeaders(url: HttpUrl, headers: Headers) {
val domain = NetworkUtils.getSubDomain(url.toString())
val cookies = Cookie.parseAll(url, headers)
val sessionCookie = cookies.filter { !it.persistent }.getString()
updateSessionCookie(domain, sessionCookie)
val cookieString = cookies.filter { it.persistent }.getString()
CookieStore.replaceCookie(domain, cookieString)
}
/**
* 加载Cookies到请求中
*/
fun loadRequest(request: Request): Request {
val domain = NetworkUtils.getSubDomain(request.url.toString())
val cookie = CookieStore.getCookie(domain)
val requestCookie = request.header("Cookie")
mergeCookies(cookie, requestCookie)?.let {
return request.newBuilder()
.header("Cookie", it)
.build()
}
return request
}
private fun getSessionCookieMap(domain: String): MutableMap<String, String>? {
return getSessionCookie(domain)?.let { CookieStore.cookieToMap(it) }
}
fun getSessionCookie(domain: String): String? {
return CacheManager.getFromMemory("${domain}_session_cookie") as? String
}
private fun updateSessionCookie(domain: String, cookies: String) {
val sessionCookie = getSessionCookie(domain)
if (sessionCookie.isNullOrEmpty()) {
CacheManager.putMemory("${domain}_session_cookie", cookies)
return
}
val ck = mergeCookies(sessionCookie, cookies) ?: return
CacheManager.putMemory("${domain}_session_cookie", ck)
}
fun mergeCookies(vararg cookies: String?): String? {
val cookieMap = mergeCookiesToMap(*cookies)
return CookieStore.mapToCookie(cookieMap)
}
fun mergeCookiesToMap(vararg cookies: String?): MutableMap<String, String> {
return cookies.filterNotNull().map {
CookieStore.cookieToMap(it)
}.reduce { acc, cookieMap ->
acc.apply { putAll(cookieMap) }
}
}
/**
* 删除单个Cookie
*/
fun removeCookie(url: String, key: String) {
val domain = NetworkUtils.getSubDomain(url)
getSessionCookieMap(domain)?.let {
it.remove(key)
CookieStore.mapToCookie(it)?.let { cookie ->
CacheManager.putMemory("${domain}_session_cookie", cookie)
}
}
val cookie = getCookieNoSession(url)
if (cookie.isNotEmpty()) {
val cookieMap = CookieStore.cookieToMap(cookie).apply { remove(key) }
CookieStore.mapToCookie(cookieMap)?.let {
CookieStore.setCookie(url, it)
}
}
}
fun getCookieNoSession(url: String): String {
val domain = NetworkUtils.getSubDomain(url)
val cacheCookie = CacheManager.getFromMemory("${domain}_cookie") as? String
return if (cacheCookie != null) {
cacheCookie
} else {
val cookieBean = appDb.cookieDao.get(domain)
cookieBean?.cookie ?: ""
}
}
fun List<Cookie>.getString() = buildString {
this@getString.forEachIndexed { index, cookie ->
if (index > 0) append("; ")
append(cookie.name).append('=').append(cookie.value)
}
}
private fun Map<String, List<String>>.toHeaders(): Headers {
return Headers.Builder().apply {
this@toHeaders.forEach { (k, v) ->
v.forEach {
add(k, it)
}
}
}.build()
}
}
@@ -3,14 +3,18 @@
package io.legado.app.help.http
import android.text.TextUtils
import io.legado.app.constant.AppPattern.semicolonRegex
import io.legado.app.constant.AppPattern.equalsRegex
import io.legado.app.data.appDb
import io.legado.app.data.entities.Cookie
import io.legado.app.help.CacheManager
import io.legado.app.help.http.api.CookieManager
import io.legado.app.help.http.CookieManager.getCookieNoSession
import io.legado.app.help.http.CookieManager.mergeCookiesToMap
import io.legado.app.help.http.api.CookieManagerInterface
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.removeCookie
object CookieStore : CookieManager {
object CookieStore : CookieManagerInterface {
/**
*保存cookie到数据库,会自动识别url的二级域名
@@ -26,7 +30,7 @@ object CookieStore : CookieManager {
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(cookie)) {
return
}
val oldCookie = getCookie(url)
val oldCookie = getCookieNoSession(url)
if (TextUtils.isEmpty(oldCookie)) {
setCookie(url, cookie)
} else {
@@ -42,19 +46,26 @@ object CookieStore : CookieManager {
*/
override fun getCookie(url: String): String {
val domain = NetworkUtils.getSubDomain(url)
CacheManager.getFromMemory("${domain}_cookie")?.let {
if (it is String) return it
}
val cookieBean = appDb.cookieDao.get(domain)
val cookie = cookieBean?.cookie ?: ""
CacheManager.putMemory(url, cookie)
return cookie
val cookie = getCookieNoSession(url)
val sessionCookie = CookieManager.getSessionCookie(domain)
val cookieMap = mergeCookiesToMap(cookie, sessionCookie)
var ck = mapToCookie(cookieMap) ?: ""
while (ck.length > 4096) {
val removeKey = cookieMap.keys.random()
CookieManager.removeCookie(url, removeKey)
cookieMap.remove(removeKey)
ck = mapToCookie(cookieMap) ?: ""
}
return ck
}
fun getKey(url: String, key: String): String {
val cookie = getCookie(url)
val cookieMap = cookieToMap(cookie)
val sessionCookie = CookieManager.getSessionCookie(url)
val cookieMap = mergeCookiesToMap(cookie, sessionCookie)
return cookieMap[key] ?: ""
}
@@ -62,6 +73,7 @@ object CookieStore : CookieManager {
val domain = NetworkUtils.getSubDomain(url)
appDb.cookieDao.delete(domain)
CacheManager.deleteMemory("${domain}_cookie")
CacheManager.deleteMemory("${domain}_session_cookie")
android.webkit.CookieManager.getInstance().removeCookie(domain)
}
@@ -70,9 +82,9 @@ object CookieStore : CookieManager {
if (cookie.isBlank()) {
return cookieMap
}
val pairArray = cookie.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val pairArray = cookie.split(semicolonRegex).dropLastWhile { it.isEmpty() }.toTypedArray()
for (pair in pairArray) {
val pairs = pair.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val pairs = pair.split(equalsRegex).dropLastWhile { it.isEmpty() }.toTypedArray()
if (pairs.size == 1) {
continue
}
@@ -91,7 +103,7 @@ object CookieStore : CookieManager {
}
val builder = StringBuilder()
cookieMap.keys.forEachIndexed { index, key ->
if (index > 0) builder.append(";")
if (index > 0) builder.append("; ")
builder.append(key).append("=").append(cookieMap[key])
}
return builder.toString()
@@ -3,6 +3,7 @@ package io.legado.app.help.http
import io.legado.app.constant.AppConst
import io.legado.app.help.CacheManager
import io.legado.app.help.config.AppConfig
import io.legado.app.help.http.CookieManager.cookieJarHeader
import io.legado.app.utils.NetworkUtils
import okhttp3.*
import java.net.InetSocketAddress
@@ -48,7 +49,7 @@ val okHttpClient: OkHttpClient by lazy {
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.cookieJar(cookieJar = cookieJar)
//.cookieJar(cookieJar = cookieJar)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory, SSLHelper.unsafeTrustManager)
.retryOnConnectionFailure(true)
.hostnameVerifier(SSLHelper.unsafeHostnameVerifier)
@@ -66,8 +67,26 @@ val okHttpClient: OkHttpClient by lazy {
builder.addHeader("Keep-Alive", "300")
builder.addHeader("Connection", "Keep-Alive")
builder.addHeader("Cache-Control", "no-cache")
chain.proceed(builder.build())
chain.proceed(builder.build()).newBuilder().removeHeader(cookieJarHeader).build()
})
.addNetworkInterceptor { chain ->
var request = chain.request()
val enableCookieJar = request.header(cookieJarHeader) != null
if (enableCookieJar) {
val requestBuilder = request.newBuilder()
requestBuilder.removeHeader(cookieJarHeader)
request = CookieManager.loadRequest(requestBuilder.build())
}
var networkResponse = chain.proceed(request)
if (enableCookieJar) {
CookieManager.saveResponse(networkResponse)
networkResponse = networkResponse.newBuilder().header(cookieJarHeader, "1").build()
}
networkResponse
}
if (!AppConst.isPlayChannel && AppConfig.isCronet) {
if (Cronet.loader?.install() == true) {
Cronet.interceptor?.let {
@@ -1,6 +1,6 @@
package io.legado.app.help.http.api
interface CookieManager {
interface CookieManagerInterface {
/**
* 保存cookie
@@ -20,6 +20,7 @@ import io.legado.app.help.JsExtensions
import io.legado.app.help.config.AppConfig
import io.legado.app.help.glide.GlideHeaders
import io.legado.app.help.http.*
import io.legado.app.help.http.CookieManager.mergeCookies
import io.legado.app.utils.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
@@ -606,13 +607,13 @@ class AnalyzeUrl(
CookieStore.getCookie(domain)
}
if (cookie.isNotEmpty()) {
val cookieMap = CookieStore.cookieToMap(cookie)
val customCookieMap = CookieStore.cookieToMap(headerMap["Cookie"] ?: "")
cookieMap.putAll(customCookieMap)
CookieStore.mapToCookie(cookieMap)?.let {
mergeCookies(cookie, headerMap["Cookie"])?.let {
headerMap.put("Cookie", it)
}
}
if (enabledCookieJar) {
headerMap[CookieManager.cookieJarHeader] = "1"
}
}
/**
@@ -2,6 +2,7 @@ package io.legado.app.utils
import io.legado.app.BuildConfig
import io.legado.app.constant.AppLog
import io.legado.app.constant.AppPattern.semicolonRegex
import io.legado.app.help.config.AppConfig
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.analyzeRule.CustomUrl
@@ -102,7 +103,7 @@ object UrlUtil {
val redirectUrl: String? = conn.getHeaderField("Location")
return if (raw != null) {
val fileNames = raw.split(";".toRegex()).filter { it.contains("filename") }
val fileNames = raw.split(semicolonRegex).filter { it.contains("filename") }
val names = hashSetOf<String>()
fileNames.forEach {
val fileName = it.substringAfter("=")