ostree_ext/
generic_decompress.rs1use std::io::Read;
17
18use crate::oci_spec::image as oci_image;
19
20const DOCKER_TYPE_LAYER_TAR: &str = "application/vnd.docker.image.rootfs.diff.tar";
24
25trait ReadWithGetInnerMut: Read + Send + 'static {
27 fn get_inner_mut(&mut self) -> &mut dyn Read;
28}
29
30struct TransparentDecompressor<R: Read + Send + 'static>(R);
33
34impl<R: Read + Send + 'static> Read for TransparentDecompressor<R> {
35 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
36 self.0.read(buf)
37 }
38}
39
40impl<R: Read + Send + 'static> ReadWithGetInnerMut for TransparentDecompressor<R> {
41 fn get_inner_mut(&mut self) -> &mut dyn Read {
42 &mut self.0
43 }
44}
45
46struct GzipDecompressor<R: std::io::BufRead>(flate2::bufread::GzDecoder<R>);
49
50impl<R: std::io::BufRead + Send + 'static> Read for GzipDecompressor<R> {
51 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
52 self.0.read(buf)
53 }
54}
55
56impl<R: std::io::BufRead + Send + 'static> ReadWithGetInnerMut for GzipDecompressor<R> {
57 fn get_inner_mut(&mut self) -> &mut dyn Read {
58 self.0.get_mut()
59 }
60}
61
62struct ZstdDecompressor<'a, R: std::io::BufRead>(zstd::stream::read::Decoder<'a, R>);
65
66impl<'a: 'static, R: std::io::BufRead + Send + 'static> Read for ZstdDecompressor<'a, R> {
67 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
68 self.0.read(buf)
69 }
70}
71
72impl<'a: 'static, R: std::io::BufRead + Send + 'static> ReadWithGetInnerMut
73 for ZstdDecompressor<'a, R>
74{
75 fn get_inner_mut(&mut self) -> &mut dyn Read {
76 self.0.get_mut()
77 }
78}
79
80pub(crate) struct Decompressor {
81 inner: Box<dyn ReadWithGetInnerMut>,
82 finished: bool,
83}
84
85impl Read for Decompressor {
86 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
87 self.inner.read(buf)
88 }
89}
90
91impl Drop for Decompressor {
92 fn drop(&mut self) {
93 if self.finished {
94 return;
95 }
96
97 self._finish()
107 .expect("Failed to flush pipe while dropping Decompressor")
108 }
109}
110
111impl Decompressor {
112 pub(crate) fn new(
114 media_type: &oci_image::MediaType,
115 src: impl Read + Send + 'static,
116 ) -> anyhow::Result<Self> {
117 let r: Box<dyn ReadWithGetInnerMut> = match media_type {
118 oci_image::MediaType::ImageLayerZstd => {
119 Box::new(ZstdDecompressor(zstd::stream::read::Decoder::new(src)?))
120 }
121 oci_image::MediaType::ImageLayerGzip => Box::new(GzipDecompressor(
122 flate2::bufread::GzDecoder::new(std::io::BufReader::new(src)),
123 )),
124 oci_image::MediaType::ImageLayer => Box::new(TransparentDecompressor(src)),
125 oci_image::MediaType::Other(t) if t.as_str() == DOCKER_TYPE_LAYER_TAR => {
126 Box::new(TransparentDecompressor(src))
127 }
128 o => anyhow::bail!("Unhandled layer type: {}", o),
129 };
130 Ok(Self {
131 inner: r,
132 finished: false,
133 })
134 }
135
136 pub(crate) fn finish(mut self) -> anyhow::Result<()> {
137 self._finish()
138 }
139
140 fn _finish(&mut self) -> anyhow::Result<()> {
141 self.finished = true;
142
143 let mut sink = std::io::sink();
159 let n = std::io::copy(self.inner.get_inner_mut(), &mut sink)?;
160
161 if n > 0 {
162 tracing::debug!("Read extra {n} bytes at end of decompressor stream");
163 }
164
165 Ok(())
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 struct BrokenPipe;
174
175 impl Read for BrokenPipe {
176 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
177 std::io::Result::Err(std::io::ErrorKind::BrokenPipe.into())
178 }
179 }
180
181 #[test]
182 #[should_panic(expected = "Failed to flush pipe while dropping Decompressor")]
183 fn test_drop_decompressor_with_finish_error_should_panic() {
184 let broken = BrokenPipe;
185 let d = Decompressor::new(&oci_image::MediaType::ImageLayer, broken).unwrap();
186 drop(d)
187 }
188
189 #[test]
190 fn test_drop_decompressor_with_successful_finish() {
191 let empty = std::io::empty();
192 let d = Decompressor::new(&oci_image::MediaType::ImageLayer, empty).unwrap();
193 drop(d)
194 }
195
196 #[test]
197 fn test_drop_decompressor_with_incomplete_gzip_data() {
198 let empty = std::io::empty();
199 let d = Decompressor::new(&oci_image::MediaType::ImageLayerGzip, empty).unwrap();
200 drop(d)
201 }
202
203 #[test]
204 fn test_drop_decompressor_with_incomplete_zstd_data() {
205 let empty = std::io::empty();
206 let d = Decompressor::new(&oci_image::MediaType::ImageLayerZstd, empty).unwrap();
207 drop(d)
208 }
209
210 #[test]
211 fn test_gzip_decompressor_with_garbage_input() {
212 let garbage = b"This is not valid gzip data";
213 let mut d = Decompressor::new(&oci_image::MediaType::ImageLayerGzip, &garbage[..]).unwrap();
214 let mut buf = [0u8; 32];
215 let e = d.read(&mut buf).unwrap_err();
216 assert!(matches!(e.kind(), std::io::ErrorKind::InvalidInput));
217 assert_eq!(e.to_string(), "invalid gzip header".to_string());
218 drop(d)
219 }
220
221 #[test]
222 fn test_zstd_decompressor_with_garbage_input() {
223 let garbage = b"This is not valid zstd data";
224 let mut d = Decompressor::new(&oci_image::MediaType::ImageLayerZstd, &garbage[..]).unwrap();
225 let mut buf = [0u8; 32];
226 let e = d.read(&mut buf).unwrap_err();
227 assert!(matches!(e.kind(), std::io::ErrorKind::Other));
228 assert_eq!(e.to_string(), "Unknown frame descriptor".to_string());
229 drop(d)
230 }
231}