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