1use structured_asn1::memory::{MemoryError, OwnedVec, Work};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Part {
9 Whole,
11 Query,
13 Fragment,
15 QueryAndFragment,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Negative {
22 Outside,
24 Malformed,
26 Encoding,
28}
29
30#[derive(Debug)]
33pub enum Target<'uri> {
34 SameDocument(Part),
36 Root(Part),
38 Member {
40 name: MemberName<'uri>,
42 part: Part,
44 },
45 Negative(Negative),
47}
48
49#[derive(Debug)]
52pub enum MemberName<'uri> {
53 Borrowed(&'uri [u8]),
55 Owned(OwnedVec<u8>),
57}
58impl AsRef<[u8]> for MemberName<'_> {
59 fn as_ref(&self) -> &[u8] {
60 match self {
61 Self::Borrowed(bytes) => bytes,
62 Self::Owned(bytes) => bytes,
63 }
64 }
65}
66
67pub fn resolve<'uri, W: Work>(
73 source_path: &[u8],
74 uri: &'uri str,
75 work: &W,
76) -> Result<Target<'uri>, MemoryError> {
77 let cost = source_path
78 .len()
79 .checked_add(uri.len())
80 .and_then(|n| n.checked_mul(16))
81 .and_then(|n| n.checked_add(1))
82 .ok_or(MemoryError::WorkExceeded)?;
83 work.charge_work(u64::try_from(cost).map_err(|_| MemoryError::WorkExceeded)?)?;
84 let bytes = uri.as_bytes();
85 if !lexical(bytes) {
86 return Ok(Target::Negative(Negative::Malformed));
87 }
88 let fragment = bytes.iter().position(|byte| *byte == b'#');
91 let before_fragment = &bytes[..fragment.unwrap_or(bytes.len())];
92 let query = before_fragment.iter().position(|byte| *byte == b'?');
93 let path = &before_fragment[..query.unwrap_or(before_fragment.len())];
94 let part = match (query.is_some(), fragment.is_some()) {
95 (false, false) => Part::Whole,
96 (true, false) => Part::Query,
97 (false, true) => Part::Fragment,
98 (true, true) => Part::QueryAndFragment,
99 };
100 if path.is_empty() {
101 return Ok(Target::SameDocument(part));
102 }
103 if path.starts_with(b"//") {
104 return Ok(Target::Negative(Negative::Outside));
105 }
106 let first = &path[..match path.iter().position(|byte| *byte == b'/') {
107 Some(at) => at,
108 None => path.len(),
109 }];
110 if first.contains(&b':') {
111 return Ok(Target::Negative(Negative::Outside));
112 }
113 let mut base = if source_path.starts_with(b"META-INF/") || path[0] == b'/' {
114 &[][..]
115 } else {
116 match source_path.iter().rposition(|byte| *byte == b'/') {
117 Some(at) => &source_path[..=at],
118 None => &[],
119 }
120 };
121 let mut path = if path[0] == b'/' { &path[1..] } else { path };
122 loop {
125 if path.starts_with(b"./") {
126 path = &path[2..];
127 continue;
128 }
129 if path == b"." {
130 path = &[];
131 break;
132 }
133 if path == b".." || path.starts_with(b"../") {
134 if base.is_empty() {
135 return Ok(Target::Negative(Negative::Outside));
136 }
137 let without_separator = &base[..base.len() - 1];
138 base = match without_separator.iter().rposition(|byte| *byte == b'/') {
139 Some(at) => &base[..=at],
140 None => &[],
141 };
142 path = if path.len() == 2 { &[] } else { &path[3..] };
143 continue;
144 }
145 break;
146 }
147 if base.is_empty() && !path.contains(&b'%') {
148 if path.is_empty() {
149 return Ok(Target::Root(part));
150 }
151 return Ok(Target::Member {
152 name: MemberName::Borrowed(path),
153 part,
154 });
155 }
156 decoded_name(base, path, part, work)
157}
158
159fn decoded_name<'uri, W: Work>(
160 base: &[u8],
161 path: &'uri [u8],
162 part: Part,
163 work: &W,
164) -> Result<Target<'uri>, MemoryError> {
165 let maximum = base
166 .len()
167 .checked_add(path.len())
168 .ok_or(MemoryError::LimitExceeded)?;
169 let mut name = work.vector();
170 name.try_reserve_exact(maximum)?;
171 name.try_extend_from_slice(base)?;
172 let mut rest = path;
173 while let Some(at) = rest.iter().position(|byte| *byte == b'%') {
174 name.try_extend_from_slice(&rest[..at])?;
175 if rest.len() - at < 3 {
176 return Ok(Target::Negative(Negative::Malformed));
177 }
178 let (Some(high), Some(low)) = (hex(rest[at + 1]), hex(rest[at + 2])) else {
179 return Ok(Target::Negative(Negative::Malformed));
180 };
181 name.try_push(high * 16 + low)?;
183 rest = &rest[at + 3..];
184 }
185 name.try_extend_from_slice(rest)?;
186 if core::str::from_utf8(&name).is_err() {
187 return Ok(Target::Negative(Negative::Encoding));
188 }
189 if name.is_empty() {
190 return Ok(Target::Root(part));
191 }
192 Ok(Target::Member {
193 name: MemberName::Owned(name.into_owned()),
194 part,
195 })
196}
197
198fn hex(byte: u8) -> Option<u8> {
199 match byte {
200 b'0'..=b'9' => Some(byte - b'0'),
201 b'a'..=b'f' => Some(byte - b'a' + 10),
202 b'A'..=b'F' => Some(byte - b'A' + 10),
203 _ => None,
204 }
205}
206
207fn lexical(bytes: &[u8]) -> bool {
208 let mut at = 0;
209 while at < bytes.len() {
210 let byte = bytes[at];
211 if byte < 0x20 || byte == 0x7f || byte == b'\\' {
212 return false;
213 }
214 if byte == b'%' {
215 if bytes.len() - at < 3 {
216 return false;
217 }
218 if hex(bytes[at + 1]).is_none() || hex(bytes[at + 2]).is_none() {
219 return false;
220 }
221 at += 3;
222 } else {
223 at += 1;
224 }
225 }
226 true
227}
228
229#[cfg(test)]
230mod tests;