1use crate::{
4 error::{Error, resource},
5 zip::{Archive, Member, MemberId},
6};
7use structured_asn1::{
8 Span,
9 memory::{OwnedVec, Work},
10 range::RangeSource,
11};
12
13mod assess;
14pub use assess::{Form, Inspection, Issue, Principal, Requirement, Rules, Strength};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Role {
20 Directory,
22 Mimetype,
24 Data,
26 Cades,
28 Xades,
30 Timestamp,
32 Evidence,
34 XmlEvidence,
36 Manifest,
38 ArchiveManifest,
40 EvidenceManifest,
42 OdfManifest,
44 OcfContainer,
46 Application,
48 Reserved,
50}
51
52#[derive(Debug, Clone, Copy)]
55pub struct MimetypeHeader<D, G> {
56 pub member: MemberId<D, G>,
58 pub local: Option<Span>,
60 flags: u8,
61}
62impl<D, G> MimetypeHeader<D, G> {
63 #[must_use]
65 pub fn first(&self) -> bool {
66 self.flags & 1 != 0
67 }
68 #[must_use]
70 pub fn stored(&self) -> bool {
71 self.flags & 2 != 0
72 }
73 #[must_use]
75 pub fn no_extra(&self) -> bool {
76 self.flags & 4 != 0
77 }
78 #[must_use]
80 pub fn unencrypted(&self) -> bool {
81 self.flags & 8 != 0
82 }
83}
84
85pub type OccurrenceResult<'v, 's, S> =
87 crate::error::SourceResult<Option<(Member<'v, 's, S>, Role)>, S>;
88type HeaderResult<S> = crate::error::SourceResult<
89 MimetypeHeader<<S as RangeSource>::Document, <S as RangeSource>::Generation>,
90 S,
91>;
92
93pub struct Census<'v, 's, S: RangeSource> {
97 archive: &'v Archive<'s, S>,
98 roles: OwnedVec<Role>,
99 mimetypes: OwnedVec<MimetypeHeader<S::Document, S::Generation>>,
100}
101
102impl<'v, 's, S: RangeSource> Census<'v, 's, S> {
103 pub fn read<W: Work>(archive: &'v Archive<'s, S>, work: &W) -> Result<Self, Error<S::Error>> {
108 archive.admit(work, archive.directory_span())?;
109 let execution = archive.session().query(work);
110 let mut roles = execution.vector();
111 roles
112 .try_reserve_exact(archive.len())
113 .map_err(|cause| resource(archive.directory_span(), cause))?;
114 let mut mimetypes = execution.vector();
115 for ordinal in 0..archive.len() {
116 let member = archive
117 .member(ordinal, work)?
118 .expect("bounded original ordinal");
119 let execution = member.session().query(work);
120 execution
121 .charge_work(member.name().len() as u64 * 64 + 1)
122 .map_err(|cause| resource(member.name_span(), cause))?;
123 let role = classify(member.name());
124 if role == Role::Mimetype {
125 mimetypes
129 .try_push(MimetypeHeader {
130 member: member.id(),
131 local: member.local_span(),
132 flags: u8::from(member.local_span().is_some_and(|span| span.start == 0))
133 | (u8::from(member.method() == 0) << 1)
134 | (u8::from(
135 member
136 .local_extra_span()
137 .is_some_and(|span| span.length == 0),
138 ) << 2)
139 | (u8::from(member.flags() & 0x2041 == 0) << 3),
140 })
141 .map_err(|cause| resource(member.central_span(), cause))?;
142 }
143 roles
144 .try_push(role)
145 .map_err(|cause| resource(member.central_span(), cause))?;
146 }
147 archive.admit(work, archive.directory_span())?;
148 Ok(Self {
149 archive,
150 roles: roles.into_owned(),
151 mimetypes: mimetypes.into_owned(),
152 })
153 }
154 #[must_use]
156 pub fn archive(&self) -> &'v Archive<'s, S> {
157 self.archive
158 }
159 #[must_use]
161 pub fn len(&self) -> usize {
162 self.roles.len()
163 }
164 #[must_use]
166 pub fn is_empty(&self) -> bool {
167 self.roles.is_empty()
168 }
169 pub fn occurrence<W: Work>(&self, ordinal: usize, work: &W) -> OccurrenceResult<'v, 's, S> {
171 let Some(member) = self.archive.member(ordinal, work)? else {
172 return Ok(None);
173 };
174 Ok(Some((member, self.roles[ordinal])))
175 }
176 pub fn mimetypes<'q, W: Work>(
179 &'q self,
180 work: &'q W,
181 ) -> impl Iterator<Item = HeaderResult<S>> + use<'q, 'v, 's, S, W> {
182 let mut at = 0;
183 let mut stopped = false;
184 core::iter::from_fn(move || {
185 if stopped || at == self.mimetypes.len() {
186 return None;
187 }
188 if let Err(cause) = self.archive.admit(work, self.archive.directory_span()) {
189 stopped = true;
190 return Some(Err(cause));
191 }
192 let value = self.mimetypes[at];
193 at += 1;
194 Some(Ok(value))
195 })
196 }
197}
198
199fn classify(name: &[u8]) -> Role {
200 if name.ends_with(b"/") {
201 return Role::Directory;
202 }
203 if name == b"mimetype" {
204 return Role::Mimetype;
205 }
206 let Some(relative) = name.strip_prefix(b"META-INF/") else {
207 return Role::Data;
208 };
209 if relative == b"manifest.xml" {
210 return Role::OdfManifest;
211 }
212 if relative == b"container.xml" {
213 return Role::OcfContainer;
214 }
215 if relative.ends_with(b".xml") {
216 if contains(relative, b"ASiCArchiveManifest") {
219 return Role::ArchiveManifest;
220 }
221 if relative.starts_with(b"ASiCEvidenceRecordManifest") {
222 return Role::EvidenceManifest;
223 }
224 if relative.starts_with(b"ASiCManifest") {
225 return Role::Manifest;
226 }
227 if contains(relative, b"evidencerecord") {
228 return Role::XmlEvidence;
229 }
230 if contains(relative, b"signatures") {
231 return Role::Xades;
232 }
233 }
234 if relative.ends_with(b".p7s") && contains(relative, b"signature") {
235 return Role::Cades;
236 }
237 if relative.ends_with(b".tst") && contains(relative, b"timestamp") {
238 return Role::Timestamp;
239 }
240 if relative.ends_with(b".ers") && contains(relative, b"evidencerecord") {
241 return Role::Evidence;
242 }
243 for word in [
246 b"signature".as_slice(),
247 b"timestamp",
248 b"manifest",
249 b"container.xml",
250 ] {
251 if relative
252 .windows(word.len())
253 .any(|part| part.eq_ignore_ascii_case(word))
254 {
255 return Role::Reserved;
256 }
257 }
258 Role::Application
259}
260
261fn contains(name: &[u8], word: &[u8]) -> bool {
262 name.windows(word.len()).any(|part| part == word)
263}