This commit is contained in:
Horis 2023-05-13 23:43:58 +08:00
parent b38c1539c6
commit bb70568b3e
3 changed files with 1221 additions and 13 deletions

View File

@ -26,12 +26,15 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ThemeConfig.applyDayNight
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.http.Cronet
import io.legado.app.help.http.ObsoleteUrlFactory
import io.legado.app.help.http.okHttpClient
import io.legado.app.model.BookCover
import io.legado.app.utils.defaultSharedPreferences
import io.legado.app.utils.getPrefBoolean
import kotlinx.coroutines.launch
import splitties.init.appCtx
import splitties.systemservices.notificationManager
import java.net.URL
import java.util.concurrent.TimeUnit
class App : Application() {
@ -53,6 +56,7 @@ class App : Application() {
defaultSharedPreferences.registerOnSharedPreferenceChangeListener(AppConfig)
DefaultData.upVersion()
Coroutine.async {
URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient))
launch { installGmsTlsProvider(appCtx) }
//初始化封面
BookCover.toString()

View File

@ -14,6 +14,7 @@ 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.CookieManager.cookieJarHeader
import io.legado.app.help.http.CookieStore
import io.legado.app.help.http.SSLHelper
import io.legado.app.help.http.StrResponse
@ -202,7 +203,7 @@ interface JsExtensions : JsEncodeUtils {
val cachePath = CacheManager.get(key)
return if (
cachePath.isNullOrBlank() ||
!getFile(cachePath).exists()
!getFile(cachePath).exists()
) {
val path = downloadFile(urlStr)
log("首次下载 $urlStr >> $path")
@ -279,52 +280,55 @@ interface JsExtensions : JsEncodeUtils {
/**
* js实现重定向拦截,网络访问get
*/
@Suppress("UnnecessaryVariable")
fun get(urlStr: String, headers: Map<String, String>): Connection.Response {
val requestHeaders = if (getSource()?.enabledCookieJar == true) {
headers.toMutableMap().apply { put(cookieJarHeader, "1") }
} else headers
val response = Jsoup.connect(urlStr)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
.ignoreContentType(true)
.followRedirects(false)
.headers(headers)
.headers(requestHeaders)
.method(Connection.Method.GET)
.execute()
if (getSource()?.enabledCookieJar == true) {
CookieManager.saveResponse(response)
}
return response
}
/**
* js实现重定向拦截,网络访问head,不返回Response Body更省流量
*/
@Suppress("UnnecessaryVariable")
fun head(urlStr: String, headers: Map<String, String>): Connection.Response {
val requestHeaders = if (getSource()?.enabledCookieJar == true) {
headers.toMutableMap().apply { put(cookieJarHeader, "1") }
} else headers
val response = Jsoup.connect(urlStr)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
.ignoreContentType(true)
.followRedirects(false)
.headers(headers)
.headers(requestHeaders)
.method(Connection.Method.HEAD)
.execute()
if (getSource()?.enabledCookieJar == true) {
CookieManager.saveResponse(response)
}
return response
}
/**
* 网络访问post
*/
@Suppress("UnnecessaryVariable")
fun post(urlStr: String, body: String, headers: Map<String, String>): Connection.Response {
val requestHeaders = if (getSource()?.enabledCookieJar == true) {
headers.toMutableMap().apply { put(cookieJarHeader, "1") }
} else headers
val response = Jsoup.connect(urlStr)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
.ignoreContentType(true)
.followRedirects(false)
.requestBody(body)
.headers(headers)
.headers(requestHeaders)
.method(Connection.Method.POST)
.execute()
if (getSource()?.enabledCookieJar == true) {
CookieManager.saveResponse(response)
}
return response
}
@ -511,6 +515,7 @@ interface JsExtensions : JsEncodeUtils {
fun unzipFile(zipPath: String): String {
return unArchiveFile(zipPath)
}
/**
* js实现7Zip压缩文件解压
* @param zipPath 相对路径
@ -519,6 +524,7 @@ interface JsExtensions : JsEncodeUtils {
fun un7zFile(zipPath: String): String {
return unArchiveFile(zipPath)
}
/**
* js实现Rar压缩文件解压
* @param zipPath 相对路径
@ -527,6 +533,7 @@ interface JsExtensions : JsEncodeUtils {
fun unrarFile(zipPath: String): String {
return unArchiveFile(zipPath)
}
/**
* js实现压缩文件解压
* @param zipPath 相对路径
@ -730,6 +737,7 @@ interface JsExtensions : JsEncodeUtils {
}
return@runBlocking x
}
str.isContentScheme() -> Uri.parse(str).readBytes(appCtx)
str.startsWith("/storage") -> File(str).readBytes()
else -> base64DecodeToByteArray(str)

View File

@ -0,0 +1,1196 @@
package io.legado.app.help.http
import io.legado.app.help.http.CookieManager.cookieJarHeader
import io.legado.app.help.http.SSLHelper.unsafeTrustManager
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Dispatcher
import okhttp3.Handshake
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import okhttp3.internal.notifyAll
import okhttp3.internal.wait
import okio.Buffer
import okio.BufferedSink
import okio.Pipe
import okio.Timeout
import okio.buffer
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.io.InterruptedIOException
import java.io.OutputStream
import java.net.HttpURLConnection
import java.net.InetSocketAddress
import java.net.MalformedURLException
import java.net.ProtocolException
import java.net.Proxy
import java.net.SocketPermission
import java.net.SocketTimeoutException
import java.net.URL
import java.net.URLConnection
import java.net.URLStreamHandler
import java.net.URLStreamHandlerFactory
import java.security.AccessControlException
import java.security.Permission
import java.security.Principal
import java.security.cert.Certificate
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Collections
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.TreeMap
import java.util.concurrent.TimeUnit
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLSocketFactory
/**
* OkHttp 3.14 dropped support for the long-deprecated OkUrlFactory class, which allows you to use
* the HttpURLConnection API with OkHttp's implementation. This class does the same thing using only
* public APIs in OkHttp. It requires OkHttp 3.14 or newer.
*
*
* Rather than pasting this 1100 line gist into your source code, please upgrade to OkHttp's
* request/response API. Your code will be shorter, easier to read, and you'll be able to use
* interceptors.
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
class ObsoleteUrlFactory(private var client: OkHttpClient) : URLStreamHandlerFactory,
Cloneable {
fun client(): OkHttpClient {
return client
}
fun setClient(client: OkHttpClient): ObsoleteUrlFactory {
this.client = client
return this
}
/**
* Returns a copy of this stream handler factory that includes a shallow copy of the internal
* [HTTP client][OkHttpClient].
*/
public override fun clone(): ObsoleteUrlFactory {
return ObsoleteUrlFactory(client)
}
fun open(url: URL): HttpURLConnection {
return open(url, client.proxy)
}
fun open(url: URL, proxy: Proxy?): HttpURLConnection {
val protocol = url.protocol
val copy = client.newBuilder()
.proxy(proxy)
.build()
if (protocol == "http") return OkHttpURLConnection(url, copy)
if (protocol == "https") return OkHttpsURLConnection(url, copy)
throw IllegalArgumentException("Unexpected protocol: $protocol")
}
/**
* Creates a URLStreamHandler as a [java.net.URL.setURLStreamHandlerFactory].
*
*
* This code configures OkHttp to handle all HTTP and HTTPS connections
* created with [java.net.URL.openConnection]: <pre> `OkHttpClient okHttpClient = new OkHttpClient();
* URL.setURLStreamHandlerFactory(new ObsoleteUrlFactory(okHttpClient));
`</pre> *
*/
override fun createURLStreamHandler(protocol: String): URLStreamHandler? {
return if (protocol != "http" && protocol != "https") null else object :
URLStreamHandler() {
override fun openConnection(url: URL): URLConnection {
return open(url)
}
override fun openConnection(url: URL, proxy: Proxy): URLConnection {
return open(url, proxy)
}
override fun getDefaultPort(): Int {
if ((protocol == "http")) return 80
if ((protocol == "https")) return 443
throw AssertionError()
}
}
}
internal class OkHttpURLConnection(
url: URL?, // These fields are confined to the application thread that uses HttpURLConnection.
var client: OkHttpClient
) :
HttpURLConnection(url), Callback {
private val networkInterceptor: NetworkInterceptor = NetworkInterceptor()
var requestHeaders: Headers.Builder = Headers.Builder()
var responseHeaders: Headers? = null
var executed = false
var call: Call? = null
/** Like the superclass field of the same name, but a long and available on all platforms. */
//var fixedContentLength = -1L
// These fields are guarded by lock.
private val lock = Any()
private var response: Response? = null
private var callFailure: Throwable? = null
var networkResponse: Response? = null
var connectPending = true
var proxy: Proxy? = null
var handshake: Handshake? = null
@Throws(IOException::class)
override fun connect() {
if (executed) return
val call = buildCall()
executed = true
call.enqueue(this)
synchronized(lock) {
try {
while (connectPending && (response == null) && (callFailure == null)) {
lock.wait() // Wait 'til the network interceptor is reached or the call fails.
}
if (callFailure != null) {
throw propagate(callFailure)
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt() // Retain interrupted status.
throw InterruptedIOException()
}
}
}
override fun disconnect() {
// Calling disconnect() before a connection exists should have no effect.
if (call == null) return
networkInterceptor.proceed() // Unblock any waiting async thread.
call!!.cancel()
}
override fun getErrorStream(): InputStream? {
return try {
val response = getResponse(true)
if (hasBody(response) && response.code >= HTTP_BAD_REQUEST) {
response.body!!.byteStream()
} else null
} catch (e: IOException) {
null
}
}
@get:Throws(IOException::class)
val headers: Headers
get() {
if (responseHeaders == null) {
val response = getResponse(true)
val headers = response.headers
responseHeaders = headers.newBuilder()
.add(SELECTED_PROTOCOL, response.protocol.toString())
.add(RESPONSE_SOURCE, responseSourceHeader(response))
.build()
}
return responseHeaders as Headers
}
override fun getHeaderField(position: Int): String? {
return try {
val headers = headers
if (position < 0 || position >= headers.size) null else headers.value(
position
)
} catch (e: IOException) {
null
}
}
override fun getHeaderField(fieldName: String?): String? {
return try {
if (fieldName == null) statusLineToString(getResponse(true)) else headers[fieldName]
} catch (e: IOException) {
null
}
}
override fun getHeaderFieldKey(position: Int): String? {
return try {
val headers = headers
if (position < 0 || position >= headers.size) null else headers.name(position)
} catch (e: IOException) {
null
}
}
override fun getHeaderFields(): Map<String, List<String>> {
return try {
toMultimap(headers, statusLineToString(getResponse(true)))
} catch (e: IOException) {
emptyMap()
}
}
override fun getRequestProperties(): Map<String, List<String>> {
if (connected) {
throw IllegalStateException(
"Cannot access request header fields after connection is set"
)
}
return toMultimap(requestHeaders.build(), null)
}
@Throws(IOException::class)
override fun getInputStream(): InputStream {
if (!doInput) {
throw ProtocolException("This protocol does not support input")
}
val response = getResponse(false)
if (response.code >= HTTP_BAD_REQUEST) throw FileNotFoundException(url.toString())
return response.body!!.byteStream()
}
@Throws(IOException::class)
override fun getOutputStream(): OutputStream {
val requestBody = buildCall().request().body as OutputStreamRequestBody?
?: throw ProtocolException("method does not support a request body: $method")
if (requestBody is StreamedRequestBody) {
connect()
networkInterceptor.proceed()
}
if (requestBody.closed) {
throw ProtocolException("cannot write request body after response has been read")
}
return requestBody.outputStream!!
}
override fun getPermission(): Permission {
val url = getURL()
var hostname = url.host
var hostPort = if (url.port != -1) url.port else HttpUrl.defaultPort(url.protocol)
if (usingProxy()) {
val proxyAddress = client.proxy!!.address() as InetSocketAddress
hostname = proxyAddress.hostName
hostPort = proxyAddress.port
}
return SocketPermission("$hostname:$hostPort", "connect, resolve")
}
override fun getRequestProperty(field: String?): String? {
return if (field == null) null else requestHeaders[field]
}
override fun setConnectTimeout(timeoutMillis: Int) {
client = client.newBuilder()
.connectTimeout(timeoutMillis.toLong(), TimeUnit.MILLISECONDS)
.build()
}
override fun setInstanceFollowRedirects(followRedirects: Boolean) {
client = client.newBuilder()
.followRedirects(followRedirects)
.build()
}
override fun getInstanceFollowRedirects(): Boolean {
return client.followRedirects
}
override fun getConnectTimeout(): Int {
return client.connectTimeoutMillis
}
override fun setReadTimeout(timeoutMillis: Int) {
client = client.newBuilder()
.readTimeout(timeoutMillis.toLong(), TimeUnit.MILLISECONDS)
.build()
}
override fun getReadTimeout(): Int {
return client.readTimeoutMillis
}
@Throws(IOException::class)
private fun buildCall(): Call {
if (call != null) {
return call as Call
}
connected = true
if (doOutput) {
if (method == "GET") {
method = "POST"
} else if (!permitsRequestBody(method)) {
throw ProtocolException("$method does not support writing")
}
}
if (requestHeaders["User-Agent"] == null) {
requestHeaders.add("User-Agent", defaultUserAgent())
}
var requestBody: OutputStreamRequestBody? = null
if (permitsRequestBody(method)) {
var contentType: String? = requestHeaders["Content-Type"]
if (contentType == null) {
contentType = "application/x-www-form-urlencoded"
requestHeaders.add("Content-Type", contentType)
}
val stream = fixedContentLength != -1 || chunkLength > 0
var contentLength = -1L
val contentLengthString: String? = requestHeaders["Content-Length"]
if (fixedContentLength != -1) {
contentLength = fixedContentLength.toLong()
} else if (contentLengthString != null) {
contentLength = contentLengthString.toLong()
}
requestBody =
if (stream) StreamedRequestBody(contentLength) else BufferedRequestBody(
contentLength
)
requestBody.timeout!!.timeout(
client.writeTimeoutMillis.toLong(),
TimeUnit.MILLISECONDS
)
}
val url: HttpUrl
try {
url = getURL().toString().toHttpUrl()
} catch (e: IllegalArgumentException) {
val malformedUrl = MalformedURLException()
malformedUrl.initCause(e)
throw malformedUrl
}
val request: Request = Request.Builder()
.url(url)
.headers(requestHeaders.build())
.method(method, requestBody)
.build()
val clientBuilder: OkHttpClient.Builder = client.newBuilder()
clientBuilder.interceptors().clear()
clientBuilder.interceptors().add(UnexpectedException.INTERCEPTOR)
clientBuilder.addInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder().removeHeader(cookieJarHeader).build()
}
clientBuilder.networkInterceptors().clear()
clientBuilder.networkInterceptors().add(networkInterceptor)
clientBuilder.addNetworkInterceptor { chain ->
var request1 = chain.request()
val enableCookieJar = request1.header(cookieJarHeader) != null
if (enableCookieJar) {
val requestBuilder = request1.newBuilder()
requestBuilder.removeHeader(cookieJarHeader)
request1 = CookieManager.loadRequest(requestBuilder.build())
}
var networkResponse = chain.proceed(request1)
if (enableCookieJar) {
CookieManager.saveResponse(networkResponse)
networkResponse = networkResponse.newBuilder()
.header(cookieJarHeader, "1")
.build()
}
networkResponse
}
// Use a separate dispatcher so that limits aren't impacted. But use the same executor service!
clientBuilder.dispatcher(Dispatcher(client.dispatcher.executorService))
// If we're currently not using caches, make sure the engine's client doesn't have one.
if (!getUseCaches()) {
clientBuilder.cache(null)
}
return clientBuilder.build().newCall(request).also { call = it }
}
@Throws(IOException::class)
private fun getResponse(networkResponseOnError: Boolean): Response {
synchronized(lock) {
if (response != null) return response as Response
if (callFailure != null) {
if (networkResponseOnError && networkResponse != null) return networkResponse as Response
throw propagate(callFailure)
}
}
val call = buildCall()
networkInterceptor.proceed()
val requestBody = call.request().body as OutputStreamRequestBody?
if (requestBody != null) requestBody.outputStream!!.close()
if (executed) {
synchronized(lock) {
try {
while (response == null && callFailure == null) {
lock.wait() // Wait until the response is returned or the call fails.
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt() // Retain interrupted status.
throw InterruptedIOException()
}
}
} else {
executed = true
try {
onResponse(call, call.execute())
} catch (e: IOException) {
onFailure(call, e)
}
}
synchronized(lock) {
if (callFailure != null) throw propagate(callFailure)
if (response != null) return response as Response
}
throw AssertionError()
}
override fun usingProxy(): Boolean {
if (proxy != null) return true
val clientProxy = client.proxy
return clientProxy != null && clientProxy.type() != Proxy.Type.DIRECT
}
@Throws(IOException::class)
override fun getResponseMessage(): String {
return getResponse(true).message
}
@Throws(IOException::class)
override fun getResponseCode(): Int {
return getResponse(true).code
}
override fun setRequestProperty(field: String?, newValue: String?) {
if (connected) {
throw IllegalStateException("Cannot set request property after connection is made")
}
if (field == null) {
throw NullPointerException("field == null")
}
if (newValue == null) {
return
}
requestHeaders[field] = newValue
}
override fun setIfModifiedSince(newValue: Long) {
super.setIfModifiedSince(newValue)
if (ifModifiedSince != 0L) {
requestHeaders["If-Modified-Since"] = format(Date(ifModifiedSince))
} else {
requestHeaders.removeAll("If-Modified-Since")
}
}
override fun addRequestProperty(field: String?, value: String?) {
if (connected) {
throw IllegalStateException("Cannot add request property after connection is made")
}
if (field == null) {
throw NullPointerException("field == null")
}
if (value == null) {
return
}
requestHeaders.add(field, value)
}
@Throws(ProtocolException::class)
override fun setRequestMethod(method: String) {
if (!METHODS.contains(method)) {
throw ProtocolException("Expected one of $METHODS but was $method")
}
this.method = method
}
override fun setFixedLengthStreamingMode(contentLength: Int) {
setFixedLengthStreamingMode(contentLength.toLong())
}
override fun setFixedLengthStreamingMode(contentLength: Long) {
if (super.connected) throw IllegalStateException("Already connected")
if (chunkLength > 0) throw IllegalStateException("Already in chunked mode")
if (contentLength < 0) throw IllegalArgumentException("contentLength < 0")
this.fixedContentLength = contentLength.toInt()
super.fixedContentLength = contentLength.toInt().coerceAtMost(Int.MAX_VALUE)
}
override fun onFailure(call: Call, e: IOException) {
synchronized(lock) {
callFailure = if ((e is UnexpectedException)) e.cause else e
lock.notifyAll()
}
}
override fun onResponse(call: Call, response: Response) {
synchronized(lock) {
this.response = response
handshake = response.handshake
url = response.request.url.toUrl()
lock.notifyAll()
}
}
internal inner class NetworkInterceptor : Interceptor {
// Guarded by HttpUrlConnection.this.
private var proceed = false
fun proceed() {
synchronized(lock) {
proceed = true
lock.notifyAll()
}
}
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var request: Request = chain.request()
synchronized(lock) {
connectPending = false
proxy = chain.connection()!!.route().proxy
handshake = chain.connection()!!.handshake()
lock.notifyAll()
try {
while (!proceed) {
lock.wait() // Wait until proceed() is called.
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt() // Retain interrupted status.
throw InterruptedIOException()
}
}
// Try to lock in the Content-Length before transmitting the request body.
if (request.body is OutputStreamRequestBody) {
val requestBody = request.body as OutputStreamRequestBody?
request = requestBody!!.prepareToSendRequest(request)
}
val response: Response = chain.proceed(request)
synchronized(lock) {
networkResponse = response
url = response.request.url.toUrl()
}
return response
}
}
}
internal abstract class OutputStreamRequestBody : RequestBody() {
var timeout: Timeout? = null
var expectedContentLength: Long = 0
var outputStream: OutputStream? = null
var closed = false
fun initOutputStream(sink: BufferedSink, expectedContentLength: Long) {
timeout = sink.timeout()
this.expectedContentLength = expectedContentLength
// An output stream that writes to sink. If expectedContentLength is not -1, then this expects
// exactly that many bytes to be written.
outputStream = object : OutputStream() {
private var bytesReceived: Long = 0
@Throws(IOException::class)
override fun write(b: Int) {
write(byteArrayOf(b.toByte()), 0, 1)
}
@Throws(IOException::class)
override fun write(source: ByteArray, offset: Int, byteCount: Int) {
if (closed) throw IOException("closed") // Not IllegalStateException!
if (expectedContentLength != -1L && bytesReceived + byteCount > expectedContentLength) {
throw ProtocolException(
"expected " + expectedContentLength
+ " bytes but received " + bytesReceived + byteCount
)
}
bytesReceived += byteCount.toLong()
try {
sink.write(source, offset, byteCount)
} catch (e: InterruptedIOException) {
throw SocketTimeoutException(e.message)
}
}
@Throws(IOException::class)
override fun flush() {
if (closed) return // Weird, but consistent with historical behavior.
sink.flush()
}
@Throws(IOException::class)
override fun close() {
closed = true
if (expectedContentLength != -1L && bytesReceived < expectedContentLength) {
throw ProtocolException(
("expected " + expectedContentLength
+ " bytes but received " + bytesReceived)
)
}
sink.close()
}
}
}
override fun contentLength(): Long {
return expectedContentLength
}
override fun contentType(): MediaType? {
return null // Let the caller provide this in a regular header.
}
@Throws(IOException::class)
open fun prepareToSendRequest(request: Request): Request {
return request
}
}
@Suppress("MemberVisibilityCanBePrivate")
internal class BufferedRequestBody(expectedContentLength: Long) :
OutputStreamRequestBody() {
val buffer = Buffer()
var contentLength = -1L
init {
initOutputStream(buffer, expectedContentLength)
}
override fun contentLength(): Long {
return contentLength
}
@Throws(IOException::class)
override fun prepareToSendRequest(request: Request): Request {
if (request.header("Content-Length") != null) return request
outputStream!!.close()
contentLength = buffer.size
return request.newBuilder()
.removeHeader("Transfer-Encoding")
.header("Content-Length", buffer.size.toString())
.build()
}
override fun writeTo(sink: BufferedSink) {
buffer.copyTo(sink.buffer, 0, buffer.size)
}
}
internal class StreamedRequestBody(expectedContentLength: Long) :
OutputStreamRequestBody() {
private val pipe = Pipe(8192)
init {
initOutputStream(pipe.sink.buffer(), expectedContentLength)
}
override fun isOneShot(): Boolean {
return true
}
@Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
val buffer = Buffer()
while (pipe.source.read(buffer, 8192) != -1L) {
sink.write(buffer, buffer.size)
}
}
}
internal abstract class DelegatingHttpsURLConnection(private val delegate: HttpURLConnection) :
HttpsURLConnection(delegate.url) {
protected abstract fun handshake(): Handshake?
abstract override fun setHostnameVerifier(hostnameVerifier: HostnameVerifier)
abstract override fun getHostnameVerifier(): HostnameVerifier
abstract override fun setSSLSocketFactory(sslSocketFactory: SSLSocketFactory?)
abstract override fun getSSLSocketFactory(): SSLSocketFactory
override fun getCipherSuite(): String? {
val handshake = handshake()
return handshake?.cipherSuite?.javaName
}
override fun getLocalCertificates(): Array<Certificate>? {
val handshake = handshake() ?: return null
val result = handshake.localCertificates
return if (result.isNotEmpty()) result.toTypedArray() else null
}
override fun getServerCertificates(): Array<Certificate>? {
val handshake = handshake() ?: return null
val result = handshake.peerCertificates
return if (result.isNotEmpty()) result.toTypedArray() else null
}
override fun getPeerPrincipal(): Principal? {
return handshake()?.peerPrincipal
}
override fun getLocalPrincipal(): Principal? {
return handshake()?.localPrincipal
}
@Throws(IOException::class)
override fun connect() {
connected = true
delegate.connect()
}
override fun disconnect() {
delegate.disconnect()
}
override fun getErrorStream(): InputStream? {
return delegate.errorStream
}
override fun getRequestMethod(): String {
return delegate.requestMethod
}
@Throws(IOException::class)
override fun getResponseCode(): Int {
return delegate.responseCode
}
@Throws(IOException::class)
override fun getResponseMessage(): String? {
return delegate.responseMessage
}
@Throws(ProtocolException::class)
override fun setRequestMethod(method: String) {
delegate.requestMethod = method
}
override fun usingProxy(): Boolean {
return delegate.usingProxy()
}
override fun getInstanceFollowRedirects(): Boolean {
return delegate.instanceFollowRedirects
}
override fun setInstanceFollowRedirects(followRedirects: Boolean) {
delegate.instanceFollowRedirects = followRedirects
}
override fun getAllowUserInteraction(): Boolean {
return delegate.allowUserInteraction
}
@Throws(IOException::class)
override fun getContent(): Any {
return delegate.content
}
@Throws(IOException::class)
override fun getContent(types: Array<Class<*>?>?): Any? {
return delegate.getContent(types)
}
override fun getContentEncoding(): String? {
return delegate.contentEncoding
}
override fun getContentLength(): Int {
return delegate.contentLength
}
// Should only be invoked on Java 8+ or Android API 24+.
override fun getContentLengthLong(): Long {
return delegate.contentLengthLong
}
override fun getContentType(): String? {
return delegate.contentType
}
override fun getDate(): Long {
return delegate.date
}
override fun getDefaultUseCaches(): Boolean {
return delegate.defaultUseCaches
}
override fun getDoInput(): Boolean {
return delegate.doInput
}
override fun getDoOutput(): Boolean {
return delegate.doOutput
}
override fun getExpiration(): Long {
return delegate.expiration
}
override fun getHeaderField(pos: Int): String? {
return delegate.getHeaderField(pos)
}
override fun getHeaderFields(): Map<String, List<String>> {
return delegate.headerFields
}
override fun getRequestProperties(): Map<String, List<String>> {
return delegate.requestProperties
}
override fun addRequestProperty(field: String, newValue: String) {
delegate.addRequestProperty(field, newValue)
}
override fun getHeaderField(key: String): String? {
return delegate.getHeaderField(key)
}
// Should only be invoked on Java 8+ or Android API 24+.
override fun getHeaderFieldLong(field: String, defaultValue: Long): Long {
return delegate.getHeaderFieldLong(field, defaultValue)
}
override fun getHeaderFieldDate(field: String, defaultValue: Long): Long {
return delegate.getHeaderFieldDate(field, defaultValue)
}
override fun getHeaderFieldInt(field: String, defaultValue: Int): Int {
return delegate.getHeaderFieldInt(field, defaultValue)
}
override fun getHeaderFieldKey(position: Int): String? {
return delegate.getHeaderFieldKey(position)
}
override fun getIfModifiedSince(): Long {
return delegate.ifModifiedSince
}
@Throws(IOException::class)
override fun getInputStream(): InputStream {
return delegate.inputStream
}
override fun getLastModified(): Long {
return delegate.lastModified
}
@Throws(IOException::class)
override fun getOutputStream(): OutputStream {
return delegate.outputStream
}
@Throws(IOException::class)
override fun getPermission(): Permission {
return delegate.permission
}
override fun getRequestProperty(field: String): String? {
return delegate.getRequestProperty(field)
}
override fun getURL(): URL {
return delegate.url
}
override fun getUseCaches(): Boolean {
return delegate.useCaches
}
override fun setAllowUserInteraction(newValue: Boolean) {
delegate.allowUserInteraction = newValue
}
override fun setDefaultUseCaches(newValue: Boolean) {
delegate.defaultUseCaches = newValue
}
override fun setDoInput(newValue: Boolean) {
delegate.doInput = newValue
}
override fun setDoOutput(newValue: Boolean) {
delegate.doOutput = newValue
}
// Should only be invoked on Java 8+ or Android API 24+.
override fun setFixedLengthStreamingMode(contentLength: Long) {
delegate.setFixedLengthStreamingMode(contentLength)
}
override fun setIfModifiedSince(newValue: Long) {
delegate.ifModifiedSince = newValue
}
override fun setRequestProperty(field: String, newValue: String) {
delegate.setRequestProperty(field, newValue)
}
override fun setUseCaches(newValue: Boolean) {
delegate.useCaches = newValue
}
override fun setConnectTimeout(timeoutMillis: Int) {
delegate.connectTimeout = timeoutMillis
}
override fun getConnectTimeout(): Int {
return delegate.connectTimeout
}
override fun setReadTimeout(timeoutMillis: Int) {
delegate.readTimeout = timeoutMillis
}
override fun getReadTimeout(): Int {
return delegate.readTimeout
}
override fun toString(): String {
return delegate.toString()
}
override fun setFixedLengthStreamingMode(contentLength: Int) {
delegate.setFixedLengthStreamingMode(contentLength)
}
override fun setChunkedStreamingMode(chunkLength: Int) {
delegate.setChunkedStreamingMode(chunkLength)
}
}
internal class OkHttpsURLConnection(private val delegate: OkHttpURLConnection) :
DelegatingHttpsURLConnection(delegate) {
constructor(url: URL?, client: OkHttpClient) : this(OkHttpURLConnection(url, client))
override fun handshake(): Handshake? {
if (delegate.call == null) {
throw IllegalStateException("Connection has not yet been established")
}
return delegate.handshake
}
override fun setHostnameVerifier(hostnameVerifier: HostnameVerifier) {
delegate.client = delegate.client.newBuilder()
.hostnameVerifier(hostnameVerifier)
.build()
}
override fun getHostnameVerifier(): HostnameVerifier {
return delegate.client.hostnameVerifier
}
override fun setSSLSocketFactory(sslSocketFactory: SSLSocketFactory?) {
if (sslSocketFactory == null) {
throw IllegalArgumentException("sslSocketFactory == null")
}
// This fails in JDK 9 because OkHttp is unable to extract the trust manager.
delegate.client = delegate.client.newBuilder()
.sslSocketFactory(sslSocketFactory, unsafeTrustManager)
.build()
}
override fun getSSLSocketFactory(): SSLSocketFactory {
return delegate.client.sslSocketFactory
}
}
internal class UnexpectedException(cause: Throwable?) : IOException(cause) {
companion object {
val INTERCEPTOR = Interceptor { chain: Interceptor.Chain ->
try {
return@Interceptor chain.proceed(chain.request())
} catch (e: Error) {
throw UnexpectedException(e)
} catch (e: RuntimeException) {
throw UnexpectedException(e)
}
}
}
}
companion object {
const val SELECTED_PROTOCOL = "ObsoleteUrlFactory-Selected-Protocol"
const val RESPONSE_SOURCE = "ObsoleteUrlFactory-Response-Source"
val METHODS: Set<String> = LinkedHashSet(
listOf("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "PATCH")
)
val UTC: TimeZone = TimeZone.getTimeZone("GMT")
const val HTTP_CONTINUE = 100
val STANDARD_DATE_FORMAT: ThreadLocal<DateFormat> = ThreadLocal.withInitial {
// Date format specified by RFC 7231 section 7.1.1.1.
val rfc1123: DateFormat = SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss 'GMT'",
Locale.US
)
rfc1123.isLenient = false
rfc1123.timeZone = UTC
rfc1123
}
private val FIELD_NAME_COMPARATOR =
java.util.Comparator { a: String?, b: String? ->
// @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
if (a === b) {
return@Comparator 0
} else if (a == null) {
return@Comparator -1
} else if (b == null) {
return@Comparator 1
} else {
return@Comparator java.lang.String.CASE_INSENSITIVE_ORDER.compare(a, b)
}
}
@Suppress(
"RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS",
"NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS"
)
fun format(value: Date?): String {
return STANDARD_DATE_FORMAT.get().format(value)
}
fun permitsRequestBody(method: String): Boolean {
return !((method == "GET") || (method == "HEAD"))
}
/** Returns true if the response must have a (possibly 0-length) body. See RFC 7231. */
fun hasBody(response: Response): Boolean {
// HEAD requests never yield a body regardless of the response headers.
if ((response.request.method == "HEAD")) {
return false
}
val responseCode = response.code
if (((responseCode < HTTP_CONTINUE || responseCode >= 200)
&& (responseCode != HttpURLConnection.HTTP_NO_CONTENT
) && (responseCode != HttpURLConnection.HTTP_NOT_MODIFIED))
) {
return true
}
// If the Content-Length or Transfer-Encoding headers disagree with the response code, the
// response is malformed. For best compatibility, we honor the headers.
return (contentLength(response.headers) != -1L
|| "chunked".equals(
response.header("Transfer-Encoding"),
ignoreCase = true
))
}
fun contentLength(headers: Headers): Long {
val s = headers["Content-Length"] ?: return -1
return try {
s.toLong()
} catch (e: NumberFormatException) {
-1
}
}
fun responseSourceHeader(response: Response): String {
if (response.networkResponse == null) {
return if (response.cacheResponse == null) "NONE" else "CACHE " + response.code
}
return if (response.cacheResponse == null) "NETWORK " + response.code else "CONDITIONAL_CACHE " + response.networkResponse!!.code
}
fun statusLineToString(response: Response): String {
return ((if (response.protocol == Protocol.HTTP_1_0) "HTTP/1.0" else "HTTP/1.1")
+ ' ' + response.code
+ ' ' + response.message)
}
fun toHumanReadableAscii(s: String): String {
var i = 0
val length = s.length
var c: Int
while (i < length) {
c = s.codePointAt(i)
if (c > '\u001f'.code && c < '\u007f'.code) {
i += Character.charCount(c)
continue
}
val buffer = Buffer()
buffer.writeUtf8(s, 0, i)
buffer.writeUtf8CodePoint('?'.code)
var j = i + Character.charCount(c)
while (j < length) {
c = s.codePointAt(j)
buffer.writeUtf8CodePoint((if (c > '\u001f'.code && c < '\u007f'.code) c else '?') as Int)
j += Character.charCount(c)
}
return buffer.readUtf8()
}
return s
}
fun toMultimap(headers: Headers, valueForNullKey: String?): Map<String, List<String>> {
val result: MutableMap<String?, List<String>> = TreeMap(FIELD_NAME_COMPARATOR)
var i = 0
val size = headers.size
while (i < size) {
val fieldName = headers.name(i)
val value = headers.value(i)
val allValues: MutableList<String> = ArrayList()
val otherValues = result[fieldName]
if (otherValues != null) {
allValues.addAll(otherValues)
}
allValues.add(value)
result[fieldName] = Collections.unmodifiableList(allValues)
i++
}
if (valueForNullKey != null) {
result[null] = Collections.unmodifiableList(listOf(valueForNullKey))
}
return Collections.unmodifiableMap(result)
}
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
fun getSystemProperty(key: String?, defaultValue: String?): String? {
val value: String?
try {
value = System.getProperty(key)
} catch (ex: AccessControlException) {
return defaultValue
}
return value ?: defaultValue
}
fun defaultUserAgent(): String {
val agent = getSystemProperty("http.agent", null)
return if (agent != null) toHumanReadableAscii(agent) else "ObsoleteUrlFactory"
}
@Throws(IOException::class)
fun propagate(throwable: Throwable?): IOException {
if (throwable is IOException) throw throwable
if (throwable is Error) throw throwable
if (throwable is RuntimeException) throw throwable
throw AssertionError()
}
@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
val okHttpClient = OkHttpClient()
URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient))
val url = URL("https://publicobject.com/helloworld.txt")
val urlConnection = url.openConnection() as HttpURLConnection
BufferedReader(
InputStreamReader(urlConnection.inputStream)
).use { reader ->
var line: String?
while ((reader.readLine().also { line = it }) != null) {
println(line)
}
}
}
}
}