ntex_files/header/
entity.rs1use super::error::{Error, Result};
2use std::fmt::{self, Display};
3use std::str::FromStr;
4
5fn check_slice_validity(slice: &str) -> bool {
10 slice.bytes().all(|c| c == b'\x21' || (b'\x23'..=b'\x7e').contains(&c) | (c >= b'\x80'))
11}
12
13#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct EntityTag {
45 pub weak: bool,
47 tag: String,
49}
50
51impl EntityTag {
52 pub fn new(weak: bool, tag: String) -> EntityTag {
56 assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
57 EntityTag { weak, tag }
58 }
59
60 pub fn weak(tag: String) -> EntityTag {
64 EntityTag::new(true, tag)
65 }
66
67 pub fn strong(tag: String) -> EntityTag {
71 EntityTag::new(false, tag)
72 }
73
74 pub fn tag(&self) -> &str {
76 self.tag.as_ref()
77 }
78
79 pub fn set_tag(&mut self, tag: String) {
83 assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
84 self.tag = tag
85 }
86
87 pub fn strong_eq(&self, other: &EntityTag) -> bool {
90 !self.weak && !other.weak && self.tag == other.tag
91 }
92
93 pub fn weak_eq(&self, other: &EntityTag) -> bool {
97 self.tag == other.tag
98 }
99
100 pub fn strong_ne(&self, other: &EntityTag) -> bool {
102 !self.strong_eq(other)
103 }
104
105 pub fn weak_ne(&self, other: &EntityTag) -> bool {
107 !self.weak_eq(other)
108 }
109}
110
111impl Display for EntityTag {
112 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113 if self.weak { write!(f, "W/\"{}\"", self.tag) } else { write!(f, "\"{}\"", self.tag) }
114 }
115}
116
117impl FromStr for EntityTag {
118 type Err = Error;
119 fn from_str(slice: &str) -> Result<EntityTag> {
120 let length: usize = slice.len();
121 if !slice.ends_with('"') || slice.len() < 2 {
123 return Err(Error::Header);
124 }
125 if slice.len() >= 2
127 && slice.starts_with('"')
128 && check_slice_validity(&slice[1..length - 1])
129 {
130 return Ok(EntityTag { weak: false, tag: slice[1..length - 1].to_owned() });
133 } else if slice.len() >= 4
134 && slice.starts_with("W/\"")
135 && check_slice_validity(&slice[3..length - 1])
136 {
137 return Ok(EntityTag { weak: true, tag: slice[3..length - 1].to_owned() });
138 }
139 Err(Error::Header)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::EntityTag;
146
147 #[test]
148 fn test_etag_parse_success() {
149 assert_eq!(
151 "\"foobar\"".parse::<EntityTag>().unwrap(),
152 EntityTag::strong("foobar".to_owned())
153 );
154 assert_eq!("\"\"".parse::<EntityTag>().unwrap(), EntityTag::strong("".to_owned()));
155 assert_eq!(
156 "W/\"weaktag\"".parse::<EntityTag>().unwrap(),
157 EntityTag::weak("weaktag".to_owned())
158 );
159 assert_eq!(
160 "W/\"\x65\x62\"".parse::<EntityTag>().unwrap(),
161 EntityTag::weak("\x65\x62".to_owned())
162 );
163 assert_eq!("W/\"\"".parse::<EntityTag>().unwrap(), EntityTag::weak("".to_owned()));
164 }
165
166 #[test]
167 fn test_etag_parse_failures() {
168 assert!("no-dquotes".parse::<EntityTag>().is_err());
170 assert!("w/\"the-first-w-is-case-sensitive\"".parse::<EntityTag>().is_err());
171 assert!("".parse::<EntityTag>().is_err());
172 assert!("\"unmatched-dquotes1".parse::<EntityTag>().is_err());
173 assert!("unmatched-dquotes2\"".parse::<EntityTag>().is_err());
174 assert!("matched-\"dquotes\"".parse::<EntityTag>().is_err());
175 }
176
177 #[test]
178 fn test_etag_fmt() {
179 assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\"");
180 assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\"");
181 assert_eq!(format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\"");
182 assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\"");
183 assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\"");
184 }
185
186 #[test]
187 fn test_cmp() {
188 let mut etag1 = EntityTag::weak("1".to_owned());
195 let mut etag2 = EntityTag::weak("1".to_owned());
196 assert!(!etag1.strong_eq(&etag2));
197 assert!(etag1.weak_eq(&etag2));
198 assert!(etag1.strong_ne(&etag2));
199 assert!(!etag1.weak_ne(&etag2));
200
201 etag1 = EntityTag::weak("1".to_owned());
202 etag2 = EntityTag::weak("2".to_owned());
203 assert!(!etag1.strong_eq(&etag2));
204 assert!(!etag1.weak_eq(&etag2));
205 assert!(etag1.strong_ne(&etag2));
206 assert!(etag1.weak_ne(&etag2));
207
208 etag1 = EntityTag::weak("1".to_owned());
209 etag2 = EntityTag::strong("1".to_owned());
210 assert!(!etag1.strong_eq(&etag2));
211 assert!(etag1.weak_eq(&etag2));
212 assert!(etag1.strong_ne(&etag2));
213 assert!(!etag1.weak_ne(&etag2));
214
215 etag1 = EntityTag::strong("1".to_owned());
216 etag2 = EntityTag::strong("1".to_owned());
217 assert!(etag1.strong_eq(&etag2));
218 assert!(etag1.weak_eq(&etag2));
219 assert!(!etag1.strong_ne(&etag2));
220 assert!(!etag1.weak_ne(&etag2));
221 }
222}