@@ -12,22 +12,41 @@ use std::option;
12
12
/// # Specifications
13
13
///
14
14
/// - [RFC 7232 HTTP/1.1: Conditional Requests](https://tools.ietf.org/html/rfc7232#section-2.3)
15
- #[ derive( Debug ) ]
16
- pub enum Etag {
17
- /// An Etag using strong validation.
15
+ ///
16
+ /// # Examples
17
+ ///
18
+ /// ```
19
+ /// # fn main() -> http_types::Result<()> {
20
+ /// #
21
+ /// use http_types::Response;
22
+ /// use http_types::cache::ETag;
23
+ ///
24
+ /// let etag = ETag::new("0xcafebeef".to_string());
25
+ ///
26
+ /// let mut res = Response::new(200);
27
+ /// etag.apply(&mut res);
28
+ ///
29
+ /// let etag = ETag::from_headers(res)?.unwrap();
30
+ /// assert_eq!(etag, ETag::Strong(String::from("0xcafebeef")));
31
+ /// #
32
+ /// # Ok(()) }
33
+ /// ```
34
+ #[ derive( Debug , Clone , Eq , PartialEq ) ]
35
+ pub enum ETag {
36
+ /// An ETag using strong validation.
18
37
Strong ( String ) ,
19
38
/// An ETag using weak validation.
20
39
Weak ( String ) ,
21
40
}
22
41
23
- impl Etag {
24
- /// Create a new Etag that uses strong validation.
42
+ impl ETag {
43
+ /// Create a new ETag that uses strong validation.
25
44
pub fn new ( s : String ) -> Self {
26
45
debug_assert ! ( !s. contains( '\\' ) , "ETags ought to avoid backslash chars" ) ;
27
46
Self :: Strong ( s)
28
47
}
29
48
30
- /// Create a new Etag that uses weak validation.
49
+ /// Create a new ETag that uses weak validation.
31
50
pub fn new_weak ( s : String ) -> Self {
32
51
debug_assert ! ( !s. contains( '\\' ) , "ETags ought to avoid backslash chars" ) ;
33
52
Self :: Weak ( s)
@@ -44,13 +63,15 @@ impl Etag {
44
63
} ;
45
64
46
65
// If a header is returned we can assume at least one exists.
47
- let mut s = headers. iter ( ) . last ( ) . unwrap ( ) . as_str ( ) ;
66
+ let s = headers. iter ( ) . last ( ) . unwrap ( ) . as_str ( ) ;
48
67
49
- let weak = if s. starts_with ( "/W" ) {
50
- s = & s[ 2 ..] ;
51
- true
52
- } else {
53
- false
68
+ let mut weak = false ;
69
+ let s = match s. strip_prefix ( "W/" ) {
70
+ Some ( s) => {
71
+ weak = true ;
72
+ s
73
+ }
74
+ None => s,
54
75
} ;
55
76
56
77
let s = match s. strip_prefix ( '"' ) . and_then ( |s| s. strip_suffix ( '"' ) ) {
@@ -86,9 +107,19 @@ impl Etag {
86
107
// SAFETY: the internal string is validated to be ASCII.
87
108
unsafe { HeaderValue :: from_bytes_unchecked ( s. into ( ) ) }
88
109
}
110
+
111
+ /// Returns `true` if the ETag is a `Strong` value.
112
+ pub fn is_strong ( & self ) -> bool {
113
+ matches ! ( self , Self :: Strong ( _) )
114
+ }
115
+
116
+ /// Returns `true` if the ETag is a `Weak` value.
117
+ pub fn is_weak ( & self ) -> bool {
118
+ matches ! ( self , Self :: Weak ( _) )
119
+ }
89
120
}
90
121
91
- impl ToHeaderValues for Etag {
122
+ impl ToHeaderValues for ETag {
92
123
type Iter = option:: IntoIter < HeaderValue > ;
93
124
fn to_header_values ( & self ) -> crate :: Result < Self :: Iter > {
94
125
// A HeaderValue will always convert into itself.
@@ -99,38 +130,54 @@ impl ToHeaderValues for Etag {
99
130
#[ cfg( test) ]
100
131
mod test {
101
132
use super :: * ;
102
- use crate :: headers:: { Headers , CACHE_CONTROL } ;
133
+ use crate :: headers:: Headers ;
103
134
104
135
#[ test]
105
136
fn smoke ( ) -> crate :: Result < ( ) > {
106
- let mut etag = Etag :: new ( "0xcafebeef" ) ;
137
+ let etag = ETag :: new ( "0xcafebeef" . to_string ( ) ) ;
107
138
108
139
let mut headers = Headers :: new ( ) ;
109
- entries . apply ( & mut headers) ;
140
+ etag . apply ( & mut headers) ;
110
141
111
- let entries = Etag :: from_headers ( headers) ?. unwrap ( ) ;
112
- let mut entries = entries. iter ( ) ;
113
- assert_eq ! ( entries. next( ) . unwrap( ) , & CacheDirective :: Immutable ) ;
114
- assert_eq ! ( entries. next( ) . unwrap( ) , & CacheDirective :: NoStore ) ;
142
+ let etag = ETag :: from_headers ( headers) ?. unwrap ( ) ;
143
+ assert_eq ! ( etag, ETag :: Strong ( String :: from( "0xcafebeef" ) ) ) ;
115
144
Ok ( ( ) )
116
145
}
117
146
118
147
#[ test]
119
- fn ignore_unkonwn_directives ( ) -> crate :: Result < ( ) > {
148
+ fn smoke_weak ( ) -> crate :: Result < ( ) > {
149
+ let etag = ETag :: new_weak ( "0xcafebeef" . to_string ( ) ) ;
150
+
120
151
let mut headers = Headers :: new ( ) ;
121
- headers . insert ( CACHE_CONTROL , "barrel_roll" ) ;
122
- let entries = Etag :: from_headers ( headers ) ? . unwrap ( ) ;
123
- let mut entries = entries . iter ( ) ;
124
- assert ! ( entries . next ( ) . is_none ( ) ) ;
152
+ etag . apply ( & mut headers ) ;
153
+
154
+ let etag = ETag :: from_headers ( headers ) ? . unwrap ( ) ;
155
+ assert_eq ! ( etag , ETag :: Weak ( String :: from ( "0xcafebeef" ) ) ) ;
125
156
Ok ( ( ) )
126
157
}
127
158
128
159
#[ test]
129
160
fn bad_request_on_parse_error ( ) -> crate :: Result < ( ) > {
130
161
let mut headers = Headers :: new ( ) ;
131
- headers. insert ( CACHE_CONTROL , "min-fresh=0.9" ) ; // floats are not supported
132
- let err = Etag :: from_headers ( headers) . unwrap_err ( ) ;
162
+ headers. insert ( ETAG , "<nori ate the tag. yum.>" ) ;
163
+ let err = ETag :: from_headers ( headers) . unwrap_err ( ) ;
133
164
assert_eq ! ( err. status( ) , 400 ) ;
134
165
Ok ( ( ) )
135
166
}
167
+
168
+ #[ test]
169
+ fn validate_quotes ( ) -> crate :: Result < ( ) > {
170
+ assert_entry_err ( r#""hello"# , "Invalid ETag header" ) ;
171
+ assert_entry_err ( r#"hello""# , "Invalid ETag header" ) ;
172
+ assert_entry_err ( r#"/O"valid content""# , "Invalid ETag header" ) ;
173
+ assert_entry_err ( r#"/Wvalid content""# , "Invalid ETag header" ) ;
174
+ Ok ( ( ) )
175
+ }
176
+
177
+ fn assert_entry_err ( s : & str , msg : & str ) {
178
+ let mut headers = Headers :: new ( ) ;
179
+ headers. insert ( ETAG , s) ;
180
+ let err = ETag :: from_headers ( headers) . unwrap_err ( ) ;
181
+ assert_eq ! ( format!( "{}" , err) , msg) ;
182
+ }
136
183
}
0 commit comments