1
+ use header:: { Header , HeaderFormat } ;
2
+ use std:: fmt:: { mod} ;
3
+ use super :: util:: from_one_raw_str;
4
+
5
+ /// The `Etag` header.
6
+ ///
7
+ /// An Etag consists of a string enclosed by two literal double quotes.
8
+ /// Preceding the first double quote is an optional weakness indicator,
9
+ /// which always looks like this: W/
10
+ /// See also: https://tools.ietf.org/html/rfc7232#section-2.3
11
+ #[ deriving( Clone , PartialEq , Show ) ]
12
+ pub struct Etag {
13
+ /// Weakness indicator for the tag
14
+ pub weak : bool ,
15
+ /// The opaque string in between the DQUOTEs
16
+ pub tag : String
17
+ }
18
+
19
+ impl Header for Etag {
20
+ fn header_name ( _: Option < Etag > ) -> & ' static str {
21
+ "Etag"
22
+ }
23
+
24
+ fn parse_header ( raw : & [ Vec < u8 > ] ) -> Option < Etag > {
25
+ // check that each char in the slice is either:
26
+ // 1. %x21, or
27
+ // 2. in the range %x23 to %x7E, or
28
+ // 3. in the range %x80 to %xFF
29
+ fn check_slice_validity ( slice : & str ) -> bool {
30
+ for c in slice. bytes ( ) {
31
+ match c {
32
+ b'\x21' | b'\x23' ... b'\x7e' | b'\x80' ... b'\xff' => ( ) ,
33
+ _ => { return false ; }
34
+ }
35
+ }
36
+ true
37
+ }
38
+
39
+
40
+ from_one_raw_str ( raw) . and_then ( |s : String | {
41
+ let length: uint = s. len ( ) ;
42
+ let slice = s[ ] ;
43
+
44
+ // Early exits:
45
+ // 1. The string is empty, or,
46
+ // 2. it doesn't terminate in a DQUOTE.
47
+ if slice. is_empty ( ) || !slice. ends_with ( "\" " ) {
48
+ return None ;
49
+ }
50
+
51
+ // The etag is weak if its first char is not a DQUOTE.
52
+ if slice. char_at ( 0 ) == '"' {
53
+ // No need to check if the last char is a DQUOTE,
54
+ // we already did that above.
55
+ if check_slice_validity ( slice. slice_chars ( 1 , length-1 ) ) {
56
+ return Some ( Etag {
57
+ weak : false ,
58
+ tag : slice. slice_chars ( 1 , length-1 ) . into_string ( )
59
+ } ) ;
60
+ } else {
61
+ return None ;
62
+ }
63
+ }
64
+
65
+ if slice. slice_chars ( 0 , 3 ) == "W/\" " {
66
+ if check_slice_validity ( slice. slice_chars ( 3 , length-1 ) ) {
67
+ return Some ( Etag {
68
+ weak : true ,
69
+ tag : slice. slice_chars ( 3 , length-1 ) . into_string ( )
70
+ } ) ;
71
+ } else {
72
+ return None ;
73
+ }
74
+ }
75
+
76
+ None
77
+ } )
78
+ }
79
+ }
80
+
81
+ impl HeaderFormat for Etag {
82
+ fn fmt_header ( & self , fmt : & mut fmt:: Formatter ) -> fmt:: Result {
83
+ if self . weak {
84
+ try!( fmt. write ( b"W/" ) ) ;
85
+ }
86
+ write ! ( fmt, "\" {}\" " , self . tag)
87
+ }
88
+ }
89
+
90
+ #[ cfg( test) ]
91
+ mod tests {
92
+ use super :: Etag ;
93
+ use header:: Header ;
94
+
95
+ #[ test]
96
+ fn test_etag_successes ( ) {
97
+ // Expected successes
98
+ let mut etag: Option < Etag > ;
99
+
100
+ etag = Header :: parse_header ( [ b"\" foobar\" " . to_vec ( ) ] . as_slice ( ) ) ;
101
+ assert_eq ! ( etag, Some ( Etag {
102
+ weak: false ,
103
+ tag: "foobar" . into_string( )
104
+ } ) ) ;
105
+
106
+ etag = Header :: parse_header ( [ b"\" \" " . to_vec ( ) ] . as_slice ( ) ) ;
107
+ assert_eq ! ( etag, Some ( Etag {
108
+ weak: false ,
109
+ tag: "" . into_string( )
110
+ } ) ) ;
111
+
112
+ etag = Header :: parse_header ( [ b"W/\" weak-etag\" " . to_vec ( ) ] . as_slice ( ) ) ;
113
+ assert_eq ! ( etag, Some ( Etag {
114
+ weak: true ,
115
+ tag: "weak-etag" . into_string( )
116
+ } ) ) ;
117
+
118
+ etag = Header :: parse_header ( [ b"W/\" \x65 \x62 \" " . to_vec ( ) ] . as_slice ( ) ) ;
119
+ assert_eq ! ( etag, Some ( Etag {
120
+ weak: true ,
121
+ tag: "\u0065 \u0062 " . into_string( )
122
+ } ) ) ;
123
+
124
+ etag = Header :: parse_header ( [ b"W/\" \" " . to_vec ( ) ] . as_slice ( ) ) ;
125
+ assert_eq ! ( etag, Some ( Etag {
126
+ weak: true ,
127
+ tag: "" . into_string( )
128
+ } ) ) ;
129
+ }
130
+
131
+ #[ test]
132
+ fn test_etag_failures ( ) {
133
+ // Expected failures
134
+ let mut etag: Option < Etag > ;
135
+
136
+ etag = Header :: parse_header ( [ b"no-dquotes" . to_vec ( ) ] . as_slice ( ) ) ;
137
+ assert_eq ! ( etag, None ) ;
138
+
139
+ etag = Header :: parse_header ( [ b"w/\" the-first-w-is-case-sensitive\" " . to_vec ( ) ] . as_slice ( ) ) ;
140
+ assert_eq ! ( etag, None ) ;
141
+
142
+ etag = Header :: parse_header ( [ b"" . to_vec ( ) ] . as_slice ( ) ) ;
143
+ assert_eq ! ( etag, None ) ;
144
+
145
+ etag = Header :: parse_header ( [ b"\" unmatched-dquotes1" . to_vec ( ) ] . as_slice ( ) ) ;
146
+ assert_eq ! ( etag, None ) ;
147
+
148
+ etag = Header :: parse_header ( [ b"unmatched-dquotes2\" " . to_vec ( ) ] . as_slice ( ) ) ;
149
+ assert_eq ! ( etag, None ) ;
150
+
151
+ etag = Header :: parse_header ( [ b"matched-\" dquotes\" " . to_vec ( ) ] . as_slice ( ) ) ;
152
+ assert_eq ! ( etag, None ) ;
153
+ }
154
+ }
155
+
156
+ bench_header ! ( bench, Etag , { vec![ b"W/\" nonemptytag\" " . to_vec( ) ] } )
0 commit comments