优化
This commit is contained in:
@@ -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
-1
@@ -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("=")
|
||||
|
||||
Reference in New Issue
Block a user