Skip to main content

dav_server/
davheaders.rs

1use std::convert::TryFrom;
2use std::fmt::Display;
3use std::str::FromStr;
4
5use headers::Header;
6use http::header::{HeaderName, HeaderValue};
7use url::Url;
8
9use crate::fs::DavMetaData;
10
11pub static DEPTH: HeaderName = HeaderName::from_static("depth");
12pub static TIMEOUT: HeaderName = HeaderName::from_static("timeout");
13pub static OVERWRITE: HeaderName = HeaderName::from_static("overwrite");
14pub static DESTINATION: HeaderName = HeaderName::from_static("destination");
15pub static ETAG: HeaderName = HeaderName::from_static("etag");
16pub static IF_RANGE: HeaderName = HeaderName::from_static("if-range");
17pub static IF_MATCH: HeaderName = HeaderName::from_static("if-match");
18pub static IF_NONE_MATCH: HeaderName = HeaderName::from_static("if-none-match");
19pub static X_UPDATE_RANGE: HeaderName = HeaderName::from_static("x-update-range");
20pub static IF: HeaderName = HeaderName::from_static("if");
21pub static CONTENT_LANGUAGE: HeaderName = HeaderName::from_static("content-language");
22
23// helper.
24fn one<'i, I>(values: &mut I) -> Result<&'i HeaderValue, headers::Error>
25where
26    I: Iterator<Item = &'i HeaderValue>,
27{
28    let v = values.next().ok_or_else(invalid)?;
29    if values.next().is_some() {
30        Err(invalid())
31    } else {
32        Ok(v)
33    }
34}
35
36// helper
37fn invalid() -> headers::Error {
38    headers::Error::invalid()
39}
40
41// helper
42fn map_invalid(_e: impl std::error::Error) -> headers::Error {
43    headers::Error::invalid()
44}
45
46macro_rules! header {
47    ($tname:ident, $hname:ident, $sname:expr_2021) => {
48        pub static $hname: HeaderName = HeaderName::from_static($sname);
49
50        #[derive(Debug, Clone, PartialEq)]
51        pub struct $tname(pub String);
52
53        impl Header for $tname {
54            fn name() -> &'static HeaderName {
55                &$hname
56            }
57
58            fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
59            where
60                I: Iterator<Item = &'i HeaderValue>,
61            {
62                one(values)?
63                    .to_str()
64                    .map(|x| $tname(x.to_owned()))
65                    .map_err(map_invalid)
66            }
67
68            fn encode<E>(&self, values: &mut E)
69            where
70                E: Extend<HeaderValue>,
71            {
72                let value = HeaderValue::from_str(&self.0).unwrap();
73                values.extend(std::iter::once(value))
74            }
75        }
76    };
77}
78
79header!(ContentType, CONTENT_TYPE, "content-type");
80header!(ContentLocation, CONTENT_LOCATION, "content-location");
81header!(LockToken, LOCK_TOKEN, "lock-token");
82header!(XLitmus, X_LITMUS, "x-litmus");
83
84/// - "Depth" header for PROPFIND requests. See the items for its response behaviour
85#[derive(Debug, Copy, Clone, PartialEq)]
86pub enum Depth {
87    /// DEFAULT / no Depth header: no target resource, Depth 1 children
88    Default,
89    /// Depth 0: only target resource, no children
90    Zero,
91    /// Depth 1: target resource, Depth 1 children
92    One,
93    /// Infinite depth or `Depth` > 1 are not to be supported and return `NotImplemented` for performance reasons
94    Infinity,
95}
96
97impl Header for Depth {
98    fn name() -> &'static HeaderName {
99        &DEPTH
100    }
101
102    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
103    where
104        I: Iterator<Item = &'i HeaderValue>,
105    {
106        let value = one(values)?;
107        match value.as_bytes() {
108            b"0" => Ok(Depth::Zero),
109            b"1" => Ok(Depth::One),
110            b"infinity" | b"Infinity" => Ok(Depth::Infinity),
111            _ => Err(invalid()),
112        }
113    }
114
115    fn encode<E>(&self, values: &mut E)
116    where
117        E: Extend<HeaderValue>,
118    {
119        let value = match *self {
120            Depth::Default => "",
121            Depth::Zero => "0",
122            Depth::One => "1",
123            Depth::Infinity => "Infinity",
124        };
125        values.extend(std::iter::once(HeaderValue::from_static(value)));
126    }
127}
128
129/// Content-Language header.
130#[derive(Debug, Clone, PartialEq)]
131pub struct ContentLanguage(headers::Vary);
132
133impl ContentLanguage {
134    #[allow(dead_code)]
135    pub fn iter_langs(&self) -> impl Iterator<Item = &str> {
136        self.0.iter_strs()
137    }
138}
139
140impl TryFrom<&str> for ContentLanguage {
141    type Error = headers::Error;
142
143    fn try_from(value: &str) -> Result<Self, Self::Error> {
144        let value = HeaderValue::from_str(value).map_err(map_invalid)?;
145        let mut values = std::iter::once(&value);
146        ContentLanguage::decode(&mut values)
147    }
148}
149
150impl Header for ContentLanguage {
151    fn name() -> &'static HeaderName {
152        &CONTENT_LANGUAGE
153    }
154
155    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
156    where
157        I: Iterator<Item = &'i HeaderValue>,
158    {
159        let h = headers::Vary::decode(values)?;
160        for lang in h.iter_strs() {
161            let lang = lang.as_bytes();
162            // **VERY** rudimentary check ...
163            let ok = lang.len() == 2 || (lang.len() > 4 && lang[2] == b'-');
164            if !ok {
165                return Err(invalid());
166            }
167        }
168        Ok(ContentLanguage(h))
169    }
170
171    fn encode<E>(&self, values: &mut E)
172    where
173        E: Extend<HeaderValue>,
174    {
175        self.0.encode(values)
176    }
177}
178
179#[derive(Debug, Clone, PartialEq)]
180pub enum DavTimeout {
181    Seconds(u32),
182    Infinite,
183}
184
185#[derive(Debug, Clone)]
186pub struct Timeout(pub Vec<DavTimeout>);
187
188impl Header for Timeout {
189    fn name() -> &'static HeaderName {
190        &TIMEOUT
191    }
192
193    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
194    where
195        I: Iterator<Item = &'i HeaderValue>,
196    {
197        let value = one(values)?;
198        let mut v = Vec::new();
199        let words = value.to_str().map_err(map_invalid)?.split(',');
200        for word in words {
201            let w = match word {
202                "Infinite" => DavTimeout::Infinite,
203                _ if word.starts_with("Second-") => {
204                    let num = &word[7..];
205                    match num.parse::<u32>() {
206                        Err(_) => return Err(invalid()),
207                        Ok(n) => DavTimeout::Seconds(n),
208                    }
209                }
210                _ => return Err(invalid()),
211            };
212            v.push(w);
213        }
214        Ok(Timeout(v))
215    }
216
217    fn encode<E>(&self, values: &mut E)
218    where
219        E: Extend<HeaderValue>,
220    {
221        let mut first = false;
222        let mut value = String::new();
223        for s in &self.0 {
224            if !first {
225                value.push_str(", ");
226            }
227            first = false;
228            match *s {
229                DavTimeout::Seconds(n) => value.push_str(&format!("Second-{n}")),
230                DavTimeout::Infinite => value.push_str("Infinite"),
231            }
232        }
233        values.extend(std::iter::once(HeaderValue::from_str(&value).unwrap()));
234    }
235}
236
237#[derive(Debug, Clone, PartialEq)]
238pub struct Destination(pub String);
239
240impl Header for Destination {
241    fn name() -> &'static HeaderName {
242        &DESTINATION
243    }
244
245    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
246    where
247        I: Iterator<Item = &'i HeaderValue>,
248    {
249        let s = one(values)?.to_str().map_err(map_invalid)?;
250        if s.starts_with('/') {
251            return Ok(Destination(s.to_string()));
252        }
253        if let Ok(url) = s.parse::<Url>() {
254            return Ok(Destination(url.path().to_string()));
255        }
256        Err(invalid())
257    }
258
259    fn encode<E>(&self, values: &mut E)
260    where
261        E: Extend<HeaderValue>,
262    {
263        values.extend(std::iter::once(HeaderValue::from_str(&self.0).unwrap()));
264    }
265}
266
267#[derive(Debug, Clone, PartialEq)]
268pub struct Overwrite(pub bool);
269
270impl Header for Overwrite {
271    fn name() -> &'static HeaderName {
272        &OVERWRITE
273    }
274
275    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
276    where
277        I: Iterator<Item = &'i HeaderValue>,
278    {
279        let line = one(values)?;
280        match line.as_bytes() {
281            b"F" => Ok(Overwrite(false)),
282            b"T" => Ok(Overwrite(true)),
283            _ => Err(invalid()),
284        }
285    }
286
287    fn encode<E>(&self, values: &mut E)
288    where
289        E: Extend<HeaderValue>,
290    {
291        let value = match self.0 {
292            true => "T",
293            false => "F",
294        };
295        values.extend(std::iter::once(HeaderValue::from_static(value)));
296    }
297}
298
299#[derive(Debug, Clone)]
300pub struct ETag {
301    tag: String,
302    weak: bool,
303}
304
305impl ETag {
306    #[allow(dead_code)]
307    pub fn new(weak: bool, t: impl Into<String>) -> Result<ETag, headers::Error> {
308        let t = t.into();
309        if t.contains('\"') {
310            Err(invalid())
311        } else {
312            let w = if weak { "W/" } else { "" };
313            Ok(ETag {
314                tag: format!("{w}\"{t}\""),
315                weak,
316            })
317        }
318    }
319
320    pub fn from_meta(meta: &dyn DavMetaData) -> Option<ETag> {
321        let tag = meta.etag()?;
322        Some(ETag {
323            tag: format!("\"{tag}\""),
324            weak: false,
325        })
326    }
327
328    #[allow(dead_code)]
329    pub fn is_weak(&self) -> bool {
330        self.weak
331    }
332}
333
334impl FromStr for ETag {
335    type Err = headers::Error;
336
337    fn from_str(t: &str) -> Result<Self, Self::Err> {
338        let (weak, s) = if let Some(t) = t.strip_prefix("W/") {
339            (true, t)
340        } else {
341            (false, t)
342        };
343        if s.starts_with('\"') && s.ends_with('\"') && !s[1..s.len() - 1].contains('\"') {
344            Ok(ETag {
345                tag: t.to_owned(),
346                weak,
347            })
348        } else {
349            Err(invalid())
350        }
351    }
352}
353
354impl TryFrom<&HeaderValue> for ETag {
355    type Error = headers::Error;
356
357    fn try_from(value: &HeaderValue) -> Result<Self, Self::Error> {
358        let s = value.to_str().map_err(map_invalid)?;
359        ETag::from_str(s)
360    }
361}
362
363impl Display for ETag {
364    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
365        write!(f, "\"{}\"", self.tag)
366    }
367}
368
369impl PartialEq for ETag {
370    fn eq(&self, other: &Self) -> bool {
371        !self.weak && !other.weak && self.tag == other.tag
372    }
373}
374
375impl Header for ETag {
376    fn name() -> &'static HeaderName {
377        &ETAG
378    }
379
380    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
381    where
382        I: Iterator<Item = &'i HeaderValue>,
383    {
384        let value = one(values)?;
385        ETag::try_from(value)
386    }
387
388    fn encode<E>(&self, values: &mut E)
389    where
390        E: Extend<HeaderValue>,
391    {
392        values.extend(std::iter::once(HeaderValue::from_str(&self.tag).unwrap()));
393    }
394}
395
396#[derive(Debug, Clone, PartialEq)]
397pub enum IfRange {
398    ETag(ETag),
399    Date(headers::Date),
400}
401
402impl Header for IfRange {
403    fn name() -> &'static HeaderName {
404        &IF_RANGE
405    }
406
407    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
408    where
409        I: Iterator<Item = &'i HeaderValue>,
410    {
411        let value = one(values)?;
412
413        let mut iter = std::iter::once(value);
414        if let Ok(tm) = headers::Date::decode(&mut iter) {
415            return Ok(IfRange::Date(tm));
416        }
417
418        let mut iter = std::iter::once(value);
419        if let Ok(et) = ETag::decode(&mut iter) {
420            return Ok(IfRange::ETag(et));
421        }
422
423        Err(invalid())
424    }
425
426    fn encode<E>(&self, values: &mut E)
427    where
428        E: Extend<HeaderValue>,
429    {
430        match *self {
431            IfRange::Date(ref d) => d.encode(values),
432            IfRange::ETag(ref t) => t.encode(values),
433        }
434    }
435}
436
437#[derive(Debug, Clone, PartialEq)]
438pub enum ETagList {
439    Tags(Vec<ETag>),
440    Star,
441}
442
443#[derive(Debug, Clone, PartialEq)]
444pub struct IfMatch(pub ETagList);
445
446#[derive(Debug, Clone, PartialEq)]
447pub struct IfNoneMatch(pub ETagList);
448
449// Decode a list of etags. This is not entirely correct, we should
450// actually use a real parser. E.g. we don't handle comma's in
451// etags correctly - but we never generated those anyway.
452fn decode_etaglist<'i, I>(values: &mut I) -> Result<ETagList, headers::Error>
453where
454    I: Iterator<Item = &'i HeaderValue>,
455{
456    let mut v = Vec::new();
457    let mut count = 0usize;
458    for value in values {
459        let s = value.to_str().map_err(map_invalid)?;
460        if s.trim() == "*" {
461            return Ok(ETagList::Star);
462        }
463        for t in s.split(',') {
464            // Simply skip misformed etags, they will never match.
465            if let Ok(t) = ETag::from_str(t.trim()) {
466                v.push(t);
467            }
468        }
469        count += 1;
470    }
471    if count != 0 {
472        Ok(ETagList::Tags(v))
473    } else {
474        Err(invalid())
475    }
476}
477
478fn encode_etaglist<E>(m: &ETagList, values: &mut E)
479where
480    E: Extend<HeaderValue>,
481{
482    let value = match *m {
483        ETagList::Star => "*".to_string(),
484        ETagList::Tags(ref t) => t
485            .iter()
486            .map(|t| t.tag.as_str())
487            .collect::<Vec<&str>>()
488            .join(", "),
489    };
490    values.extend(std::iter::once(HeaderValue::from_str(&value).unwrap()));
491}
492
493impl Header for IfMatch {
494    fn name() -> &'static HeaderName {
495        &IF_MATCH
496    }
497
498    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
499    where
500        I: Iterator<Item = &'i HeaderValue>,
501    {
502        Ok(IfMatch(decode_etaglist(values)?))
503    }
504
505    fn encode<E>(&self, values: &mut E)
506    where
507        E: Extend<HeaderValue>,
508    {
509        encode_etaglist(&self.0, values)
510    }
511}
512
513impl Header for IfNoneMatch {
514    fn name() -> &'static HeaderName {
515        &IF_NONE_MATCH
516    }
517
518    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
519    where
520        I: Iterator<Item = &'i HeaderValue>,
521    {
522        Ok(IfNoneMatch(decode_etaglist(values)?))
523    }
524
525    fn encode<E>(&self, values: &mut E)
526    where
527        E: Extend<HeaderValue>,
528    {
529        encode_etaglist(&self.0, values)
530    }
531}
532
533#[derive(Debug, Clone, PartialEq)]
534pub enum XUpdateRange {
535    FromTo(u64, u64),
536    AllFrom(u64),
537    Last(u64),
538    Append,
539}
540
541impl Header for XUpdateRange {
542    fn name() -> &'static HeaderName {
543        &X_UPDATE_RANGE
544    }
545
546    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
547    where
548        I: Iterator<Item = &'i HeaderValue>,
549    {
550        let mut s = one(values)?.to_str().map_err(map_invalid)?;
551        if s == "append" {
552            return Ok(XUpdateRange::Append);
553        }
554        if !s.starts_with("bytes=") {
555            return Err(invalid());
556        }
557        s = &s[6..];
558
559        let nums = s.split('-').collect::<Vec<&str>>();
560        if nums.len() != 2 {
561            return Err(invalid());
562        }
563        if !nums[0].is_empty() && !nums[1].is_empty() {
564            return Ok(XUpdateRange::FromTo(
565                (nums[0]).parse::<u64>().map_err(map_invalid)?,
566                (nums[1]).parse::<u64>().map_err(map_invalid)?,
567            ));
568        }
569        if !nums[0].is_empty() {
570            return Ok(XUpdateRange::AllFrom(
571                (nums[0]).parse::<u64>().map_err(map_invalid)?,
572            ));
573        }
574        if !nums[1].is_empty() {
575            return Ok(XUpdateRange::Last(
576                (nums[1]).parse::<u64>().map_err(map_invalid)?,
577            ));
578        }
579        Err(invalid())
580    }
581
582    fn encode<E>(&self, values: &mut E)
583    where
584        E: Extend<HeaderValue>,
585    {
586        let value = match *self {
587            XUpdateRange::Append => "append".to_string(),
588            XUpdateRange::FromTo(b, e) => format!("{b}-{e}"),
589            XUpdateRange::AllFrom(b) => format!("{b}-"),
590            XUpdateRange::Last(e) => format!("-{e}"),
591        };
592        values.extend(std::iter::once(HeaderValue::from_str(&value).unwrap()));
593    }
594}
595
596// The "If" header contains IfLists, of which the results are ORed.
597#[derive(Debug, Clone, PartialEq)]
598pub struct If(pub Vec<IfList>);
599
600// An IfList contains Conditions, of which the results are ANDed.
601#[derive(Debug, Clone, PartialEq)]
602pub struct IfList {
603    pub resource_tag: Option<url::Url>,
604    pub conditions: Vec<IfCondition>,
605}
606
607// helpers.
608impl IfList {
609    fn new() -> IfList {
610        IfList {
611            resource_tag: None,
612            conditions: Vec::new(),
613        }
614    }
615    fn add(&mut self, not: bool, item: IfItem) {
616        self.conditions.push(IfCondition { not, item });
617    }
618}
619
620// Single Condition is [NOT] State-Token | ETag
621#[derive(Debug, Clone, PartialEq)]
622pub struct IfCondition {
623    pub not: bool,
624    pub item: IfItem,
625}
626#[derive(Debug, Clone, PartialEq)]
627pub enum IfItem {
628    StateToken(String),
629    ETag(ETag),
630}
631
632// Below stuff is for the parser state.
633#[derive(Debug, Clone, PartialEq)]
634enum IfToken {
635    ListOpen,
636    ListClose,
637    Not,
638    Word(String),
639    Pointy(String),
640    ETag(ETag),
641    End,
642}
643
644#[derive(Debug, Clone, PartialEq)]
645enum IfState {
646    Start,
647    RTag,
648    List,
649    Not,
650    Bad,
651}
652
653// helpers.
654fn is_whitespace(c: u8) -> bool {
655    b" \t\r\n".contains(&c)
656}
657fn is_special(c: u8) -> bool {
658    b"<>()[]".contains(&c)
659}
660
661fn trim_left(mut out: &'_ [u8]) -> &'_ [u8] {
662    while !out.is_empty() && is_whitespace(out[0]) {
663        out = &out[1..];
664    }
665    out
666}
667
668// parse one token.
669fn scan_until(buf: &[u8], c: u8) -> Result<(&[u8], &[u8]), headers::Error> {
670    let mut i = 1;
671    let mut quote = false;
672    while quote || buf[i] != c {
673        if buf.is_empty() || is_whitespace(buf[i]) {
674            return Err(invalid());
675        }
676        if buf[i] == b'"' {
677            quote = !quote;
678        }
679        i += 1
680    }
681    Ok((&buf[1..i], &buf[i + 1..]))
682}
683
684// scan one word.
685fn scan_word(buf: &[u8]) -> Result<(&[u8], &[u8]), headers::Error> {
686    for (i, &c) in buf.iter().enumerate() {
687        if is_whitespace(c) || is_special(c) || c < 32 {
688            if i == 0 {
689                return Err(invalid());
690            }
691            return Ok((&buf[..i], &buf[i..]));
692        }
693    }
694    Ok((buf, b""))
695}
696
697// get next token.
698fn get_token(buf: &'_ [u8]) -> Result<(IfToken, &'_ [u8]), headers::Error> {
699    let buf = trim_left(buf);
700    if buf.is_empty() {
701        return Ok((IfToken::End, buf));
702    }
703    match buf[0] {
704        b'(' => Ok((IfToken::ListOpen, &buf[1..])),
705        b')' => Ok((IfToken::ListClose, &buf[1..])),
706        b'N' if buf.starts_with(b"Not") => Ok((IfToken::Not, &buf[3..])),
707        b'<' => {
708            let (tok, rest) = scan_until(buf, b'>')?;
709            let s = std::string::String::from_utf8(tok.to_vec()).map_err(map_invalid)?;
710            Ok((IfToken::Pointy(s), rest))
711        }
712        b'[' => {
713            let (tok, rest) = scan_until(buf, b']')?;
714            let s = std::str::from_utf8(tok).map_err(map_invalid)?;
715            Ok((IfToken::ETag(ETag::from_str(s)?), rest))
716        }
717        _ => {
718            let (tok, rest) = scan_word(buf)?;
719            if tok == b"Not" {
720                Ok((IfToken::Not, rest))
721            } else {
722                let s = std::string::String::from_utf8(tok.to_vec()).map_err(map_invalid)?;
723                Ok((IfToken::Word(s), rest))
724            }
725        }
726    }
727}
728
729impl Header for If {
730    fn name() -> &'static HeaderName {
731        &IF
732    }
733
734    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
735    where
736        I: Iterator<Item = &'i HeaderValue>,
737    {
738        // one big state machine.
739        let mut if_lists = If(Vec::new());
740        let mut cur_list = IfList::new();
741
742        let mut state = IfState::Start;
743        let mut input = one(values)?.as_bytes();
744
745        loop {
746            let (tok, rest) = get_token(input)?;
747            input = rest;
748            state = match state {
749                IfState::Start => match tok {
750                    IfToken::ListOpen => IfState::List,
751                    IfToken::Pointy(url) => {
752                        let u = url::Url::parse(&url).map_err(map_invalid)?;
753                        cur_list.resource_tag = Some(u);
754                        IfState::RTag
755                    }
756                    IfToken::End => {
757                        if !if_lists.0.is_empty() {
758                            break;
759                        }
760                        IfState::Bad
761                    }
762                    _ => IfState::Bad,
763                },
764                IfState::RTag => match tok {
765                    IfToken::ListOpen => IfState::List,
766                    _ => IfState::Bad,
767                },
768                IfState::List | IfState::Not => {
769                    let not = state == IfState::Not;
770                    match tok {
771                        IfToken::Not => {
772                            if not {
773                                IfState::Bad
774                            } else {
775                                IfState::Not
776                            }
777                        }
778                        IfToken::Pointy(stok) | IfToken::Word(stok) => {
779                            // as we don't have an URI parser, just
780                            // check if there's at least one ':' in there.
781                            if !stok.contains(':') {
782                                IfState::Bad
783                            } else {
784                                cur_list.add(not, IfItem::StateToken(stok));
785                                IfState::List
786                            }
787                        }
788                        IfToken::ETag(etag) => {
789                            cur_list.add(not, IfItem::ETag(etag));
790                            IfState::List
791                        }
792                        IfToken::ListClose => {
793                            if cur_list.conditions.is_empty() {
794                                IfState::Bad
795                            } else {
796                                if_lists.0.push(cur_list);
797                                cur_list = IfList::new();
798                                IfState::Start
799                            }
800                        }
801                        _ => IfState::Bad,
802                    }
803                }
804                IfState::Bad => return Err(invalid()),
805            };
806        }
807        Ok(if_lists)
808    }
809
810    fn encode<E>(&self, values: &mut E)
811    where
812        E: Extend<HeaderValue>,
813    {
814        let value = "[If header]";
815        values.extend(std::iter::once(HeaderValue::from_static(value)));
816    }
817}
818
819#[cfg(test)]
820mod tests {
821    use super::*;
822
823    #[test]
824    fn if_header() {
825        // Note that some implementations (golang net/x/webdav) also
826        // accept a "plain  word" as StateToken, instead of only
827        // a Coded-Url (<...>). We allow that as well, but I have
828        // no idea if we need to (or should!).
829        //let val = r#"  <http://x.yz/> ([W/"etag"] Not <DAV:nope> )
830        //    (Not<urn:x>[W/"bla"] plain:word:123) "#;
831        let val = r#"  <http://x.yz/> ([W/"etag"] Not <DAV:nope> ) (Not<urn:x>[W/"bla"] plain:word:123) "#;
832        let hdrval = HeaderValue::from_static(val);
833        let mut iter = std::iter::once(&hdrval);
834        let hdr = If::decode(&mut iter);
835        assert!(hdr.is_ok());
836    }
837
838    #[test]
839    fn etag_header() {
840        let t1 = ETag::from_str(r#"W/"12345""#).unwrap();
841        let t2 = ETag::from_str(r#"W/"12345""#).unwrap();
842        let t3 = ETag::from_str(r#""12346""#).unwrap();
843        let t4 = ETag::from_str(r#""12346""#).unwrap();
844        assert!(t1 != t2);
845        assert!(t2 != t3);
846        assert!(t3 == t4);
847    }
848}