Skip to main content

gpg_inspector_lib/packet/
compressed_data.rs

1//! Compressed Data packet parsing (tag 8).
2//!
3//! Compressed Data packets contain compressed data that, when decompressed,
4//! yields other OpenPGP packets (typically Literal Data or Signature packets).
5//!
6//! RFC 4880 Section 5.6
7
8use crate::error::Result;
9use crate::lookup::lookup_compression_algorithm;
10use crate::stream::ByteStream;
11
12use super::Field;
13
14/// Parsed Compressed Data packet.
15#[derive(Debug, Clone)]
16pub struct CompressedDataPacket {
17    /// Compression algorithm used.
18    pub algorithm: u8,
19    /// Compressed data (not decompressed).
20    pub compressed_data: Vec<u8>,
21}
22
23/// Parses a Compressed Data packet body.
24pub fn parse_compressed_data(
25    stream: &mut ByteStream,
26    fields: &mut Vec<Field>,
27    body_offset: usize,
28) -> Result<CompressedDataPacket> {
29    let algo_start = body_offset + stream.pos();
30    let algorithm = stream.octet()?;
31    let algo_lookup = lookup_compression_algorithm(algorithm);
32    fields.push(Field::field(
33        "Algorithm",
34        algo_lookup.display(),
35        (algo_start, algo_start + 1),
36    ));
37
38    let data_start = body_offset + stream.pos();
39    let compressed_data = stream.rest();
40    let data_end = body_offset + stream.pos();
41
42    if !compressed_data.is_empty() {
43        fields.push(Field::field(
44            "Compressed Data",
45            format!("{} bytes", compressed_data.len()),
46            (data_start, data_end),
47        ));
48    }
49
50    Ok(CompressedDataPacket {
51        algorithm,
52        compressed_data,
53    })
54}
55
56/// Maximum decompressed size in bytes (decompression-bomb guard).
57#[cfg(feature = "decompress")]
58pub const MAX_DECOMPRESSED: u64 = 64 * 1024 * 1024;
59
60/// Decompresses a Compressed Data packet payload.
61///
62/// Supports the RFC 4880 algorithms: 0 (uncompressed passthrough),
63/// 1 (ZIP / raw DEFLATE), 2 (ZLIB), and 3 (BZip2). Output is capped at
64/// [`MAX_DECOMPRESSED`] bytes to guard against decompression bombs.
65///
66/// Returns a human-readable error message on failure so callers can
67/// surface it as a field without aborting the parse.
68#[cfg(feature = "decompress")]
69pub fn decompress(algorithm: u8, data: &[u8]) -> std::result::Result<Vec<u8>, String> {
70    use std::io::Read;
71
72    let mut out = Vec::new();
73    let limit = MAX_DECOMPRESSED + 1;
74    let read_result = match algorithm {
75        0 => {
76            out.extend_from_slice(data);
77            Ok(data.len())
78        }
79        1 => flate2::read::DeflateDecoder::new(data)
80            .take(limit)
81            .read_to_end(&mut out),
82        2 => flate2::read::ZlibDecoder::new(data)
83            .take(limit)
84            .read_to_end(&mut out),
85        3 => bzip2::read::BzDecoder::new(data)
86            .take(limit)
87            .read_to_end(&mut out),
88        n => return Err(format!("unsupported compression algorithm {}", n)),
89    };
90
91    match read_result {
92        Ok(_) if out.len() as u64 > MAX_DECOMPRESSED => Err(format!(
93            "decompressed data exceeds {} MiB cap; not expanded",
94            MAX_DECOMPRESSED / (1024 * 1024)
95        )),
96        Ok(_) => Ok(out),
97        Err(e) => Err(format!("decompression failed: {}", e)),
98    }
99}