diff --git a/app/src/app/java/io/legado/app/lib/cronet/AbsCallBack.kt b/app/src/app/java/io/legado/app/lib/cronet/AbsCallBack.kt index e2959fb9b..4ab10551d 100644 --- a/app/src/app/java/io/legado/app/lib/cronet/AbsCallBack.kt +++ b/app/src/app/java/io/legado/app/lib/cronet/AbsCallBack.kt @@ -2,6 +2,8 @@ package io.legado.app.lib.cronet import androidx.annotation.Keep 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.utils.DebugLog import io.legado.app.utils.asIOException @@ -11,6 +13,8 @@ import okhttp3.* import okhttp3.EventListener import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.ResponseBody.Companion.asResponseBody +import okhttp3.internal.http.HttpMethod +import okhttp3.internal.http.StatusLine import okio.Buffer import okio.Source import okio.Timeout @@ -29,7 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean @Keep abstract class AbsCallBack( - val originalRequest: Request, + var originalRequest: Request, val mCall: Call, private val eventListener: EventListener? = null, private val responseCallback: Callback? = null @@ -43,6 +47,16 @@ abstract class AbsCallBack( private val callbackResults = ArrayBlockingQueue(2) private val urlResponseInfoChain = arrayListOf() 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) @@ -80,15 +94,23 @@ abstract class AbsCallBack( urlResponseInfoChain.add(info) val client = okHttpClient if (originalRequest.url.isHttps && newLocationUrl.startsWith("http://") && client.followSslRedirects) { - request.followRedirect() + followRedirect = true } else if (!originalRequest.url.isHttps && newLocationUrl.startsWith("https://") && client.followSslRedirects) { - request.followRedirect() + followRedirect = true } else if (okHttpClient.followRedirects) { - request.followRedirect() - } else { - onError(IOException("Too many redirect")) - request.cancel() + followRedirect = true } + + 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() } - val responseBuilder: Response.Builder + val response: Response try { - responseBuilder = createResponse( - originalRequest, - info, - CronetBodySource() - ) + response = toResponse(originalRequest, info, urlResponseInfoChain, CronetBodySource()) } catch (e: IOException) { request.cancel() cancelJob?.cancel() onError(e) return } - val newRequest = originalRequest.newBuilder().url(info.url).build() - val response = responseBuilder - .request(newRequest) - .priorResponse(buildPriorResponse(originalRequest, urlResponseInfoChain, info.urlChain)) - .build() + + if (enableCookieJar) { + CookieManager.saveResponse(response) + } + mResponse = 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) { eventListener.responseHeadersEnd(mCall, response) eventListener.responseBodyStart(mCall) @@ -167,6 +186,16 @@ abstract class AbsCallBack( } 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) callbackResults.add(CallbackResult(CallbackStep.ON_CANCELED)) //DebugLog.i(javaClass.simpleName, "cancel[${info?.negotiatedProtocol}]${info?.url}") @@ -293,15 +322,12 @@ abstract class AbsCallBack( private fun buildPriorResponse( request: Request, redirectResponseInfos: List, - urlChain: List ): Response? { var priorResponse: Response? = null 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) { - 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(priorResponse) .build() @@ -336,6 +362,48 @@ abstract class AbsCallBack( return bodySource.buffer() .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, + 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 { diff --git a/app/src/app/java/io/legado/app/lib/cronet/CronetHelper.kt b/app/src/app/java/io/legado/app/lib/cronet/CronetHelper.kt index 47e68b08d..1f891fd78 100644 --- a/app/src/app/java/io/legado/app/lib/cronet/CronetHelper.kt +++ b/app/src/app/java/io/legado/app/lib/cronet/CronetHelper.kt @@ -6,6 +6,7 @@ package io.legado.app.lib.cronet import androidx.annotation.Keep import io.legado.app.constant.AppConst 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.utils.DebugLog import okhttp3.Headers @@ -80,6 +81,7 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? { setHttpMethod(request.method)//设置 allowDirectExecutor() headers.forEachIndexed { index, _ -> + if (headers.name(index) == cookieJarHeader) return@forEachIndexed addHeader(headers.name(index), headers.value(index)) } if (requestBody != null) { diff --git a/app/src/app/java/io/legado/app/lib/cronet/CronetInterceptor.kt b/app/src/app/java/io/legado/app/lib/cronet/CronetInterceptor.kt index 989f9d75e..0bf16a082 100644 --- a/app/src/app/java/io/legado/app/lib/cronet/CronetInterceptor.kt +++ b/app/src/app/java/io/legado/app/lib/cronet/CronetInterceptor.kt @@ -25,18 +25,11 @@ class CronetInterceptor(private val cookieJar: CookieJar) : Interceptor { //移除Keep-Alive,手动设置会导致400 BadRequest builder.removeHeader("Keep-Alive") 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() proceedWithCronet(newReq, chain.call())?.let { response -> //从Response 中保存Cookie到CookieJar - cookieJar.receiveHeaders(newReq.url, response.headers) + //cookieJar.receiveHeaders(newReq.url, response.headers) response } ?: chain.proceed(original) } catch (e: Exception) { diff --git a/app/src/main/java/io/legado/app/constant/AppPattern.kt b/app/src/main/java/io/legado/app/constant/AppPattern.kt index 5bc621f1f..b6263f5d9 100644 --- a/app/src/main/java/io/legado/app/constant/AppPattern.kt +++ b/app/src/main/java/io/legado/app/constant/AppPattern.kt @@ -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() } diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index f498b8ca4..cb445e9af 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -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 } diff --git a/app/src/main/java/io/legado/app/help/http/CookieManager.kt b/app/src/main/java/io/legado/app/help/http/CookieManager.kt new file mode 100644 index 000000000..9112a9d33 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/http/CookieManager.kt @@ -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 { + /** + * _session_cookie 会话期 cookie,应用重启后失效 + * _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? { + 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 { + 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.getString() = buildString { + this@getString.forEachIndexed { index, cookie -> + if (index > 0) append("; ") + append(cookie.name).append('=').append(cookie.value) + } + } + + private fun Map>.toHeaders(): Headers { + return Headers.Builder().apply { + this@toHeaders.forEach { (k, v) -> + v.forEach { + add(k, it) + } + } + }.build() + } + +} diff --git a/app/src/main/java/io/legado/app/help/http/CookieStore.kt b/app/src/main/java/io/legado/app/help/http/CookieStore.kt index 707c90293..5292298c3 100644 --- a/app/src/main/java/io/legado/app/help/http/CookieStore.kt +++ b/app/src/main/java/io/legado/app/help/http/CookieStore.kt @@ -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() diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt index b7fc67ed2..836a0738b 100644 --- a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -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 { diff --git a/app/src/main/java/io/legado/app/help/http/api/CookieManager.kt b/app/src/main/java/io/legado/app/help/http/api/CookieManagerInterface.kt similarity index 93% rename from app/src/main/java/io/legado/app/help/http/api/CookieManager.kt rename to app/src/main/java/io/legado/app/help/http/api/CookieManagerInterface.kt index 525aa712d..927ba4faf 100644 --- a/app/src/main/java/io/legado/app/help/http/api/CookieManager.kt +++ b/app/src/main/java/io/legado/app/help/http/api/CookieManagerInterface.kt @@ -1,6 +1,6 @@ package io.legado.app.help.http.api -interface CookieManager { +interface CookieManagerInterface { /** * 保存cookie diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index a20b6bacb..9659bc764 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -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" + } } /** diff --git a/app/src/main/java/io/legado/app/utils/UrlUtil.kt b/app/src/main/java/io/legado/app/utils/UrlUtil.kt index 7112e27a0..bc057b2e4 100644 --- a/app/src/main/java/io/legado/app/utils/UrlUtil.kt +++ b/app/src/main/java/io/legado/app/utils/UrlUtil.kt @@ -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() fileNames.forEach { val fileName = it.substringAfter("=")