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 @file:JvmName("-GzipSinkExtensions") 18 @file:Suppress("NOTHING_TO_INLINE") // Aliases to public API. 19 20 package okio 21 22 import java.io.IOException 23 import java.util.zip.CRC32 24 import java.util.zip.Deflater 25 import java.util.zip.Deflater.DEFAULT_COMPRESSION 26 27 /** 28 * A sink that uses [GZIP](http://www.ietf.org/rfc/rfc1952.txt) to 29 * compress written data to another sink. 30 * 31 * ### Sync flush 32 * 33 * Aggressive flushing of this stream may result in reduced compression. Each 34 * call to [flush] immediately compresses all currently-buffered data; 35 * this early compression may be less effective than compression performed 36 * without flushing. 37 * 38 * This is equivalent to using [Deflater] with the sync flush option. 39 * This class does not offer any partial flush mechanism. For best performance, 40 * only call [flush] when application behavior requires it. 41 */ 42 class GzipSink(sink: Sink) : Sink { 43 /** Sink into which the GZIP format is written. */ 44 private val sink = RealBufferedSink(sink) 45 46 /** The deflater used to compress the body. */ 47 @get:JvmName("deflater") 48 val deflater = Deflater(DEFAULT_COMPRESSION, true /* No wrap */) 49 50 /** 51 * The deflater sink takes care of moving data between decompressed source and 52 * compressed sink buffers. 53 */ 54 private val deflaterSink = DeflaterSink(this.sink, deflater) 55 56 private var closed = false 57 58 /** Checksum calculated for the compressed body. */ 59 private val crc = CRC32() 60 61 init { 62 // Write the Gzip header directly into the buffer for the sink to avoid handling IOException. <lambda>null63 this.sink.buffer.apply { 64 writeShort(0x1f8b) // Two-byte Gzip ID. 65 writeByte(0x08) // 8 == Deflate compression method. 66 writeByte(0x00) // No flags. 67 writeInt(0x00) // No modification time. 68 writeByte(0x00) // No extra flags. 69 writeByte(0x00) // No OS. 70 } 71 } 72 73 @Throws(IOException::class) writenull74 override fun write(source: Buffer, byteCount: Long) { 75 require(byteCount >= 0L) { "byteCount < 0: $byteCount" } 76 if (byteCount == 0L) return 77 78 updateCrc(source, byteCount) 79 deflaterSink.write(source, byteCount) 80 } 81 82 @Throws(IOException::class) flushnull83 override fun flush() = deflaterSink.flush() 84 85 override fun timeout(): Timeout = sink.timeout() 86 87 @Throws(IOException::class) 88 override fun close() { 89 if (closed) return 90 91 // This method delegates to the DeflaterSink for finishing the deflate process 92 // but keeps responsibility for releasing the deflater's resources. This is 93 // necessary because writeFooter needs to query the processed byte count which 94 // only works when the deflater is still open. 95 96 var thrown: Throwable? = null 97 try { 98 deflaterSink.finishDeflate() 99 writeFooter() 100 } catch (e: Throwable) { 101 thrown = e 102 } 103 104 try { 105 deflater.end() 106 } catch (e: Throwable) { 107 if (thrown == null) thrown = e 108 } 109 110 try { 111 sink.close() 112 } catch (e: Throwable) { 113 if (thrown == null) thrown = e 114 } 115 116 closed = true 117 118 if (thrown != null) throw thrown 119 } 120 writeFooternull121 private fun writeFooter() { 122 sink.writeIntLe(crc.value.toInt()) // CRC of original data. 123 sink.writeIntLe(deflater.bytesRead.toInt()) // Length of original data. 124 } 125 126 /** Updates the CRC with the given bytes. */ updateCrcnull127 private fun updateCrc(buffer: Buffer, byteCount: Long) { 128 var head = buffer.head!! 129 var remaining = byteCount 130 while (remaining > 0) { 131 val segmentLength = minOf(remaining, head.limit - head.pos).toInt() 132 crc.update(head.data, head.pos, segmentLength) 133 remaining -= segmentLength 134 head = head.next!! 135 } 136 } 137 138 @JvmName("-deprecated_deflater") 139 @Deprecated( 140 message = "moved to val", 141 replaceWith = ReplaceWith(expression = "deflater"), 142 level = DeprecationLevel.ERROR, 143 ) deflaternull144 fun deflater() = deflater 145 } 146 147 /** 148 * Returns a [GzipSink] that gzip-compresses to this [Sink] while writing. 149 * 150 * @see GzipSource 151 */ 152 inline fun Sink.gzip() = GzipSink(this) 153