gpg_inspector_lib/packet/
literal_data.rs1use chrono::{DateTime, TimeZone, Utc};
10
11use crate::error::Result;
12use crate::stream::ByteStream;
13
14use super::Field;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum DataFormat {
19 Binary,
21 Text,
23 Utf8,
25 Mime,
27 Local,
29 Unknown(u8),
31}
32
33impl DataFormat {
34 fn from_u8(b: u8) -> Self {
35 match b {
36 0x62 => DataFormat::Binary,
37 0x74 => DataFormat::Text,
38 0x75 => DataFormat::Utf8,
39 0x6D => DataFormat::Mime,
40 0x6C => DataFormat::Local,
41 _ => DataFormat::Unknown(b),
42 }
43 }
44
45 fn description(&self) -> String {
46 match self {
47 DataFormat::Binary => "b (Binary)".to_string(),
48 DataFormat::Text => "t (Text)".to_string(),
49 DataFormat::Utf8 => "u (UTF-8 text)".to_string(),
50 DataFormat::Mime => "m (MIME)".to_string(),
51 DataFormat::Local => "l (Local - deprecated)".to_string(),
52 DataFormat::Unknown(b) => format!("{:#04x} (Unknown)", b),
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct LiteralDataPacket {
60 pub format: DataFormat,
62 pub filename: String,
64 pub date: u32,
66 pub data: Vec<u8>,
68}
69
70pub fn parse_literal_data(
72 stream: &mut ByteStream,
73 fields: &mut Vec<Field>,
74 body_offset: usize,
75) -> Result<LiteralDataPacket> {
76 let format_start = body_offset + stream.pos();
77 let format_byte = stream.octet()?;
78 let format = DataFormat::from_u8(format_byte);
79 fields.push(Field::field(
80 "Format",
81 format.description(),
82 (format_start, format_start + 1),
83 ));
84
85 let filename_len_start = body_offset + stream.pos();
86 let filename_len = stream.octet()? as usize;
87 fields.push(Field::field(
88 "Filename Length",
89 filename_len.to_string(),
90 (filename_len_start, filename_len_start + 1),
91 ));
92
93 let filename = if filename_len > 0 {
94 let filename_start = body_offset + stream.pos();
95 let filename_bytes = stream.bytes(filename_len)?;
96 let filename_str = String::from_utf8_lossy(&filename_bytes).to_string();
97
98 let display_name = if filename_str == "_CONSOLE" {
100 format!("{} (for your eyes only)", filename_str)
101 } else {
102 filename_str.clone()
103 };
104
105 fields.push(Field::field(
106 "Filename",
107 display_name,
108 (filename_start, filename_start + filename_len),
109 ));
110 filename_str
111 } else {
112 String::new()
113 };
114
115 let date_start = body_offset + stream.pos();
116 let date = stream.uint32()?;
117 let date_display = if date == 0 {
118 "0 (unspecified)".to_string()
119 } else {
120 let datetime: DateTime<Utc> = Utc.timestamp_opt(date as i64, 0).unwrap();
121 datetime.format("%Y-%m-%d %H:%M:%S UTC").to_string()
122 };
123 fields.push(Field::field(
124 "Date",
125 date_display,
126 (date_start, date_start + 4),
127 ));
128
129 let data_start = body_offset + stream.pos();
130 let data = stream.rest();
131 let data_end = body_offset + stream.pos();
132
133 if !data.is_empty() {
134 let data_desc = match format {
136 DataFormat::Text | DataFormat::Utf8 => {
137 let preview = String::from_utf8_lossy(&data);
138 if preview.len() > 64 {
139 format!(
140 "{} bytes: \"{}...\"",
141 data.len(),
142 &preview[..60].replace('\n', "\\n")
143 )
144 } else {
145 format!("{} bytes: \"{}\"", data.len(), preview.replace('\n', "\\n"))
146 }
147 }
148 _ => format!("{} bytes", data.len()),
149 };
150
151 fields.push(Field::field("Data", data_desc, (data_start, data_end)));
152 }
153
154 Ok(LiteralDataPacket {
155 format,
156 filename,
157 date,
158 data,
159 })
160}