actix_web/http/header/
entity.rs1use std::{
2 fmt::{self, Display, Write},
3 str::FromStr,
4};
5
6use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer};
7
8fn entity_validate_char(c: u8) -> bool {
13 c == 0x21 || (0x23..=0x7e).contains(&c) || (c >= 0x80)
14}
15
16fn check_slice_validity(slice: &str) -> bool {
17 slice.bytes().all(entity_validate_char)
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct EntityTag {
54 pub weak: bool,
56
57 tag: String,
59}
60
61impl EntityTag {
62 pub fn new(weak: bool, tag: String) -> EntityTag {
67 assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
68 EntityTag { weak, tag }
69 }
70
71 pub fn new_weak(tag: String) -> EntityTag {
76 EntityTag::new(true, tag)
77 }
78
79 #[deprecated(since = "3.0.0", note = "Renamed to `new_weak`.")]
80 pub fn weak(tag: String) -> EntityTag {
81 Self::new_weak(tag)
82 }
83
84 pub fn new_strong(tag: String) -> EntityTag {
89 EntityTag::new(false, tag)
90 }
91
92 #[deprecated(since = "3.0.0", note = "Renamed to `new_strong`.")]
93 pub fn strong(tag: String) -> EntityTag {
94 Self::new_strong(tag)
95 }
96
97 pub fn tag(&self) -> &str {
99 self.tag.as_ref()
100 }
101
102 pub fn set_tag(&mut self, tag: impl Into<String>) {
107 let tag = tag.into();
108 assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
109 self.tag = tag
110 }
111
112 pub fn strong_eq(&self, other: &EntityTag) -> bool {
115 !self.weak && !other.weak && self.tag == other.tag
116 }
117
118 pub fn weak_eq(&self, other: &EntityTag) -> bool {
121 self.tag == other.tag
122 }
123
124 pub fn strong_ne(&self, other: &EntityTag) -> bool {
126 !self.strong_eq(other)
127 }
128
129 pub fn weak_ne(&self, other: &EntityTag) -> bool {
131 !self.weak_eq(other)
132 }
133}
134
135impl Display for EntityTag {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 if self.weak {
138 write!(f, "W/\"{}\"", self.tag)
139 } else {
140 write!(f, "\"{}\"", self.tag)
141 }
142 }
143}
144
145impl FromStr for EntityTag {
146 type Err = crate::error::ParseError;
147
148 fn from_str(slice: &str) -> Result<EntityTag, crate::error::ParseError> {
149 let length = slice.len();
150 if !slice.ends_with('"') || slice.len() < 2 {
152 return Err(crate::error::ParseError::Header);
153 }
154 if slice.len() >= 2 && slice.starts_with('"') && check_slice_validity(&slice[1..length - 1])
156 {
157 return Ok(EntityTag {
160 weak: false,
161 tag: slice[1..length - 1].to_owned(),
162 });
163 } else if slice.len() >= 4
164 && slice.starts_with("W/\"")
165 && check_slice_validity(&slice[3..length - 1])
166 {
167 return Ok(EntityTag {
168 weak: true,
169 tag: slice[3..length - 1].to_owned(),
170 });
171 }
172 Err(crate::error::ParseError::Header)
173 }
174}
175
176impl TryIntoHeaderValue for EntityTag {
177 type Error = InvalidHeaderValue;
178
179 fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
180 let mut wrt = Writer::new();
181 write!(wrt, "{}", self).unwrap();
182 HeaderValue::from_maybe_shared(wrt.take())
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::EntityTag;
189
190 #[test]
191 fn test_etag_parse_success() {
192 assert_eq!(
194 "\"foobar\"".parse::<EntityTag>().unwrap(),
195 EntityTag::new_strong("foobar".to_owned())
196 );
197 assert_eq!(
198 "\"\"".parse::<EntityTag>().unwrap(),
199 EntityTag::new_strong("".to_owned())
200 );
201 assert_eq!(
202 "W/\"weaktag\"".parse::<EntityTag>().unwrap(),
203 EntityTag::new_weak("weaktag".to_owned())
204 );
205 assert_eq!(
206 "W/\"\x65\x62\"".parse::<EntityTag>().unwrap(),
207 EntityTag::new_weak("\x65\x62".to_owned())
208 );
209 assert_eq!(
210 "W/\"\"".parse::<EntityTag>().unwrap(),
211 EntityTag::new_weak("".to_owned())
212 );
213 }
214
215 #[test]
216 fn test_etag_parse_failures() {
217 assert!("no-dquotes".parse::<EntityTag>().is_err());
219 assert!("w/\"the-first-w-is-case-sensitive\""
220 .parse::<EntityTag>()
221 .is_err());
222 assert!("".parse::<EntityTag>().is_err());
223 assert!("\"unmatched-dquotes1".parse::<EntityTag>().is_err());
224 assert!("unmatched-dquotes2\"".parse::<EntityTag>().is_err());
225 assert!("matched-\"dquotes\"".parse::<EntityTag>().is_err());
226 }
227
228 #[test]
229 fn test_etag_fmt() {
230 assert_eq!(
231 format!("{}", EntityTag::new_strong("foobar".to_owned())),
232 "\"foobar\""
233 );
234 assert_eq!(format!("{}", EntityTag::new_strong("".to_owned())), "\"\"");
235 assert_eq!(
236 format!("{}", EntityTag::new_weak("weak-etag".to_owned())),
237 "W/\"weak-etag\""
238 );
239 assert_eq!(
240 format!("{}", EntityTag::new_weak("\u{0065}".to_owned())),
241 "W/\"\x65\""
242 );
243 assert_eq!(format!("{}", EntityTag::new_weak("".to_owned())), "W/\"\"");
244 }
245
246 #[test]
247 fn test_cmp() {
248 let mut etag1 = EntityTag::new_weak("1".to_owned());
255 let mut etag2 = EntityTag::new_weak("1".to_owned());
256 assert!(!etag1.strong_eq(&etag2));
257 assert!(etag1.weak_eq(&etag2));
258 assert!(etag1.strong_ne(&etag2));
259 assert!(!etag1.weak_ne(&etag2));
260
261 etag1 = EntityTag::new_weak("1".to_owned());
262 etag2 = EntityTag::new_weak("2".to_owned());
263 assert!(!etag1.strong_eq(&etag2));
264 assert!(!etag1.weak_eq(&etag2));
265 assert!(etag1.strong_ne(&etag2));
266 assert!(etag1.weak_ne(&etag2));
267
268 etag1 = EntityTag::new_weak("1".to_owned());
269 etag2 = EntityTag::new_strong("1".to_owned());
270 assert!(!etag1.strong_eq(&etag2));
271 assert!(etag1.weak_eq(&etag2));
272 assert!(etag1.strong_ne(&etag2));
273 assert!(!etag1.weak_ne(&etag2));
274
275 etag1 = EntityTag::new_strong("1".to_owned());
276 etag2 = EntityTag::new_strong("1".to_owned());
277 assert!(etag1.strong_eq(&etag2));
278 assert!(etag1.weak_eq(&etag2));
279 assert!(!etag1.strong_ne(&etag2));
280 assert!(!etag1.weak_ne(&etag2));
281 }
282}