xref: /aosp_15_r20/external/okio/okio/src/jvmMain/kotlin/okio/GzipSink.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 @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