Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_file/src/zip/write.rs

7.3 KiB, 7 runs

created by r1870400018:22544, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Writing an archive back out, copying everything nobody touched.
2//!
3//! A held member is written by copying the bytes it was read from -- header, data, descriptor and
4//! directory entry alike -- so a member this never understood comes out exactly as it went in. Only a
5//! member the caller replaced is built afresh, and only its directory entry is written rather than
6//! copied.
7//!
8//! The one field a copied directory entry has patched is the offset of the member's local header,
9//! because inserting or removing a member moves everything after it. Where nothing moved, nothing is
10//! patched, and the bytes out are the bytes in.
11//!
12//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
13//! Anthropic Claude
14
15use crate::zip::{
16 Body,
17 Member,
18 Method,
19 Zip,
20 u32le,
21};
22
23use oxedyne_fe2o3_core::prelude::*;
24
25/// A central directory entry.
26const SIG_CEN: u32 = 0x0201_4b50;
27/// A local file header.
28const SIG_LOC: u32 = 0x0403_4b50;
29/// The end of central directory record.
30const SIG_EOCD: u32 = 0x0605_4b50;
31
32/// The version that reads a DEFLATE member: 2.0.
33const VERSION: u16 = 20;
34/// Bit 11 of the general purpose flag: the name is UTF-8.
35const FLAG_UTF8: u16 = 0x0800;
36
37impl Zip {
38
39 /// An archive read and written straight back gives the bytes it was read from. That is a property
40 /// callers should check rather than take on trust: it is what says this build understood the file
41 /// well enough to be handed somebody's document.
42 pub fn write(&self) -> Outcome<Vec<u8>> {
43 if self.zip64 {
44 return Err(err!(
45 "This archive needed the ZIP64 records to be read -- it holds more than 65,535 \
46 members, or more than 4 GB. ZIP64 is read here and not written, so writing it back \
47 would produce a file that is not the one that was opened. The archive is refused \
48 rather than damaged."; Unimplemented, Excessive));
49 }
50 let mut out = Vec::with_capacity(self.src.len() + 4096);
51 let mut at = Vec::with_capacity(self.members.len());
52 for m in &self.members {
53 at.push(out.len() as u64);
54 match &m.body {
55 Body::Held { whole, .. } => {
56 let bytes = res!(self.src.get(whole.clone()).ok_or_else(|| err!(
57 "'{}' addresses bytes {}..{} of an archive of {} bytes.",
58 m.name, whole.start, whole.end, self.src.len(); Bug, Range)));
59 out.extend_from_slice(bytes);
60 }
61 Body::Fresh { data, stamp } => res!(fresh(&mut out, m, data, *stamp)),
62 }
63 }
64 let cd_at = out.len();
65 for (i, m) in self.members.iter().enumerate() {
66 let off = at[i];
67 if off > u32::MAX as u64 {
68 return Err(err!(
69 "'{}' would sit at byte {}, past the 4 GB an ordinary ZIP directory can \
70 address. ZIP64 is read here and not written.", m.name, off;
71 Unimplemented, Excessive));
72 }
73 match &m.body {
74 Body::Held { cen, .. } => {
75 let bytes = res!(self.src.get(cen.clone()).ok_or_else(|| err!(
76 "'{}' addresses directory bytes {}..{} of an archive of {} bytes.",
77 m.name, cen.start, cen.end, self.src.len(); Bug, Range)));
78 let from = out.len();
79 out.extend_from_slice(bytes);
80 // The only field that can have moved. Every other byte of the entry is the
81 // archive's own.
82 let was = res!(u32le(bytes, 42));
83 if was as u64 != off {
84 let k = from + 42;
85 out[k..k + 4].copy_from_slice(&(off as u32).to_le_bytes());
86 }
87 }
88 Body::Fresh { data, stamp } => res!(cen_fresh(&mut out, m, data, *stamp, off as u32)),
89 }
90 }
91 let cd_len = out.len() - cd_at;
92 if self.members.len() > 0xFFFF {
93 return Err(err!(
94 "The archive holds {} members, past the 65,535 an ordinary ZIP directory can \
95 count. ZIP64 is read here and not written.", self.members.len();
96 Unimplemented, Excessive));
97 }
98 let n = self.members.len() as u16;
99 out.extend_from_slice(&SIG_EOCD.to_le_bytes());
100 out.extend_from_slice(&0u16.to_le_bytes()); // This disk.
101 out.extend_from_slice(&0u16.to_le_bytes()); // The disk the directory starts on.
102 out.extend_from_slice(&n.to_le_bytes());
103 out.extend_from_slice(&n.to_le_bytes());
104 out.extend_from_slice(&(cd_len as u32).to_le_bytes());
105 out.extend_from_slice(&(cd_at as u32).to_le_bytes());
106 out.extend_from_slice(&(self.comment.len() as u16).to_le_bytes());
107 out.extend_from_slice(&self.comment);
108 Ok(out)
109 }
110}
111
112/// Writes a fresh member's local header and its bytes.
113fn fresh(
114 out: &mut Vec<u8>,
115 m: &Member,
116 data: &[u8],
117 stamp: (u16, u16),
118)
119 -> Outcome<()>
120{
121 let body = res!(pack(m, data));
122 let name = m.name.as_bytes();
123 out.extend_from_slice(&SIG_LOC.to_le_bytes());
124 out.extend_from_slice(&VERSION.to_le_bytes());
125 out.extend_from_slice(&flags(&m.name).to_le_bytes());
126 out.extend_from_slice(&m.method.code().to_le_bytes());
127 out.extend_from_slice(&stamp.0.to_le_bytes());
128 out.extend_from_slice(&stamp.1.to_le_bytes());
129 out.extend_from_slice(&m.crc.to_le_bytes());
130 out.extend_from_slice(&(body.len() as u32).to_le_bytes());
131 out.extend_from_slice(&(data.len() as u32).to_le_bytes());
132 out.extend_from_slice(&(name.len() as u16).to_le_bytes());
133 out.extend_from_slice(&0u16.to_le_bytes()); // No extra field.
134 out.extend_from_slice(name);
135 out.extend_from_slice(&body);
136 Ok(())
137}
138
139/// Writes a fresh member's central directory entry.
140fn cen_fresh(
141 out: &mut Vec<u8>,
142 m: &Member,
143 data: &[u8],
144 stamp: (u16, u16),
145 off: u32,
146)
147 -> Outcome<()>
148{
149 let body = res!(pack(m, data));
150 let name = m.name.as_bytes();
151 out.extend_from_slice(&SIG_CEN.to_le_bytes());
152 out.extend_from_slice(&VERSION.to_le_bytes()); // Made by version 2.0, host MS-DOS.
153 out.extend_from_slice(&VERSION.to_le_bytes());
154 out.extend_from_slice(&flags(&m.name).to_le_bytes());
155 out.extend_from_slice(&m.method.code().to_le_bytes());
156 out.extend_from_slice(&stamp.0.to_le_bytes());
157 out.extend_from_slice(&stamp.1.to_le_bytes());
158 out.extend_from_slice(&m.crc.to_le_bytes());
159 out.extend_from_slice(&(body.len() as u32).to_le_bytes());
160 out.extend_from_slice(&(data.len() as u32).to_le_bytes());
161 out.extend_from_slice(&(name.len() as u16).to_le_bytes());
162 out.extend_from_slice(&0u16.to_le_bytes()); // No extra field.
163 out.extend_from_slice(&0u16.to_le_bytes()); // No comment.
164 out.extend_from_slice(&0u16.to_le_bytes()); // Disk zero.
165 out.extend_from_slice(&0u16.to_le_bytes()); // Internal attributes.
166 out.extend_from_slice(&0u32.to_le_bytes()); // External attributes.
167 out.extend_from_slice(&off.to_le_bytes());
168 out.extend_from_slice(name);
169 Ok(())
170}
171
172/// A fresh member's bytes as the archive will hold them.
173///
174/// Called twice for the same member -- once for the local header and once for the directory entry,
175/// which must agree about the compressed size -- so it is deterministic by construction rather than
176/// by a cached size that could go stale.
177fn pack(m: &Member, data: &[u8]) -> Outcome<Vec<u8>> {
178 match m.method {
179 Method::Store => Ok(data.to_vec()),
180 Method::Deflate => {
181 use std::io::Write;
182 let mut w = flate2::write::DeflateEncoder::new(
183 Vec::new(),
184 flate2::Compression::default(),
185 );
186 res!(w.write_all(data), IO);
187 Ok(res!(w.finish(), IO))
188 }
189 Method::Other(c) => Err(err!(
190 "'{}' was given to the archive under compression method {}, which this does not \
191 write.", m.name, c; Unimplemented)),
192 }
193}
194
195/// Bit 11 is set only where the name needs it. Setting it on an ASCII name would be legal and would
196/// still change the bytes of every archive written here, for nothing.
197fn flags(name: &str) -> u16 {
198 match name.is_ascii() {
199 true => 0,
200 false => FLAG_UTF8,
201 }
202}