binhex4/
lib.rs

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        // add one to include null terminator
36        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}