1use std::fmt;
11
12pub const MAX_TABLE_PATH_SEGMENTS: usize = 128;
14
15pub const MAX_TABLE_PATH_TEXT_BYTES: usize = 4096;
17
18pub fn is_legal_table_segment(name: &str) -> bool {
24 !(name.is_empty() || name == "." || name == ".." || name.contains('/') || name.contains('\\'))
25}
26
27#[derive(Clone, Debug, Default, PartialEq, Eq)]
29pub struct TablePath {
30 segments: Vec<String>,
31}
32
33impl TablePath {
34 pub fn new() -> Self {
36 Self::default()
37 }
38
39 pub fn root() -> Self {
41 Self::new()
42 }
43
44 pub fn from_segments<I, S>(segments: I) -> Result<Self, TablePathError>
46 where
47 I: IntoIterator<Item = S>,
48 S: AsRef<str>,
49 {
50 let mut path = Self::new();
51 for segment in segments {
52 path.push(segment.as_ref())?;
53 }
54 Ok(path)
55 }
56
57 pub fn parse_absolute(input: &str) -> Result<Self, TablePathRefError> {
59 let reference = TablePathRef::parse(input)?;
60 if !reference.is_absolute() {
61 return Err(TablePathRefError::ExpectedAbsolute);
62 }
63 reference.resolve(&Self::root())
64 }
65
66 pub fn segments(&self) -> &[String] {
68 &self.segments
69 }
70
71 pub fn is_root(&self) -> bool {
73 self.segments.is_empty()
74 }
75
76 pub fn push(&mut self, segment: &str) -> Result<(), TablePathError> {
78 if !is_legal_table_segment(segment) {
79 return Err(TablePathError::IllegalSegment(segment.to_owned()));
80 }
81 if self.segments.len() == MAX_TABLE_PATH_SEGMENTS {
82 return Err(TablePathError::TooManySegments {
83 limit: MAX_TABLE_PATH_SEGMENTS,
84 });
85 }
86 self.segments.push(segment.to_owned());
87 Ok(())
88 }
89
90 pub fn join(&self) -> String {
92 self.segments.join("/")
93 }
94
95 pub fn to_absolute_reference(&self) -> String {
97 TablePathRef::absolute(self).to_reference_string()
98 }
99
100 pub fn resolve(&self, reference: &TablePathRef) -> Result<Self, TablePathRefError> {
102 reference.resolve(self)
103 }
104}
105
106impl fmt::Display for TablePath {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 f.write_str(&self.to_absolute_reference())
109 }
110}
111
112#[derive(Clone, Debug, PartialEq, Eq)]
114pub enum TablePathError {
115 IllegalSegment(String),
117 TooManySegments {
119 limit: usize,
121 },
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
126pub enum TablePathRefPart {
127 Segment(String),
129 Current,
131 Parent,
133}
134
135#[derive(Clone, Debug, PartialEq, Eq)]
137pub struct TablePathRef {
138 absolute: bool,
139 parts: Vec<TablePathRefPart>,
140}
141
142impl TablePathRef {
143 pub fn new(absolute: bool, parts: Vec<TablePathRefPart>) -> Result<Self, TablePathRefError> {
145 if parts.len() > MAX_TABLE_PATH_SEGMENTS {
146 return Err(TablePathRefError::TooManySegments {
147 limit: MAX_TABLE_PATH_SEGMENTS,
148 });
149 }
150 for part in &parts {
151 if let TablePathRefPart::Segment(segment) = part
152 && !is_legal_table_segment(segment)
153 {
154 return Err(TablePathRefError::IllegalSegment(segment.clone()));
155 }
156 }
157 Ok(Self { absolute, parts })
158 }
159
160 pub fn current() -> Self {
162 Self {
163 absolute: false,
164 parts: vec![TablePathRefPart::Current],
165 }
166 }
167
168 pub fn absolute(path: &TablePath) -> Self {
170 Self {
171 absolute: true,
172 parts: path
173 .segments()
174 .iter()
175 .cloned()
176 .map(TablePathRefPart::Segment)
177 .collect(),
178 }
179 }
180
181 pub fn parse(input: &str) -> Result<Self, TablePathRefError> {
183 if input.is_empty() {
184 return Err(TablePathRefError::EmptyReference);
185 }
186 if input.len() > MAX_TABLE_PATH_TEXT_BYTES {
187 return Err(TablePathRefError::ReferenceTooLong {
188 limit: MAX_TABLE_PATH_TEXT_BYTES,
189 });
190 }
191 if input.as_bytes().contains(&b'\\') {
192 return Err(TablePathRefError::AmbiguousSeparator('\\'));
193 }
194
195 let absolute = input.starts_with('/');
196 let body = if absolute { &input[1..] } else { input };
197 if body.is_empty() {
198 return if absolute {
199 Self::new(true, Vec::new())
200 } else {
201 Err(TablePathRefError::EmptyReference)
202 };
203 }
204
205 let mut parts = Vec::new();
206 for raw in body.split('/') {
207 if raw.is_empty() {
208 return Err(TablePathRefError::EmptySegment);
209 }
210 let segment = decode_segment(raw)?;
211 let part = match segment.as_str() {
212 "." => TablePathRefPart::Current,
213 ".." => TablePathRefPart::Parent,
214 _ if is_legal_table_segment(&segment) => TablePathRefPart::Segment(segment),
215 _ => return Err(TablePathRefError::IllegalSegment(segment)),
216 };
217 parts.push(part);
218 }
219 Self::new(absolute, parts)
220 }
221
222 pub fn is_absolute(&self) -> bool {
224 self.absolute
225 }
226
227 pub fn parts(&self) -> &[TablePathRefPart] {
229 &self.parts
230 }
231
232 pub fn resolve(&self, base: &TablePath) -> Result<TablePath, TablePathRefError> {
234 let mut segments = if self.absolute {
235 Vec::new()
236 } else {
237 base.segments.clone()
238 };
239 for part in &self.parts {
240 match part {
241 TablePathRefPart::Current => {}
242 TablePathRefPart::Parent => {
243 if segments.pop().is_none() {
244 return Err(TablePathRefError::RootEscape);
245 }
246 }
247 TablePathRefPart::Segment(segment) => {
248 if segments.len() == MAX_TABLE_PATH_SEGMENTS {
249 return Err(TablePathRefError::TooManySegments {
250 limit: MAX_TABLE_PATH_SEGMENTS,
251 });
252 }
253 segments.push(segment.clone());
254 }
255 }
256 }
257 Ok(TablePath { segments })
258 }
259
260 pub fn to_reference_string(&self) -> String {
262 if self.absolute && self.parts.is_empty() {
263 return "/".to_owned();
264 }
265 if !self.absolute && self.parts.is_empty() {
266 return ".".to_owned();
267 }
268
269 let mut out = String::new();
270 if self.absolute {
271 out.push('/');
272 }
273 for (index, part) in self.parts.iter().enumerate() {
274 if index > 0 {
275 out.push('/');
276 }
277 match part {
278 TablePathRefPart::Segment(segment) => out.push_str(&encode_segment(segment)),
279 TablePathRefPart::Current => out.push('.'),
280 TablePathRefPart::Parent => out.push_str(".."),
281 }
282 }
283 out
284 }
285}
286
287impl fmt::Display for TablePathRef {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 f.write_str(&self.to_reference_string())
290 }
291}
292
293#[derive(Clone, Debug, PartialEq, Eq)]
295pub enum TablePathRefError {
296 EmptyReference,
298 EmptySegment,
300 AmbiguousSeparator(char),
302 BadEscape {
304 index: usize,
306 },
307 InvalidUtf8Escape,
309 IllegalSegment(String),
311 TooManySegments {
313 limit: usize,
315 },
316 ReferenceTooLong {
318 limit: usize,
320 },
321 ExpectedAbsolute,
323 RootEscape,
325}
326
327fn decode_segment(raw: &str) -> Result<String, TablePathRefError> {
328 let bytes = raw.as_bytes();
329 let mut out = Vec::with_capacity(bytes.len());
330 let mut index = 0;
331 while index < bytes.len() {
332 if bytes[index] == b'%' {
333 if index + 2 >= bytes.len() {
334 return Err(TablePathRefError::BadEscape { index });
335 }
336 let high = hex_value(bytes[index + 1]).ok_or(TablePathRefError::BadEscape { index })?;
337 let low = hex_value(bytes[index + 2]).ok_or(TablePathRefError::BadEscape { index })?;
338 out.push((high << 4) | low);
339 index += 3;
340 } else {
341 out.push(bytes[index]);
342 index += 1;
343 }
344 }
345 String::from_utf8(out).map_err(|_| TablePathRefError::InvalidUtf8Escape)
346}
347
348fn encode_segment(segment: &str) -> String {
349 let mut out = String::new();
350 for byte in segment.bytes() {
351 if is_unreserved_reference_byte(byte) {
352 out.push(char::from(byte));
353 } else {
354 out.push('%');
355 out.push(HEX[(byte >> 4) as usize] as char);
356 out.push(HEX[(byte & 0x0F) as usize] as char);
357 }
358 }
359 out
360}
361
362fn hex_value(byte: u8) -> Option<u8> {
363 match byte {
364 b'0'..=b'9' => Some(byte - b'0'),
365 b'a'..=b'f' => Some(byte - b'a' + 10),
366 b'A'..=b'F' => Some(byte - b'A' + 10),
367 _ => None,
368 }
369}
370
371fn is_unreserved_reference_byte(byte: u8) -> bool {
372 byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'~')
373}
374
375const HEX: &[u8; 16] = b"0123456789ABCDEF";