1pub mod decode;
2pub mod encode;
3mod error;
4mod parse;
5mod verify;
6
7use encode::binhex;
8use error::EncodeError;
9
10use std::io::{ErrorKind, Write};
11use std::{
12 ffi::{CStr, CString},
13 fs::File,
14 path::Path,
15};
16
17#[derive(Debug)]
18pub struct HQX {
19 pub vec: Vec<u8>,
20}
21
22impl HQX {
23 pub fn new(vec: Vec<u8>) -> HQX {
24 HQX { vec }
25 }
26
27 pub fn borrow(&self) -> HQXRef {
28 let mut bytes = &self.vec[..];
29
30 let name_len = &bytes[0];
31 bytes = &bytes[1..];
32
33 let name_len_usize = *name_len as usize;
34
35 let name = CStr::from_bytes_with_nul(&bytes[..name_len_usize + 1]).unwrap();
37 bytes = &bytes[name_len_usize + 1..];
38
39 let file_type = <&[u8; 4]>::try_from(&bytes[..4]).unwrap();
40 bytes = &bytes[4..];
41
42 let author = <&[u8; 4]>::try_from(&bytes[..4]).unwrap();
43 bytes = &bytes[4..];
44
45 let flags = <&[u8; 2]>::try_from(&bytes[..2]).unwrap();
46 bytes = &bytes[2..];
47
48 let data_len = u32::from_be_bytes(<[u8; 4]>::try_from(&bytes[..4]).unwrap());
49 bytes = &bytes[4..];
50
51 let resource_len = u32::from_be_bytes(<[u8; 4]>::try_from(&bytes[..4]).unwrap());
52 bytes = &bytes[4..];
53
54 let hc_bytes = <[u8; 2]>::try_from(&bytes[..2]).unwrap_or_default();
55 bytes = &bytes[2..];
56
57 let hc = u16::from_be_bytes(hc_bytes);
58
59 let header_len = get_header_len(name_len_usize) as u16;
60
61 let data = {
62 if data_len > 0 {
63 let data = &bytes[..data_len as usize];
64 bytes = &bytes[data_len as usize..];
65
66 Some(data)
67 } else {
68 None
69 }
70 };
71
72 let dc_bytes = {
73 if bytes.len() >= 2 {
74 let dc_bytes = <[u8; 2]>::try_from(&bytes[..2]).unwrap_or_default();
75 bytes = &bytes[2..];
76 dc_bytes
77 } else {
78 [0, 0]
79 }
80 };
81
82 let dc = u16::from_be_bytes(dc_bytes);
83 let data_fork = data.map(|data| Fork { data, crc: dc });
84
85 let resource = {
86 if resource_len > 0 {
87 let resource = &bytes[..resource_len as usize];
88 bytes = &bytes[resource_len as usize..];
89
90 Some(resource)
91 } else {
92 None
93 }
94 };
95
96 let rc_bytes = {
97 if bytes.len() >= 2 {
98 let rc_bytes = <[u8; 2]>::try_from(&bytes[..2]).unwrap_or_default();
99 bytes = &bytes[2..];
100 rc_bytes
101 } else {
102 [0, 0]
103 }
104 };
105
106 let rc = u16::from_be_bytes(rc_bytes);
107 let resource_fork = resource.map(|resource| Fork {
108 data: resource,
109 crc: rc,
110 });
111
112 HQXRef {
113 hqx: self,
114 name_len,
115 name,
116 file_type,
117 author,
118 flags,
119 data_len,
120 resource_len,
121 hc,
122 header_len,
123 data_fork,
124 resource_fork,
125 }
126 }
127
128 pub fn from_config(config: HQXConfig) -> Result<HQX, EncodeError> {
129 binhex(config)
130 }
131}
132
133pub struct HQXConfig<'a> {
134 pub name: Option<CString>,
135 pub file_type: Option<&'a [u8; 4]>,
136 pub author: Option<&'a [u8; 4]>,
137 pub flags: Option<&'a [u8; 2]>,
138 pub data: Option<&'a [u8]>,
139 pub resource: Option<&'a [u8]>,
140}
141
142impl<'a> HQXConfig<'a> {
143 pub fn hqx_len(&self) -> usize {
144 get_header_len(self.name.as_ref().map_or(0, |name| name.to_bytes().len()))
145 + self.data.unwrap_or_default().len()
146 + self.resource.unwrap_or_default().len()
147 }
148}
149
150pub struct HQXRef<'a> {
151 pub hqx: &'a HQX,
152 pub name_len: &'a u8,
153 pub name: &'a CStr,
154 pub file_type: &'a [u8; 4],
155 pub author: &'a [u8; 4],
156 pub flags: &'a [u8; 2],
157 pub data_len: u32,
158 pub resource_len: u32,
159 pub hc: u16,
160 pub header_len: u16,
161 pub data_fork: Option<Fork<'a>>,
162 pub resource_fork: Option<Fork<'a>>,
163}
164
165#[derive(Debug, Default)]
166pub struct Fork<'a> {
167 pub data: &'a [u8],
168 pub crc: u16,
169}
170
171impl<'a> HQXRef<'a> {
172 pub fn decode_to_file<P: AsRef<Path>>(&self, path: P) -> std::io::Result<File> {
173 let path = {
174 let mut path = path.as_ref().to_path_buf();
175 if path.is_dir() {
176 match self.name.to_str() {
177 Ok(name) => {
178 path.push(name);
179 }
180 Err(err) => {
181 eprintln!("{:#?}", err);
182 return Err(std::io::Error::new(ErrorKind::Other, "Non UTF-8 file name"));
183 }
184 }
185 }
186 path
187 };
188 let mut file = File::create(path)?;
189
190 if let Some(Fork { data, .. }) = self.data_fork.as_ref() {
191 file.write_all(data)?;
192 Ok(file)
193 } else {
194 Err(ErrorKind::InvalidData.into())
195 }
196 }
197
198 pub fn encode_to_file<P: AsRef<Path>>(&self, path: P) -> std::io::Result<File> {
199 let path = {
200 let mut path = path.as_ref().to_path_buf();
201 if path.is_dir() {
202 match self.name.to_str() {
203 Ok(name) => {
204 path.push(name);
205 }
206 Err(err) => {
207 eprintln!("{:#?}", err);
208 return Err(std::io::Error::new(ErrorKind::Other, "Non UTF-8 file name"));
209 }
210 }
211 }
212 path
213 };
214 let mut file = File::create(path)?;
215
216 let encoded = self.encode();
217
218 file.write_all(&encoded)?;
219
220 Ok(file)
221 }
222}
223
224fn get_header_len(name_len: usize) -> usize {
225 1 + (name_len + 1) + 4 + 4 + 2 + 4 + 4 + 2
226}