xref: /aosp_15_r20/external/okio/okio/src/jvmMain/kotlin/okio/JvmOkio.kt (revision f9742813c14b702d71392179818a9e591da8620c)
1 /*
2  * Copyright (C) 2014 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /** Essential APIs for working with Okio. */
18 @file:JvmMultifileClass
19 @file:JvmName("Okio")
20 
21 package okio
22 
23 import java.io.File
24 import java.io.FileNotFoundException
25 import java.io.FileOutputStream
26 import java.io.IOException
27 import java.io.InputStream
28 import java.io.OutputStream
29 import java.net.Socket
30 import java.net.SocketTimeoutException
31 import java.nio.file.Files
32 import java.nio.file.OpenOption
33 import java.nio.file.Path as NioPath
34 import java.security.MessageDigest
35 import java.util.logging.Level
36 import java.util.logging.Logger
37 import javax.crypto.Cipher
38 import javax.crypto.Mac
39 import okio.internal.ResourceFileSystem
40 
41 /** Returns a sink that writes to `out`. */
sinknull42 fun OutputStream.sink(): Sink = OutputStreamSink(this, Timeout())
43 
44 private class OutputStreamSink(
45   private val out: OutputStream,
46   private val timeout: Timeout,
47 ) : Sink {
48 
49   override fun write(source: Buffer, byteCount: Long) {
50     checkOffsetAndCount(source.size, 0, byteCount)
51     var remaining = byteCount
52     while (remaining > 0) {
53       timeout.throwIfReached()
54       val head = source.head!!
55       val toCopy = minOf(remaining, head.limit - head.pos).toInt()
56       out.write(head.data, head.pos, toCopy)
57 
58       head.pos += toCopy
59       remaining -= toCopy
60       source.size -= toCopy
61 
62       if (head.pos == head.limit) {
63         source.head = head.pop()
64         SegmentPool.recycle(head)
65       }
66     }
67   }
68 
69   override fun flush() = out.flush()
70 
71   override fun close() = out.close()
72 
73   override fun timeout() = timeout
74 
75   override fun toString() = "sink($out)"
76 }
77 
78 /** Returns a source that reads from `in`. */
InputStreamnull79 fun InputStream.source(): Source = InputStreamSource(this, Timeout())
80 
81 private open class InputStreamSource(
82   private val input: InputStream,
83   private val timeout: Timeout,
84 ) : Source {
85 
86   override fun read(sink: Buffer, byteCount: Long): Long {
87     if (byteCount == 0L) return 0L
88     require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
89     try {
90       timeout.throwIfReached()
91       val tail = sink.writableSegment(1)
92       val maxToCopy = minOf(byteCount, Segment.SIZE - tail.limit).toInt()
93       val bytesRead = input.read(tail.data, tail.limit, maxToCopy)
94       if (bytesRead == -1) {
95         if (tail.pos == tail.limit) {
96           // We allocated a tail segment, but didn't end up needing it. Recycle!
97           sink.head = tail.pop()
98           SegmentPool.recycle(tail)
99         }
100         return -1
101       }
102       tail.limit += bytesRead
103       sink.size += bytesRead
104       return bytesRead.toLong()
105     } catch (e: AssertionError) {
106       if (e.isAndroidGetsocknameError) throw IOException(e)
107       throw e
108     }
109   }
110 
111   override fun close() = input.close()
112 
113   override fun timeout() = timeout
114 
115   override fun toString() = "source($input)"
116 }
117 
118 /**
119  * Returns a sink that writes to `socket`. Prefer this over [sink]
120  * because this method honors timeouts. When the socket
121  * write times out, the socket is asynchronously closed by a watchdog thread.
122  */
123 @Throws(IOException::class)
sinknull124 fun Socket.sink(): Sink {
125   val timeout = SocketAsyncTimeout(this)
126   val sink = OutputStreamSink(getOutputStream(), timeout)
127   return timeout.sink(sink)
128 }
129 
130 /**
131  * Returns a source that reads from `socket`. Prefer this over [source]
132  * because this method honors timeouts. When the socket
133  * read times out, the socket is asynchronously closed by a watchdog thread.
134  */
135 @Throws(IOException::class)
sourcenull136 fun Socket.source(): Source {
137   val timeout = SocketAsyncTimeout(this)
138   val source = InputStreamSource(getInputStream(), timeout)
139   return timeout.source(source)
140 }
141 
142 private val logger = Logger.getLogger("okio.Okio")
143 
144 private class SocketAsyncTimeout(private val socket: Socket) : AsyncTimeout() {
newTimeoutExceptionnull145   override fun newTimeoutException(cause: IOException?): IOException {
146     val ioe = SocketTimeoutException("timeout")
147     if (cause != null) {
148       ioe.initCause(cause)
149     }
150     return ioe
151   }
152 
timedOutnull153   override fun timedOut() {
154     try {
155       socket.close()
156     } catch (e: Exception) {
157       logger.log(Level.WARNING, "Failed to close timed out socket $socket", e)
158     } catch (e: AssertionError) {
159       if (e.isAndroidGetsocknameError) {
160         // Catch this exception due to a Firmware issue up to android 4.2.2
161         // https://code.google.com/p/android/issues/detail?id=54072
162         logger.log(Level.WARNING, "Failed to close timed out socket $socket", e)
163       } else {
164         throw e
165       }
166     }
167   }
168 }
169 
170 /** Returns a sink that writes to `file`. */
171 @JvmOverloads
172 @Throws(FileNotFoundException::class)
sinknull173 fun File.sink(append: Boolean = false): Sink = FileOutputStream(this, append).sink()
174 
175 /** Returns a sink that writes to `file`. */
176 @Throws(FileNotFoundException::class)
177 fun File.appendingSink(): Sink = FileOutputStream(this, true).sink()
178 
179 /** Returns a source that reads from `file`. */
180 @Throws(FileNotFoundException::class)
181 fun File.source(): Source = InputStreamSource(inputStream(), Timeout.NONE)
182 
183 /** Returns a sink that writes to `path`. */
184 @Throws(IOException::class)
185 fun NioPath.sink(vararg options: OpenOption): Sink =
186   Files.newOutputStream(this, *options).sink()
187 
188 /** Returns a source that reads from `path`. */
189 @Throws(IOException::class)
190 fun NioPath.source(vararg options: OpenOption): Source =
191   Files.newInputStream(this, *options).source()
192 
193 /**
194  * Returns a sink that uses [cipher] to encrypt or decrypt [this].
195  *
196  * @throws IllegalArgumentException if [cipher] isn't a block cipher.
197  */
198 fun Sink.cipherSink(cipher: Cipher): CipherSink = CipherSink(this.buffer(), cipher)
199 
200 /**
201  * Returns a source that uses [cipher] to encrypt or decrypt [this].
202  *
203  * @throws IllegalArgumentException if [cipher] isn't a block cipher.
204  */
205 fun Source.cipherSource(cipher: Cipher): CipherSource = CipherSource(this.buffer(), cipher)
206 
207 /**
208  * Returns a sink that uses [mac] to hash [this].
209  */
210 fun Sink.hashingSink(mac: Mac): HashingSink = HashingSink(this, mac)
211 
212 /**
213  * Returns a source that uses [mac] to hash [this].
214  */
215 fun Source.hashingSource(mac: Mac): HashingSource = HashingSource(this, mac)
216 
217 /**
218  * Returns a sink that uses [digest] to hash [this].
219  */
220 fun Sink.hashingSink(digest: MessageDigest): HashingSink = HashingSink(this, digest)
221 
222 /**
223  * Returns a source that uses [digest] to hash [this].
224  */
225 fun Source.hashingSource(digest: MessageDigest): HashingSource = HashingSource(this, digest)
226 
227 @Throws(IOException::class)
228 fun FileSystem.openZip(zipPath: Path): FileSystem = okio.internal.openZip(zipPath, this)
229 
230 fun ClassLoader.asResourceFileSystem(): FileSystem = ResourceFileSystem(this, indexEagerly = true)
231 
232 /**
233  * Returns true if this error is due to a firmware bug fixed after Android 4.2.2.
234  * https://code.google.com/p/android/issues/detail?id=54072
235  */
236 internal val AssertionError.isAndroidGetsocknameError: Boolean get() {
237   return cause != null && message?.contains("getsockname failed") ?: false
238 }
239