| /* |
| * Copyright (C) 2014 Square, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** Essential APIs for working with Okio. */ |
| @file:JvmMultifileClass |
| @file:JvmName("Okio") |
| |
| package okio |
| |
| import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement |
| import java.io.File |
| import java.io.FileNotFoundException |
| import java.io.FileOutputStream |
| import java.io.IOException |
| import java.io.InputStream |
| import java.io.OutputStream |
| import java.net.Socket |
| import java.net.SocketTimeoutException |
| import java.nio.file.Files |
| import java.nio.file.OpenOption |
| import java.nio.file.Path |
| import java.security.MessageDigest |
| import java.util.logging.Level |
| import java.util.logging.Logger |
| import javax.crypto.Cipher |
| import javax.crypto.Mac |
| |
| /** Returns a sink that writes to `out`. */ |
| fun OutputStream.sink(): Sink = OutputStreamSink(this, Timeout()) |
| |
| private class OutputStreamSink( |
| private val out: OutputStream, |
| private val timeout: Timeout |
| ) : Sink { |
| |
| override fun write(source: Buffer, byteCount: Long) { |
| checkOffsetAndCount(source.size, 0, byteCount) |
| var remaining = byteCount |
| while (remaining > 0) { |
| timeout.throwIfReached() |
| val head = source.head!! |
| val toCopy = minOf(remaining, head.limit - head.pos).toInt() |
| out.write(head.data, head.pos, toCopy) |
| |
| head.pos += toCopy |
| remaining -= toCopy |
| source.size -= toCopy |
| |
| if (head.pos == head.limit) { |
| source.head = head.pop() |
| SegmentPool.recycle(head) |
| } |
| } |
| } |
| |
| override fun flush() = out.flush() |
| |
| override fun close() = out.close() |
| |
| override fun timeout() = timeout |
| |
| override fun toString() = "sink($out)" |
| } |
| |
| /** Returns a source that reads from `in`. */ |
| fun InputStream.source(): Source = InputStreamSource(this, Timeout()) |
| |
| private class InputStreamSource( |
| private val input: InputStream, |
| private val timeout: Timeout |
| ) : Source { |
| |
| override fun read(sink: Buffer, byteCount: Long): Long { |
| if (byteCount == 0L) return 0L |
| require(byteCount >= 0L) { "byteCount < 0: $byteCount" } |
| try { |
| timeout.throwIfReached() |
| val tail = sink.writableSegment(1) |
| val maxToCopy = minOf(byteCount, Segment.SIZE - tail.limit).toInt() |
| val bytesRead = input.read(tail.data, tail.limit, maxToCopy) |
| if (bytesRead == -1) { |
| if (tail.pos == tail.limit) { |
| // We allocated a tail segment, but didn't end up needing it. Recycle! |
| sink.head = tail.pop() |
| SegmentPool.recycle(tail) |
| } |
| return -1 |
| } |
| tail.limit += bytesRead |
| sink.size += bytesRead |
| return bytesRead.toLong() |
| } catch (e: AssertionError) { |
| if (e.isAndroidGetsocknameError) throw IOException(e) |
| throw e |
| } |
| } |
| |
| override fun close() = input.close() |
| |
| override fun timeout() = timeout |
| |
| override fun toString() = "source($input)" |
| } |
| |
| /** |
| * Returns a sink that writes to `socket`. Prefer this over [sink] |
| * because this method honors timeouts. When the socket |
| * write times out, the socket is asynchronously closed by a watchdog thread. |
| */ |
| @Throws(IOException::class) |
| fun Socket.sink(): Sink { |
| val timeout = SocketAsyncTimeout(this) |
| val sink = OutputStreamSink(getOutputStream(), timeout) |
| return timeout.sink(sink) |
| } |
| |
| /** |
| * Returns a source that reads from `socket`. Prefer this over [source] |
| * because this method honors timeouts. When the socket |
| * read times out, the socket is asynchronously closed by a watchdog thread. |
| */ |
| @Throws(IOException::class) |
| fun Socket.source(): Source { |
| val timeout = SocketAsyncTimeout(this) |
| val source = InputStreamSource(getInputStream(), timeout) |
| return timeout.source(source) |
| } |
| |
| private val logger = Logger.getLogger("okio.Okio") |
| |
| private class SocketAsyncTimeout(private val socket: Socket) : AsyncTimeout() { |
| override fun newTimeoutException(cause: IOException?): IOException { |
| val ioe = SocketTimeoutException("timeout") |
| if (cause != null) { |
| ioe.initCause(cause) |
| } |
| return ioe |
| } |
| |
| override fun timedOut() { |
| try { |
| socket.close() |
| } catch (e: Exception) { |
| logger.log(Level.WARNING, "Failed to close timed out socket $socket", e) |
| } catch (e: AssertionError) { |
| if (e.isAndroidGetsocknameError) { |
| // Catch this exception due to a Firmware issue up to android 4.2.2 |
| // https://code.google.com/p/android/issues/detail?id=54072 |
| logger.log(Level.WARNING, "Failed to close timed out socket $socket", e) |
| } else { |
| throw e |
| } |
| } |
| } |
| } |
| |
| /** Returns a sink that writes to `file`. */ |
| @JvmOverloads |
| @Throws(FileNotFoundException::class) |
| fun File.sink(append: Boolean = false): Sink = FileOutputStream(this, append).sink() |
| |
| /** Returns a sink that writes to `file`. */ |
| @Throws(FileNotFoundException::class) |
| fun File.appendingSink(): Sink = FileOutputStream(this, true).sink() |
| |
| /** Returns a source that reads from `file`. */ |
| @Throws(FileNotFoundException::class) |
| fun File.source(): Source = inputStream().source() |
| |
| /** Returns a source that reads from `path`. */ |
| @Throws(IOException::class) |
| @IgnoreJRERequirement // Can only be invoked on Java 7+. |
| fun Path.sink(vararg options: OpenOption): Sink = |
| Files.newOutputStream(this, *options).sink() |
| |
| /** Returns a sink that writes to `path`. */ |
| @Throws(IOException::class) |
| @IgnoreJRERequirement // Can only be invoked on Java 7+. |
| fun Path.source(vararg options: OpenOption): Source = |
| Files.newInputStream(this, *options).source() |
| |
| /** |
| * Returns a sink that uses [cipher] to encrypt or decrypt [this]. |
| * |
| * @throws IllegalArgumentException if [cipher] isn't a block cipher. |
| */ |
| fun Sink.cipherSink(cipher: Cipher): CipherSink = CipherSink(this.buffer(), cipher) |
| |
| /** |
| * Returns a source that uses [cipher] to encrypt or decrypt [this]. |
| * |
| * @throws IllegalArgumentException if [cipher] isn't a block cipher. |
| */ |
| fun Source.cipherSource(cipher: Cipher): CipherSource = CipherSource(this.buffer(), cipher) |
| |
| /** |
| * Returns a sink that uses [mac] to hash [this]. |
| */ |
| fun Sink.hashingSink(mac: Mac): HashingSink = HashingSink(this, mac) |
| |
| /** |
| * Returns a source that uses [mac] to hash [this]. |
| */ |
| fun Source.hashingSource(mac: Mac): HashingSource = HashingSource(this, mac) |
| |
| /** |
| * Returns a sink that uses [digest] to hash [this]. |
| */ |
| fun Sink.hashingSink(digest: MessageDigest): HashingSink = HashingSink(this, digest) |
| |
| /** |
| * Returns a source that uses [digest] to hash [this]. |
| */ |
| fun Source.hashingSource(digest: MessageDigest): HashingSource = HashingSource(this, digest) |
| |
| /** |
| * Returns true if this error is due to a firmware bug fixed after Android 4.2.2. |
| * https://code.google.com/p/android/issues/detail?id=54072 |
| */ |
| internal val AssertionError.isAndroidGetsocknameError: Boolean get() { |
| return cause != null && message?.contains("getsockname failed") ?: false |
| } |