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