1use std::time::{Duration, SystemTime};
4
5use http::header::{self, HeaderMap, HeaderValue};
6use http::uri::Authority;
7use http::{StatusCode, Uri};
8use percent_encoding::percent_decode_str;
9
10use crate::{
11 DavBackendError, DavFileSystem, DavIfResourceState, DavIfStateResolver, DavLockSystem, DavPath,
12 FsError,
13};
14use aster_forge_utils::http_validators;
15use async_trait::async_trait;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Depth {
20 Zero,
22 One,
24 Infinity,
26}
27
28impl Depth {
29 #[must_use]
31 pub fn is_infinity(self) -> bool {
32 matches!(self, Self::Infinity)
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Destination {
39 pub path: DavPath,
41 pub relative: String,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct IfHeader {
48 pub groups: Vec<IfResourceGroup>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct IfResourceGroup {
55 pub tagged_path: Option<String>,
57 pub lists: Vec<IfStateList>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct IfStateList {
64 pub conditions: Vec<IfStateCondition>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum IfStateCondition {
71 Token { value: String, negated: bool },
73 Etag { value: String, negated: bool },
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
79pub enum DavIfEvaluationError {
80 #[error(transparent)]
81 Protocol(#[from] DavProtocolError),
82 #[error(transparent)]
83 Backend(#[from] DavBackendError),
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum DavPrecondition {
89 Proceed,
91 NotModified,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum DavProtocolErrorKind {
98 BadRequest,
99 PreconditionFailed,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
104#[error("{message}")]
105pub struct DavProtocolError {
106 kind: DavProtocolErrorKind,
107 status: StatusCode,
108 message: &'static str,
109}
110
111impl DavProtocolError {
112 #[must_use]
114 pub fn kind(&self) -> DavProtocolErrorKind {
115 self.kind
116 }
117
118 #[must_use]
120 pub fn status(&self) -> StatusCode {
121 self.status
122 }
123
124 #[must_use]
126 pub fn message(&self) -> &'static str {
127 self.message
128 }
129
130 pub(crate) fn bad_request(message: &'static str) -> Self {
131 Self {
132 kind: DavProtocolErrorKind::BadRequest,
133 status: StatusCode::BAD_REQUEST,
134 message,
135 }
136 }
137
138 fn precondition_failed() -> Self {
139 Self {
140 kind: DavProtocolErrorKind::PreconditionFailed,
141 status: StatusCode::PRECONDITION_FAILED,
142 message: "Precondition failed",
143 }
144 }
145}
146
147pub fn parse_propfind_depth(headers: &HeaderMap) -> Result<Depth, DavProtocolError> {
149 match parse_depth_header(headers)? {
150 Some(Depth::Zero) => Ok(Depth::Zero),
151 Some(Depth::One) => Ok(Depth::One),
152 Some(Depth::Infinity) | None => Ok(Depth::Infinity),
153 }
154}
155
156pub fn parse_copy_depth(headers: &HeaderMap) -> Result<Depth, DavProtocolError> {
158 match parse_depth_header(headers)? {
159 Some(Depth::Zero) => Ok(Depth::Zero),
160 Some(Depth::Infinity) | None => Ok(Depth::Infinity),
161 Some(Depth::One) => Err(DavProtocolError::bad_request("Invalid Depth header")),
162 }
163}
164
165pub fn parse_move_depth(headers: &HeaderMap) -> Result<Depth, DavProtocolError> {
167 Ok(parse_depth_header(headers)?.unwrap_or(Depth::Infinity))
168}
169
170pub fn parse_delete_depth(headers: &HeaderMap) -> Result<Depth, DavProtocolError> {
172 Ok(parse_depth_header(headers)?.unwrap_or(Depth::Infinity))
173}
174
175pub fn parse_lock_depth(headers: &HeaderMap) -> Result<Depth, DavProtocolError> {
177 match parse_depth_header(headers)? {
178 None | Some(Depth::Infinity) => Ok(Depth::Infinity),
179 Some(Depth::Zero) => Ok(Depth::Zero),
180 Some(Depth::One) => Err(DavProtocolError::bad_request("Invalid Depth header")),
181 }
182}
183
184fn parse_depth_header(headers: &HeaderMap) -> Result<Option<Depth>, DavProtocolError> {
185 let Some(value) = headers.get("Depth") else {
186 return Ok(None);
187 };
188 let value = value
189 .to_str()
190 .map_err(|_| DavProtocolError::bad_request("Invalid Depth header"))?;
191
192 match value {
193 value if value.eq_ignore_ascii_case("0") => Ok(Some(Depth::Zero)),
194 value if value.eq_ignore_ascii_case("1") => Ok(Some(Depth::One)),
195 value if value.eq_ignore_ascii_case("infinity") => Ok(Some(Depth::Infinity)),
196 _ => Err(DavProtocolError::bad_request("Invalid Depth header")),
197 }
198}
199
200pub fn parse_overwrite(headers: &HeaderMap) -> Result<bool, DavProtocolError> {
202 let Some(value) = headers.get("Overwrite") else {
203 return Ok(true);
204 };
205 let value = value
206 .to_str()
207 .map_err(|_| DavProtocolError::bad_request("Invalid Overwrite header"))?
208 .trim();
209 if value.eq_ignore_ascii_case("T") {
210 Ok(true)
211 } else if value.eq_ignore_ascii_case("F") {
212 Ok(false)
213 } else {
214 Err(DavProtocolError::bad_request("Invalid Overwrite header"))
215 }
216}
217
218pub fn destination_relative_path(
220 headers: &HeaderMap,
221 prefix: &str,
222 request_scheme: &str,
223 request_host: &str,
224) -> Result<Destination, DavProtocolError> {
225 let raw = headers
226 .get("Destination")
227 .ok_or_else(|| DavProtocolError::bad_request("Missing Destination header"))?
228 .to_str()
229 .map_err(|_| DavProtocolError::bad_request("Invalid Destination header"))?
230 .trim();
231 let uri: Uri = raw
232 .parse()
233 .map_err(|_| DavProtocolError::bad_request("Invalid Destination header"))?;
234 match (uri.scheme_str(), uri.authority()) {
235 (Some(scheme), Some(authority)) => {
236 if !origin_authority_matches(scheme, authority, request_scheme, request_host) {
237 return Err(DavProtocolError::bad_request(
238 "Destination must stay on this WebDAV server",
239 ));
240 }
241 }
242 (None, None) => {
243 if !raw.starts_with('/') {
244 return Err(DavProtocolError::bad_request("Invalid Destination header"));
245 }
246 }
247 _ => return Err(DavProtocolError::bad_request("Invalid Destination header")),
248 }
249
250 let path = uri.path();
251 let relative = strip_mount_prefix(path, prefix).ok_or_else(|| {
252 DavProtocolError::bad_request("Destination must stay under WebDAV prefix")
253 })?;
254 let path = DavPath::new(relative)
255 .map_err(|_| DavProtocolError::bad_request("Invalid Destination header"))?;
256 let relative = path.as_str().to_string();
257 Ok(Destination { path, relative })
258}
259
260pub fn parse_if_header(headers: &HeaderMap) -> Result<Option<IfHeader>, DavProtocolError> {
262 let Some(value) = headers.get("If") else {
263 return Ok(None);
264 };
265 let raw = value
266 .to_str()
267 .map_err(|_| DavProtocolError::bad_request("Invalid If header"))?;
268 IfHeaderParser::new(raw).parse().map(Some)
269}
270
271pub async fn enforce_if_header(
276 if_header: Option<&IfHeader>,
277 resolver: &dyn DavIfStateResolver,
278 request_path: &DavPath,
279 prefix: &str,
280 request_scheme: &str,
281 request_host: &str,
282) -> Result<(), DavIfEvaluationError> {
283 let Some(if_header) = if_header else {
284 return Ok(());
285 };
286
287 for group in &if_header.groups {
288 let path = match group.tagged_path.as_deref() {
289 Some(tagged_path) => {
290 tagged_dav_path(prefix, tagged_path, request_scheme, request_host)?
291 }
292 None => Some(request_path.clone()),
293 };
294 let state = match path.as_ref() {
295 Some(path) => resolver.resolve_if_state(path).await?,
296 None => DavIfResourceState::default(),
297 };
298 if group
299 .lists
300 .iter()
301 .any(|list| evaluate_if_state_list(list, &state))
302 {
303 return Ok(());
304 }
305 }
306 Err(DavProtocolError::precondition_failed().into())
307}
308
309pub async fn enforce_if_header_with_backends(
315 if_header: Option<&IfHeader>,
316 filesystem: &dyn DavFileSystem,
317 lock_system: &dyn DavLockSystem,
318 request_path: &DavPath,
319 prefix: &str,
320 request_scheme: &str,
321 request_host: &str,
322) -> Result<(), DavIfEvaluationError> {
323 let resolver = BackendIfStateResolver {
324 filesystem,
325 lock_system,
326 };
327 enforce_if_header(
328 if_header,
329 &resolver,
330 request_path,
331 prefix,
332 request_scheme,
333 request_host,
334 )
335 .await
336}
337
338struct BackendIfStateResolver<'a> {
339 filesystem: &'a dyn DavFileSystem,
340 lock_system: &'a dyn DavLockSystem,
341}
342
343#[async_trait]
344impl DavIfStateResolver for BackendIfStateResolver<'_> {
345 async fn resolve_if_state(
346 &self,
347 path: &DavPath,
348 ) -> Result<DavIfResourceState, DavBackendError> {
349 let etag = match self.filesystem.metadata(path).await {
350 Ok(metadata) => metadata.etag(),
351 Err(FsError::NotFound) => None,
352 Err(error) => return Err(error.into()),
353 };
354 let lock_tokens = self
355 .lock_system
356 .discover(path)
357 .await
358 .into_iter()
359 .map(|lock| lock.token)
360 .collect();
361 Ok(DavIfResourceState { etag, lock_tokens })
362 }
363}
364
365fn evaluate_if_state_list(list: &IfStateList, state: &DavIfResourceState) -> bool {
366 list.conditions.iter().all(|condition| match condition {
367 IfStateCondition::Token { value, negated } => {
368 state.lock_tokens.iter().any(|token| token == value) ^ *negated
369 }
370 IfStateCondition::Etag { value, negated } => {
371 state.etag.as_deref().is_some_and(|etag| {
372 http_validators::if_none_match_header_matches(value, true, Some(etag))
373 .unwrap_or(false)
374 }) ^ *negated
375 }
376 })
377}
378
379fn tagged_dav_path(
380 prefix: &str,
381 tagged_path: &str,
382 request_scheme: &str,
383 request_host: &str,
384) -> Result<Option<DavPath>, DavProtocolError> {
385 let uri: Uri = tagged_path
386 .parse()
387 .map_err(|_| DavProtocolError::bad_request("Invalid If header"))?;
388 let path = match (uri.scheme_str(), uri.authority()) {
389 (Some(scheme), Some(authority)) => {
390 if !origin_authority_matches(scheme, authority, request_scheme, request_host) {
391 return Ok(None);
392 }
393 uri.path()
394 }
395 (None, None) => uri.path(),
396 _ => return Err(DavProtocolError::bad_request("Invalid If header")),
397 };
398 if !path.starts_with('/') {
399 return Err(DavProtocolError::bad_request("Invalid If header"));
400 }
401 let Some(relative) = strip_mount_prefix(path, prefix) else {
402 return Ok(None);
403 };
404 DavPath::new(relative)
405 .map(Some)
406 .map_err(|_| DavProtocolError::bad_request("Invalid If header"))
407}
408
409pub fn submitted_lock_tokens_for_path(
411 headers: &HeaderMap,
412 request_path: &str,
413 request_scheme: &str,
414 request_host: &str,
415) -> Vec<String> {
416 let Some(if_header) = parse_if_header(headers).ok().flatten() else {
417 return Vec::new();
418 };
419 submitted_lock_tokens(&if_header, request_path, request_scheme, request_host)
420}
421
422#[must_use]
424pub fn submitted_lock_tokens(
425 if_header: &IfHeader,
426 request_path: &str,
427 request_scheme: &str,
428 request_host: &str,
429) -> Vec<String> {
430 let mut tokens = Vec::new();
431 for group in &if_header.groups {
432 match group.tagged_path.as_deref() {
433 None => {}
434 Some(tagged_path)
435 if if_tag_matches_path(tagged_path, request_path, request_scheme, request_host) => {
436 }
437 Some(_) => continue,
438 }
439 for list in &group.lists {
440 for condition in &list.conditions {
441 if let IfStateCondition::Token { value, .. } = condition {
442 tokens.push(value.clone());
443 }
444 }
445 }
446 }
447 tokens.sort();
448 tokens.dedup();
449 tokens
450}
451
452pub fn parse_lock_timeout(
454 headers: &HeaderMap,
455 maximum: Duration,
456) -> Result<Duration, DavProtocolError> {
457 let Some(value) = headers.get("Timeout") else {
458 return Ok(maximum);
459 };
460 let raw = value
461 .to_str()
462 .map_err(|_| DavProtocolError::bad_request("Invalid Timeout header"))?;
463 for candidate in raw
464 .split(',')
465 .map(str::trim)
466 .filter(|value| !value.is_empty())
467 {
468 if candidate.eq_ignore_ascii_case("Infinite") {
469 return Ok(maximum);
470 }
471 if let Some(seconds) = candidate
472 .strip_prefix("Second-")
473 .and_then(|seconds| seconds.parse::<u64>().ok())
474 {
475 return Ok(Duration::from_secs(seconds).min(maximum));
476 }
477 }
478 Err(DavProtocolError::bad_request("Invalid Timeout header"))
479}
480
481pub fn parse_lock_token_header(headers: &HeaderMap) -> Result<String, DavProtocolError> {
483 let raw = headers
484 .get("Lock-Token")
485 .ok_or_else(|| DavProtocolError::bad_request("Missing Lock-Token header"))?
486 .to_str()
487 .map_err(|_| DavProtocolError::bad_request("Invalid Lock-Token header"))?
488 .trim();
489 let token = raw
490 .strip_prefix('<')
491 .and_then(|value| value.strip_suffix('>'))
492 .filter(|token| !token.is_empty() && !token.contains(['<', '>']))
493 .ok_or_else(|| DavProtocolError::bad_request("Invalid Lock-Token header"))?;
494 Ok(token.to_owned())
495}
496
497pub fn evaluate_http_etag_preconditions(
499 headers: &HeaderMap,
500 resource_exists: bool,
501 current_etag: Option<&str>,
502 safe_method: bool,
503) -> Result<DavPrecondition, DavProtocolError> {
504 if let Some(value) = headers.get(header::IF_MATCH) {
505 let raw = value
506 .to_str()
507 .map_err(|_| DavProtocolError::bad_request("Invalid If-Match header"))?;
508 if !http_validators::if_match_header_matches(raw, resource_exists, current_etag)
509 .map_err(|_| DavProtocolError::bad_request("Invalid If-Match header"))?
510 {
511 return Err(DavProtocolError::precondition_failed());
512 }
513 }
514
515 if let Some(value) = headers.get(header::IF_NONE_MATCH) {
516 let raw = value
517 .to_str()
518 .map_err(|_| DavProtocolError::bad_request("Invalid If-None-Match header"))?;
519 if http_validators::if_none_match_header_matches(raw, resource_exists, current_etag)
520 .map_err(|_| DavProtocolError::bad_request("Invalid If-None-Match header"))?
521 {
522 return if safe_method {
523 Ok(DavPrecondition::NotModified)
524 } else {
525 Err(DavProtocolError::precondition_failed())
526 };
527 }
528 }
529
530 Ok(DavPrecondition::Proceed)
531}
532
533pub fn evaluate_http_download_preconditions(
535 headers: &HeaderMap,
536 current_etag: Option<&str>,
537 last_modified: Option<SystemTime>,
538) -> Result<DavPrecondition, DavProtocolError> {
539 let has_if_match = headers.contains_key(header::IF_MATCH);
540 if let Some(value) = headers.get(header::IF_MATCH) {
541 let raw = value
542 .to_str()
543 .map_err(|_| DavProtocolError::bad_request("Invalid If-Match header"))?;
544 if !http_validators::if_match_header_matches(raw, true, current_etag)
545 .map_err(|_| DavProtocolError::bad_request("Invalid If-Match header"))?
546 {
547 return Err(DavProtocolError::precondition_failed());
548 }
549 }
550
551 if !has_if_match
552 && let (Some(value), Some(last_modified)) =
553 (headers.get(header::IF_UNMODIFIED_SINCE), last_modified)
554 {
555 let since = parse_http_date_header(value, "Invalid If-Unmodified-Since header")?;
556 if http_validators::http_date_epoch_seconds(last_modified)
557 > http_validators::http_date_epoch_seconds(since)
558 {
559 return Err(DavProtocolError::precondition_failed());
560 }
561 }
562
563 let has_if_none_match = headers.contains_key(header::IF_NONE_MATCH);
564 if let Some(value) = headers.get(header::IF_NONE_MATCH) {
565 let raw = value
566 .to_str()
567 .map_err(|_| DavProtocolError::bad_request("Invalid If-None-Match header"))?;
568 if http_validators::if_none_match_header_matches(raw, true, current_etag)
569 .map_err(|_| DavProtocolError::bad_request("Invalid If-None-Match header"))?
570 {
571 return Ok(DavPrecondition::NotModified);
572 }
573 }
574
575 if !has_if_none_match
576 && let (Some(value), Some(last_modified)) =
577 (headers.get(header::IF_MODIFIED_SINCE), last_modified)
578 {
579 let since = parse_http_date_header(value, "Invalid If-Modified-Since header")?;
580 if http_validators::http_date_epoch_seconds(last_modified)
581 <= http_validators::http_date_epoch_seconds(since)
582 {
583 return Ok(DavPrecondition::NotModified);
584 }
585 }
586
587 Ok(DavPrecondition::Proceed)
588}
589
590fn parse_http_date_header(
591 value: &HeaderValue,
592 invalid_message: &'static str,
593) -> Result<SystemTime, DavProtocolError> {
594 let raw = value
595 .to_str()
596 .map_err(|_| DavProtocolError::bad_request(invalid_message))?;
597 http_validators::parse_http_date(raw)
598 .map_err(|_| DavProtocolError::bad_request(invalid_message))
599}
600
601fn origin_authority_matches(
602 uri_scheme: &str,
603 uri_authority: &Authority,
604 request_scheme: &str,
605 request_host: &str,
606) -> bool {
607 if !uri_scheme.eq_ignore_ascii_case(request_scheme) {
608 return false;
609 }
610 let Ok(request_authority) = request_host.parse::<Authority>() else {
611 return false;
612 };
613 if uri_authority.as_str().contains('@') || request_authority.as_str().contains('@') {
614 return false;
615 }
616 uri_authority
617 .host()
618 .eq_ignore_ascii_case(request_authority.host())
619 && effective_port(uri_scheme, uri_authority)
620 == effective_port(request_scheme, &request_authority)
621}
622
623fn effective_port(scheme: &str, authority: &Authority) -> Option<u16> {
624 authority.port_u16().or_else(|| {
625 if scheme.eq_ignore_ascii_case("http") {
626 Some(80)
627 } else if scheme.eq_ignore_ascii_case("https") {
628 Some(443)
629 } else {
630 None
631 }
632 })
633}
634
635pub(crate) fn strip_mount_prefix<'a>(path: &'a str, prefix: &str) -> Option<&'a str> {
636 path.strip_prefix(prefix).filter(|_| {
637 prefix == "/"
638 || path == prefix
639 || path
640 .as_bytes()
641 .get(prefix.len())
642 .is_some_and(|byte| *byte == b'/')
643 })
644}
645
646fn normalize_lock_token(value: &str) -> String {
647 value
648 .trim()
649 .trim_matches(|character| character == '<' || character == '>')
650 .to_string()
651}
652
653struct IfHeaderParser<'a> {
654 input: &'a str,
655 position: usize,
656}
657
658impl<'a> IfHeaderParser<'a> {
659 fn new(input: &'a str) -> Self {
660 Self { input, position: 0 }
661 }
662
663 fn parse(&mut self) -> Result<IfHeader, DavProtocolError> {
664 self.skip_linear_whitespace();
665 if self.is_eof() {
666 return Err(DavProtocolError::bad_request("Invalid If header"));
667 }
668
669 let tagged = self.peek_char() == Some('<');
670 let mut groups = Vec::new();
671 if tagged {
672 while !self.is_eof() {
673 let tagged_path = self.parse_angle_value()?;
674 let mut lists = Vec::new();
675 loop {
676 self.skip_linear_whitespace();
677 if self.peek_char() != Some('(') {
678 break;
679 }
680 lists.push(self.parse_state_list()?);
681 }
682 if lists.is_empty() {
683 return Err(DavProtocolError::bad_request("Invalid If header"));
684 }
685 groups.push(IfResourceGroup {
686 tagged_path: Some(tagged_path),
687 lists,
688 });
689 self.skip_linear_whitespace();
690 if self.is_eof() {
691 break;
692 }
693 if self.peek_char() != Some('<') {
694 return Err(DavProtocolError::bad_request("Invalid If header"));
695 }
696 }
697 } else {
698 let mut lists = Vec::new();
699 while !self.is_eof() {
700 lists.push(self.parse_state_list()?);
701 self.skip_linear_whitespace();
702 if self.peek_char() == Some('<') {
703 return Err(DavProtocolError::bad_request("Invalid If header"));
704 }
705 }
706 groups.push(IfResourceGroup {
707 tagged_path: None,
708 lists,
709 });
710 }
711 Ok(IfHeader { groups })
712 }
713
714 fn parse_state_list(&mut self) -> Result<IfStateList, DavProtocolError> {
715 self.expect_char('(')?;
716 let mut conditions = Vec::new();
717 loop {
718 self.skip_linear_whitespace();
719 if self.peek_char() == Some(')') {
720 self.position += 1;
721 break;
722 }
723 if self.is_eof() {
724 return Err(DavProtocolError::bad_request("Invalid If header"));
725 }
726
727 let negated = self.consume_not();
728 self.skip_linear_whitespace();
729 let condition = match self.peek_char() {
730 Some('<') => IfStateCondition::Token {
731 value: normalize_lock_token(&self.parse_angle_value()?),
732 negated,
733 },
734 Some('[') => IfStateCondition::Etag {
735 value: self.parse_bracket_value()?,
736 negated,
737 },
738 _ => return Err(DavProtocolError::bad_request("Invalid If header")),
739 };
740 conditions.push(condition);
741 }
742
743 if conditions.is_empty() {
744 return Err(DavProtocolError::bad_request("Invalid If header"));
745 }
746 Ok(IfStateList { conditions })
747 }
748
749 fn parse_angle_value(&mut self) -> Result<String, DavProtocolError> {
750 self.parse_delimited('<', '>')
751 }
752
753 fn parse_bracket_value(&mut self) -> Result<String, DavProtocolError> {
754 self.parse_delimited('[', ']')
755 }
756
757 fn parse_delimited(
758 &mut self,
759 opening: char,
760 closing: char,
761 ) -> Result<String, DavProtocolError> {
762 self.expect_char(opening)?;
763 let start = self.position;
764 while let Some(character) = self.peek_char() {
765 if character == closing {
766 let value = self.input[start..self.position].trim();
767 self.position += closing.len_utf8();
768 if value.is_empty() {
769 return Err(DavProtocolError::bad_request("Invalid If header"));
770 }
771 return Ok(value.to_string());
772 }
773 self.position += character.len_utf8();
774 }
775 Err(DavProtocolError::bad_request("Invalid If header"))
776 }
777
778 fn consume_not(&mut self) -> bool {
779 let rest = &self.input[self.position..];
780 let Some(candidate) = rest.get(..3) else {
781 return false;
782 };
783 if !candidate.eq_ignore_ascii_case("not") {
784 return false;
785 }
786 let after_not = &rest[3..];
787 if after_not.chars().next().is_some_and(|character| {
788 !character.is_ascii_whitespace() && character != '<' && character != '['
789 }) {
790 return false;
791 }
792 self.position += 3;
793 true
794 }
795
796 fn expect_char(&mut self, expected: char) -> Result<(), DavProtocolError> {
797 if self.peek_char() == Some(expected) {
798 self.position += expected.len_utf8();
799 Ok(())
800 } else {
801 Err(DavProtocolError::bad_request("Invalid If header"))
802 }
803 }
804
805 fn skip_linear_whitespace(&mut self) {
806 while self
807 .peek_char()
808 .is_some_and(|character| matches!(character, ' ' | '\t' | '\r' | '\n'))
809 {
810 self.position += 1;
811 }
812 }
813
814 fn peek_char(&self) -> Option<char> {
815 self.input[self.position..].chars().next()
816 }
817
818 fn is_eof(&self) -> bool {
819 self.position >= self.input.len()
820 }
821}
822
823fn if_tag_matches_path(
824 tagged_path: &str,
825 request_path: &str,
826 request_scheme: &str,
827 request_host: &str,
828) -> bool {
829 if path_equivalent(tagged_path, request_path) {
830 return true;
831 }
832 let Ok(uri) = tagged_path.parse::<Uri>() else {
833 return false;
834 };
835 match (uri.scheme_str(), uri.authority()) {
836 (Some(scheme), Some(authority)) => {
837 origin_authority_matches(scheme, authority, request_scheme, request_host)
838 && path_equivalent(uri.path(), request_path)
839 }
840 (None, None) => path_equivalent(uri.path(), request_path),
841 _ => false,
842 }
843}
844
845fn path_equivalent(left: &str, right: &str) -> bool {
846 if left == right {
847 return true;
848 }
849 let left_decoded = percent_decode_str(left).decode_utf8().ok();
850 let right_decoded = percent_decode_str(right).decode_utf8().ok();
851 match (left_decoded.as_deref(), right_decoded.as_deref()) {
852 (Some(left), Some(right)) => left == right,
853 (Some(left), None) => left == right,
854 (None, Some(right)) => left == right,
855 (None, None) => false,
856 }
857}