mirror of
https://github.com/gedoor/legado.git
synced 2024-09-01 09:34:25 +08:00
优化
This commit is contained in:
parent
14ca962942
commit
1ebf3491f4
@ -2,6 +2,8 @@ package io.legado.app.lib.cronet
|
|||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
|
import io.legado.app.help.http.CookieManager
|
||||||
|
import io.legado.app.help.http.CookieManager.cookieJarHeader
|
||||||
import io.legado.app.help.http.okHttpClient
|
import io.legado.app.help.http.okHttpClient
|
||||||
import io.legado.app.utils.DebugLog
|
import io.legado.app.utils.DebugLog
|
||||||
import io.legado.app.utils.asIOException
|
import io.legado.app.utils.asIOException
|
||||||
@ -11,6 +13,8 @@ import okhttp3.*
|
|||||||
import okhttp3.EventListener
|
import okhttp3.EventListener
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||||
|
import okhttp3.internal.http.HttpMethod
|
||||||
|
import okhttp3.internal.http.StatusLine
|
||||||
import okio.Buffer
|
import okio.Buffer
|
||||||
import okio.Source
|
import okio.Source
|
||||||
import okio.Timeout
|
import okio.Timeout
|
||||||
@ -29,7 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
abstract class AbsCallBack(
|
abstract class AbsCallBack(
|
||||||
val originalRequest: Request,
|
var originalRequest: Request,
|
||||||
val mCall: Call,
|
val mCall: Call,
|
||||||
private val eventListener: EventListener? = null,
|
private val eventListener: EventListener? = null,
|
||||||
private val responseCallback: Callback? = null
|
private val responseCallback: Callback? = null
|
||||||
@ -43,6 +47,16 @@ abstract class AbsCallBack(
|
|||||||
private val callbackResults = ArrayBlockingQueue<CallbackResult>(2)
|
private val callbackResults = ArrayBlockingQueue<CallbackResult>(2)
|
||||||
private val urlResponseInfoChain = arrayListOf<UrlResponseInfo>()
|
private val urlResponseInfoChain = arrayListOf<UrlResponseInfo>()
|
||||||
private var cancelJob: Coroutine<*>? = null
|
private var cancelJob: Coroutine<*>? = null
|
||||||
|
private var followRedirect = false
|
||||||
|
private var enableCookieJar = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (originalRequest.header(cookieJarHeader) != null) {
|
||||||
|
enableCookieJar = true
|
||||||
|
originalRequest = originalRequest.newBuilder()
|
||||||
|
.removeHeader(cookieJarHeader).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@ -80,15 +94,23 @@ abstract class AbsCallBack(
|
|||||||
urlResponseInfoChain.add(info)
|
urlResponseInfoChain.add(info)
|
||||||
val client = okHttpClient
|
val client = okHttpClient
|
||||||
if (originalRequest.url.isHttps && newLocationUrl.startsWith("http://") && client.followSslRedirects) {
|
if (originalRequest.url.isHttps && newLocationUrl.startsWith("http://") && client.followSslRedirects) {
|
||||||
request.followRedirect()
|
followRedirect = true
|
||||||
} else if (!originalRequest.url.isHttps && newLocationUrl.startsWith("https://") && client.followSslRedirects) {
|
} else if (!originalRequest.url.isHttps && newLocationUrl.startsWith("https://") && client.followSslRedirects) {
|
||||||
request.followRedirect()
|
followRedirect = true
|
||||||
} else if (okHttpClient.followRedirects) {
|
} else if (okHttpClient.followRedirects) {
|
||||||
request.followRedirect()
|
followRedirect = true
|
||||||
} else {
|
|
||||||
onError(IOException("Too many redirect"))
|
|
||||||
request.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!followRedirect) {
|
||||||
|
onError(IOException("Too many redirect"))
|
||||||
|
} else {
|
||||||
|
val response = toResponse(originalRequest, info, urlResponseInfoChain)
|
||||||
|
if (enableCookieJar) {
|
||||||
|
CookieManager.saveResponse(response)
|
||||||
|
}
|
||||||
|
originalRequest = buildRedirectRequest(response, originalRequest.method, newLocationUrl)
|
||||||
|
}
|
||||||
|
request.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -102,29 +124,26 @@ abstract class AbsCallBack(
|
|||||||
request.cancel()
|
request.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
val responseBuilder: Response.Builder
|
val response: Response
|
||||||
try {
|
try {
|
||||||
responseBuilder = createResponse(
|
response = toResponse(originalRequest, info, urlResponseInfoChain, CronetBodySource())
|
||||||
originalRequest,
|
|
||||||
info,
|
|
||||||
CronetBodySource()
|
|
||||||
)
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
request.cancel()
|
request.cancel()
|
||||||
cancelJob?.cancel()
|
cancelJob?.cancel()
|
||||||
onError(e)
|
onError(e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val newRequest = originalRequest.newBuilder().url(info.url).build()
|
|
||||||
val response = responseBuilder
|
if (enableCookieJar) {
|
||||||
.request(newRequest)
|
CookieManager.saveResponse(response)
|
||||||
.priorResponse(buildPriorResponse(originalRequest, urlResponseInfoChain, info.urlChain))
|
}
|
||||||
.build()
|
|
||||||
mResponse = response
|
mResponse = response
|
||||||
onSuccess(response)
|
onSuccess(response)
|
||||||
|
|
||||||
//打印协议,用于调试
|
//打印协议,用于调试
|
||||||
DebugLog.i(javaClass.simpleName, "onResponseStarted[${info.negotiatedProtocol}][${info.httpStatusCode}]${info.url}")
|
val msg = "onResponseStarted[${info.negotiatedProtocol}][${info.httpStatusCode}]${info.url}"
|
||||||
|
DebugLog.i(javaClass.simpleName, msg)
|
||||||
if (eventListener != null) {
|
if (eventListener != null) {
|
||||||
eventListener.responseHeadersEnd(mCall, response)
|
eventListener.responseHeadersEnd(mCall, response)
|
||||||
eventListener.responseBodyStart(mCall)
|
eventListener.responseBodyStart(mCall)
|
||||||
@ -167,6 +186,16 @@ abstract class AbsCallBack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||||
|
if (followRedirect) {
|
||||||
|
followRedirect = false
|
||||||
|
if (enableCookieJar) {
|
||||||
|
val newRequest = CookieManager.loadRequest(originalRequest)
|
||||||
|
buildRequest(newRequest, this)?.start()
|
||||||
|
} else {
|
||||||
|
buildRequest(originalRequest, this)?.start()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
canceled.set(true)
|
canceled.set(true)
|
||||||
callbackResults.add(CallbackResult(CallbackStep.ON_CANCELED))
|
callbackResults.add(CallbackResult(CallbackStep.ON_CANCELED))
|
||||||
//DebugLog.i(javaClass.simpleName, "cancel[${info?.negotiatedProtocol}]${info?.url}")
|
//DebugLog.i(javaClass.simpleName, "cancel[${info?.negotiatedProtocol}]${info?.url}")
|
||||||
@ -293,15 +322,12 @@ abstract class AbsCallBack(
|
|||||||
private fun buildPriorResponse(
|
private fun buildPriorResponse(
|
||||||
request: Request,
|
request: Request,
|
||||||
redirectResponseInfos: List<UrlResponseInfo>,
|
redirectResponseInfos: List<UrlResponseInfo>,
|
||||||
urlChain: List<String>
|
|
||||||
): Response? {
|
): Response? {
|
||||||
var priorResponse: Response? = null
|
var priorResponse: Response? = null
|
||||||
if (redirectResponseInfos.isNotEmpty()) {
|
if (redirectResponseInfos.isNotEmpty()) {
|
||||||
check(urlChain.size == redirectResponseInfos.size + 1) {
|
|
||||||
"The number of redirects should be consistent across URLs and headers!"
|
|
||||||
}
|
|
||||||
for (i in redirectResponseInfos.indices) {
|
for (i in redirectResponseInfos.indices) {
|
||||||
val redirectedRequest = request.newBuilder().url(urlChain[i]).build()
|
val url = redirectResponseInfos[i].url
|
||||||
|
val redirectedRequest = request.newBuilder().url(url).build()
|
||||||
priorResponse = createResponse(redirectedRequest, redirectResponseInfos[i])
|
priorResponse = createResponse(redirectedRequest, redirectResponseInfos[i])
|
||||||
.priorResponse(priorResponse)
|
.priorResponse(priorResponse)
|
||||||
.build()
|
.build()
|
||||||
@ -336,6 +362,48 @@ abstract class AbsCallBack(
|
|||||||
return bodySource.buffer()
|
return bodySource.buffer()
|
||||||
.asResponseBody(contentType?.toMediaTypeOrNull(), contentLength)
|
.asResponseBody(contentType?.toMediaTypeOrNull(), contentLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildRedirectRequest(
|
||||||
|
userResponse: Response,
|
||||||
|
method: String,
|
||||||
|
newLocationUrl: String
|
||||||
|
): Request {
|
||||||
|
// Most redirects don't include a request body.
|
||||||
|
val requestBuilder = userResponse.request.newBuilder()
|
||||||
|
if (HttpMethod.permitsRequestBody(method)) {
|
||||||
|
val responseCode = userResponse.code
|
||||||
|
val maintainBody = HttpMethod.redirectsWithBody(method) ||
|
||||||
|
responseCode == StatusLine.HTTP_PERM_REDIRECT ||
|
||||||
|
responseCode == StatusLine.HTTP_TEMP_REDIRECT
|
||||||
|
if (HttpMethod.redirectsToGet(method) && responseCode != StatusLine.HTTP_PERM_REDIRECT && responseCode != StatusLine.HTTP_TEMP_REDIRECT) {
|
||||||
|
requestBuilder.method("GET", null)
|
||||||
|
} else {
|
||||||
|
val requestBody = if (maintainBody) userResponse.request.body else null
|
||||||
|
requestBuilder.method(method, requestBody)
|
||||||
|
}
|
||||||
|
if (!maintainBody) {
|
||||||
|
requestBuilder.removeHeader("Transfer-Encoding")
|
||||||
|
requestBuilder.removeHeader("Content-Length")
|
||||||
|
requestBuilder.removeHeader("Content-Type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestBuilder.url(newLocationUrl).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toResponse(
|
||||||
|
request: Request,
|
||||||
|
responseInfo: UrlResponseInfo,
|
||||||
|
redirectResponseInfos: List<UrlResponseInfo>,
|
||||||
|
bodySource: Source? = null
|
||||||
|
): Response {
|
||||||
|
val responseBuilder = createResponse(request, responseInfo, bodySource)
|
||||||
|
val newRequest = request.newBuilder().url(responseInfo.url).build()
|
||||||
|
return responseBuilder
|
||||||
|
.request(newRequest)
|
||||||
|
.priorResponse(buildPriorResponse(request, redirectResponseInfos))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class CronetBodySource : Source {
|
inner class CronetBodySource : Source {
|
||||||
|
@ -6,6 +6,7 @@ package io.legado.app.lib.cronet
|
|||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import io.legado.app.constant.AppConst
|
import io.legado.app.constant.AppConst
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
|
import io.legado.app.help.http.CookieManager.cookieJarHeader
|
||||||
import io.legado.app.help.http.okHttpClient
|
import io.legado.app.help.http.okHttpClient
|
||||||
import io.legado.app.utils.DebugLog
|
import io.legado.app.utils.DebugLog
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
@ -80,6 +81,7 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {
|
|||||||
setHttpMethod(request.method)//设置
|
setHttpMethod(request.method)//设置
|
||||||
allowDirectExecutor()
|
allowDirectExecutor()
|
||||||
headers.forEachIndexed { index, _ ->
|
headers.forEachIndexed { index, _ ->
|
||||||
|
if (headers.name(index) == cookieJarHeader) return@forEachIndexed
|
||||||
addHeader(headers.name(index), headers.value(index))
|
addHeader(headers.name(index), headers.value(index))
|
||||||
}
|
}
|
||||||
if (requestBody != null) {
|
if (requestBody != null) {
|
||||||
|
@ -25,18 +25,11 @@ class CronetInterceptor(private val cookieJar: CookieJar) : Interceptor {
|
|||||||
//移除Keep-Alive,手动设置会导致400 BadRequest
|
//移除Keep-Alive,手动设置会导致400 BadRequest
|
||||||
builder.removeHeader("Keep-Alive")
|
builder.removeHeader("Keep-Alive")
|
||||||
builder.removeHeader("Accept-Encoding")
|
builder.removeHeader("Accept-Encoding")
|
||||||
if (cookieJar != CookieJar.NO_COOKIES) {
|
|
||||||
val cookieStr = getCookie(original.url)
|
|
||||||
//设置Cookie
|
|
||||||
if (cookieStr.length > 3) {
|
|
||||||
builder.addHeader("Cookie", cookieStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val newReq = builder.build()
|
val newReq = builder.build()
|
||||||
proceedWithCronet(newReq, chain.call())?.let { response ->
|
proceedWithCronet(newReq, chain.call())?.let { response ->
|
||||||
//从Response 中保存Cookie到CookieJar
|
//从Response 中保存Cookie到CookieJar
|
||||||
cookieJar.receiveHeaders(newReq.url, response.headers)
|
//cookieJar.receiveHeaders(newReq.url, response.headers)
|
||||||
response
|
response
|
||||||
} ?: chain.proceed(original)
|
} ?: chain.proceed(original)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -43,4 +43,8 @@ object AppPattern {
|
|||||||
val notReadAloudRegex = Regex("^(\\s|\\p{C}|\\p{P}|\\p{Z}|\\p{S})+$")
|
val notReadAloudRegex = Regex("^(\\s|\\p{C}|\\p{P}|\\p{Z}|\\p{S})+$")
|
||||||
|
|
||||||
val xmlContentTypeRegex = "(application|text)/\\w*\\+?xml.*".toRegex()
|
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.data.entities.BaseSource
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.http.BackstageWebView
|
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.CookieStore
|
||||||
import io.legado.app.help.http.SSLHelper
|
import io.legado.app.help.http.SSLHelper
|
||||||
import io.legado.app.help.http.StrResponse
|
import io.legado.app.help.http.StrResponse
|
||||||
@ -286,10 +287,8 @@ interface JsExtensions : JsEncodeUtils {
|
|||||||
.headers(headers)
|
.headers(headers)
|
||||||
.method(Connection.Method.GET)
|
.method(Connection.Method.GET)
|
||||||
.execute()
|
.execute()
|
||||||
val cookies = response.cookies()
|
if (getSource()?.enabledCookieJar == true) {
|
||||||
CookieStore.mapToCookie(cookies)?.let {
|
CookieManager.saveResponse(response)
|
||||||
val domain = NetworkUtils.getSubDomain(urlStr)
|
|
||||||
CacheManager.putMemory("${domain}_cookieJar", it)
|
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@ -305,10 +304,8 @@ interface JsExtensions : JsEncodeUtils {
|
|||||||
.headers(headers)
|
.headers(headers)
|
||||||
.method(Connection.Method.HEAD)
|
.method(Connection.Method.HEAD)
|
||||||
.execute()
|
.execute()
|
||||||
val cookies = response.cookies()
|
if (getSource()?.enabledCookieJar == true) {
|
||||||
CookieStore.mapToCookie(cookies)?.let {
|
CookieManager.saveResponse(response)
|
||||||
val domain = NetworkUtils.getSubDomain(urlStr)
|
|
||||||
CacheManager.putMemory("${domain}_cookieJar", it)
|
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
@ -325,10 +322,8 @@ interface JsExtensions : JsEncodeUtils {
|
|||||||
.headers(headers)
|
.headers(headers)
|
||||||
.method(Connection.Method.POST)
|
.method(Connection.Method.POST)
|
||||||
.execute()
|
.execute()
|
||||||
val cookies = response.cookies()
|
if (getSource()?.enabledCookieJar == true) {
|
||||||
CookieStore.mapToCookie(cookies)?.let {
|
CookieManager.saveResponse(response)
|
||||||
val domain = NetworkUtils.getSubDomain(urlStr)
|
|
||||||
CacheManager.putMemory("${domain}_cookieJar", it)
|
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
148
app/src/main/java/io/legado/app/help/http/CookieManager.kt
Normal file
148
app/src/main/java/io/legado/app/help/http/CookieManager.kt
Normal file
@ -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
|
package io.legado.app.help.http
|
||||||
|
|
||||||
import android.text.TextUtils
|
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.appDb
|
||||||
import io.legado.app.data.entities.Cookie
|
import io.legado.app.data.entities.Cookie
|
||||||
import io.legado.app.help.CacheManager
|
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.NetworkUtils
|
||||||
import io.legado.app.utils.removeCookie
|
import io.legado.app.utils.removeCookie
|
||||||
|
|
||||||
object CookieStore : CookieManager {
|
object CookieStore : CookieManagerInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*保存cookie到数据库,会自动识别url的二级域名
|
*保存cookie到数据库,会自动识别url的二级域名
|
||||||
@ -26,7 +30,7 @@ object CookieStore : CookieManager {
|
|||||||
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(cookie)) {
|
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(cookie)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val oldCookie = getCookie(url)
|
val oldCookie = getCookieNoSession(url)
|
||||||
if (TextUtils.isEmpty(oldCookie)) {
|
if (TextUtils.isEmpty(oldCookie)) {
|
||||||
setCookie(url, cookie)
|
setCookie(url, cookie)
|
||||||
} else {
|
} else {
|
||||||
@ -42,19 +46,26 @@ object CookieStore : CookieManager {
|
|||||||
*/
|
*/
|
||||||
override fun getCookie(url: String): String {
|
override fun getCookie(url: String): String {
|
||||||
val domain = NetworkUtils.getSubDomain(url)
|
val domain = NetworkUtils.getSubDomain(url)
|
||||||
CacheManager.getFromMemory("${domain}_cookie")?.let {
|
|
||||||
if (it is String) return it
|
|
||||||
}
|
|
||||||
|
|
||||||
val cookieBean = appDb.cookieDao.get(domain)
|
val cookie = getCookieNoSession(url)
|
||||||
val cookie = cookieBean?.cookie ?: ""
|
val sessionCookie = CookieManager.getSessionCookie(domain)
|
||||||
CacheManager.putMemory(url, cookie)
|
|
||||||
return cookie
|
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 {
|
fun getKey(url: String, key: String): String {
|
||||||
val cookie = getCookie(url)
|
val cookie = getCookie(url)
|
||||||
val cookieMap = cookieToMap(cookie)
|
val sessionCookie = CookieManager.getSessionCookie(url)
|
||||||
|
val cookieMap = mergeCookiesToMap(cookie, sessionCookie)
|
||||||
return cookieMap[key] ?: ""
|
return cookieMap[key] ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +73,7 @@ object CookieStore : CookieManager {
|
|||||||
val domain = NetworkUtils.getSubDomain(url)
|
val domain = NetworkUtils.getSubDomain(url)
|
||||||
appDb.cookieDao.delete(domain)
|
appDb.cookieDao.delete(domain)
|
||||||
CacheManager.deleteMemory("${domain}_cookie")
|
CacheManager.deleteMemory("${domain}_cookie")
|
||||||
|
CacheManager.deleteMemory("${domain}_session_cookie")
|
||||||
android.webkit.CookieManager.getInstance().removeCookie(domain)
|
android.webkit.CookieManager.getInstance().removeCookie(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,9 +82,9 @@ object CookieStore : CookieManager {
|
|||||||
if (cookie.isBlank()) {
|
if (cookie.isBlank()) {
|
||||||
return cookieMap
|
return cookieMap
|
||||||
}
|
}
|
||||||
val pairArray = cookie.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
val pairArray = cookie.split(semicolonRegex).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
for (pair in pairArray) {
|
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) {
|
if (pairs.size == 1) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -91,7 +103,7 @@ object CookieStore : CookieManager {
|
|||||||
}
|
}
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
cookieMap.keys.forEachIndexed { index, key ->
|
cookieMap.keys.forEachIndexed { index, key ->
|
||||||
if (index > 0) builder.append(";")
|
if (index > 0) builder.append("; ")
|
||||||
builder.append(key).append("=").append(cookieMap[key])
|
builder.append(key).append("=").append(cookieMap[key])
|
||||||
}
|
}
|
||||||
return builder.toString()
|
return builder.toString()
|
||||||
|
@ -3,6 +3,7 @@ package io.legado.app.help.http
|
|||||||
import io.legado.app.constant.AppConst
|
import io.legado.app.constant.AppConst
|
||||||
import io.legado.app.help.CacheManager
|
import io.legado.app.help.CacheManager
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
|
import io.legado.app.help.http.CookieManager.cookieJarHeader
|
||||||
import io.legado.app.utils.NetworkUtils
|
import io.legado.app.utils.NetworkUtils
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
@ -48,7 +49,7 @@ val okHttpClient: OkHttpClient by lazy {
|
|||||||
.writeTimeout(15, TimeUnit.SECONDS)
|
.writeTimeout(15, TimeUnit.SECONDS)
|
||||||
.readTimeout(15, TimeUnit.SECONDS)
|
.readTimeout(15, TimeUnit.SECONDS)
|
||||||
.callTimeout(60, TimeUnit.SECONDS)
|
.callTimeout(60, TimeUnit.SECONDS)
|
||||||
.cookieJar(cookieJar = cookieJar)
|
//.cookieJar(cookieJar = cookieJar)
|
||||||
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory, SSLHelper.unsafeTrustManager)
|
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory, SSLHelper.unsafeTrustManager)
|
||||||
.retryOnConnectionFailure(true)
|
.retryOnConnectionFailure(true)
|
||||||
.hostnameVerifier(SSLHelper.unsafeHostnameVerifier)
|
.hostnameVerifier(SSLHelper.unsafeHostnameVerifier)
|
||||||
@ -66,8 +67,26 @@ val okHttpClient: OkHttpClient by lazy {
|
|||||||
builder.addHeader("Keep-Alive", "300")
|
builder.addHeader("Keep-Alive", "300")
|
||||||
builder.addHeader("Connection", "Keep-Alive")
|
builder.addHeader("Connection", "Keep-Alive")
|
||||||
builder.addHeader("Cache-Control", "no-cache")
|
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 (!AppConst.isPlayChannel && AppConfig.isCronet) {
|
||||||
if (Cronet.loader?.install() == true) {
|
if (Cronet.loader?.install() == true) {
|
||||||
Cronet.interceptor?.let {
|
Cronet.interceptor?.let {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package io.legado.app.help.http.api
|
package io.legado.app.help.http.api
|
||||||
|
|
||||||
interface CookieManager {
|
interface CookieManagerInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存cookie
|
* 保存cookie
|
@ -20,6 +20,7 @@ import io.legado.app.help.JsExtensions
|
|||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.glide.GlideHeaders
|
import io.legado.app.help.glide.GlideHeaders
|
||||||
import io.legado.app.help.http.*
|
import io.legado.app.help.http.*
|
||||||
|
import io.legado.app.help.http.CookieManager.mergeCookies
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@ -606,13 +607,13 @@ class AnalyzeUrl(
|
|||||||
CookieStore.getCookie(domain)
|
CookieStore.getCookie(domain)
|
||||||
}
|
}
|
||||||
if (cookie.isNotEmpty()) {
|
if (cookie.isNotEmpty()) {
|
||||||
val cookieMap = CookieStore.cookieToMap(cookie)
|
mergeCookies(cookie, headerMap["Cookie"])?.let {
|
||||||
val customCookieMap = CookieStore.cookieToMap(headerMap["Cookie"] ?: "")
|
|
||||||
cookieMap.putAll(customCookieMap)
|
|
||||||
CookieStore.mapToCookie(cookieMap)?.let {
|
|
||||||
headerMap.put("Cookie", it)
|
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.BuildConfig
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
|
import io.legado.app.constant.AppPattern.semicolonRegex
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
||||||
import io.legado.app.model.analyzeRule.CustomUrl
|
import io.legado.app.model.analyzeRule.CustomUrl
|
||||||
@ -102,7 +103,7 @@ object UrlUtil {
|
|||||||
val redirectUrl: String? = conn.getHeaderField("Location")
|
val redirectUrl: String? = conn.getHeaderField("Location")
|
||||||
|
|
||||||
return if (raw != null) {
|
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>()
|
val names = hashSetOf<String>()
|
||||||
fileNames.forEach {
|
fileNames.forEach {
|
||||||
val fileName = it.substringAfter("=")
|
val fileName = it.substringAfter("=")
|
||||||
|
Loading…
Reference in New Issue
Block a user