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
27fn 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
40fn invalid() -> headers::Error {
42 headers::Error::invalid()
43}
44
45fn 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#[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#[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 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
454fn 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 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#[derive(Debug, Clone, PartialEq)]
603pub struct If(pub Vec<IfList>);
604
605#[derive(Debug, Clone, PartialEq)]
607pub struct IfList {
608 pub resource_tag: Option<url::Url>,
609 pub conditions: Vec<IfCondition>,
610}
611
612impl 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#[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#[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
658fn 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
673fn 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
689fn 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
702fn 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 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 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 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}