actori_http/cookie/jar.rs
1use std::collections::HashSet;
2use std::mem::replace;
3
4use chrono::Duration;
5
6use super::delta::DeltaCookie;
7use super::Cookie;
8
9#[cfg(feature = "secure-cookies")]
10use super::secure::{Key, PrivateJar, SignedJar};
11
12/// A collection of cookies that tracks its modifications.
13///
14/// A `CookieJar` provides storage for any number of cookies. Any changes made
15/// to the jar are tracked; the changes can be retrieved via the
16/// [delta](#method.delta) method which returns an interator over the changes.
17///
18/// # Usage
19///
20/// A jar's life begins via [new](#method.new) and calls to
21/// [`add_original`](#method.add_original):
22///
23/// ```rust
24/// use actori_http::cookie::{Cookie, CookieJar};
25///
26/// let mut jar = CookieJar::new();
27/// jar.add_original(Cookie::new("name", "value"));
28/// jar.add_original(Cookie::new("second", "another"));
29/// ```
30///
31/// Cookies can be added via [add](#method.add) and removed via
32/// [remove](#method.remove). Finally, cookies can be looked up via
33/// [get](#method.get):
34///
35/// ```rust
36/// # use actori_http::cookie::{Cookie, CookieJar};
37/// let mut jar = CookieJar::new();
38/// jar.add(Cookie::new("a", "one"));
39/// jar.add(Cookie::new("b", "two"));
40///
41/// assert_eq!(jar.get("a").map(|c| c.value()), Some("one"));
42/// assert_eq!(jar.get("b").map(|c| c.value()), Some("two"));
43///
44/// jar.remove(Cookie::named("b"));
45/// assert!(jar.get("b").is_none());
46/// ```
47///
48/// # Deltas
49///
50/// A jar keeps track of any modifications made to it over time. The
51/// modifications are recorded as cookies. The modifications can be retrieved
52/// via [delta](#method.delta). Any new `Cookie` added to a jar via `add`
53/// results in the same `Cookie` appearing in the `delta`; cookies added via
54/// `add_original` do not count towards the delta. Any _original_ cookie that is
55/// removed from a jar results in a "removal" cookie appearing in the delta. A
56/// "removal" cookie is a cookie that a server sends so that the cookie is
57/// removed from the client's machine.
58///
59/// Deltas are typically used to create `Set-Cookie` headers corresponding to
60/// the changes made to a cookie jar over a period of time.
61///
62/// ```rust
63/// # use actori_http::cookie::{Cookie, CookieJar};
64/// let mut jar = CookieJar::new();
65///
66/// // original cookies don't affect the delta
67/// jar.add_original(Cookie::new("original", "value"));
68/// assert_eq!(jar.delta().count(), 0);
69///
70/// // new cookies result in an equivalent `Cookie` in the delta
71/// jar.add(Cookie::new("a", "one"));
72/// jar.add(Cookie::new("b", "two"));
73/// assert_eq!(jar.delta().count(), 2);
74///
75/// // removing an original cookie adds a "removal" cookie to the delta
76/// jar.remove(Cookie::named("original"));
77/// assert_eq!(jar.delta().count(), 3);
78///
79/// // removing a new cookie that was added removes that `Cookie` from the delta
80/// jar.remove(Cookie::named("a"));
81/// assert_eq!(jar.delta().count(), 2);
82/// ```
83#[derive(Default, Debug, Clone)]
84pub struct CookieJar {
85 original_cookies: HashSet<DeltaCookie>,
86 delta_cookies: HashSet<DeltaCookie>,
87}
88
89impl CookieJar {
90 /// Creates an empty cookie jar.
91 ///
92 /// # Example
93 ///
94 /// ```rust
95 /// use actori_http::cookie::CookieJar;
96 ///
97 /// let jar = CookieJar::new();
98 /// assert_eq!(jar.iter().count(), 0);
99 /// ```
100 pub fn new() -> CookieJar {
101 CookieJar::default()
102 }
103
104 /// Returns a reference to the `Cookie` inside this jar with the name
105 /// `name`. If no such cookie exists, returns `None`.
106 ///
107 /// # Example
108 ///
109 /// ```rust
110 /// use actori_http::cookie::{CookieJar, Cookie};
111 ///
112 /// let mut jar = CookieJar::new();
113 /// assert!(jar.get("name").is_none());
114 ///
115 /// jar.add(Cookie::new("name", "value"));
116 /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
117 /// ```
118 pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
119 self.delta_cookies
120 .get(name)
121 .or_else(|| self.original_cookies.get(name))
122 .and_then(|c| if !c.removed { Some(&c.cookie) } else { None })
123 }
124
125 /// Adds an "original" `cookie` to this jar. If an original cookie with the
126 /// same name already exists, it is replaced with `cookie`. Cookies added
127 /// with `add` take precedence and are not replaced by this method.
128 ///
129 /// Adding an original cookie does not affect the [delta](#method.delta)
130 /// computation. This method is intended to be used to seed the cookie jar
131 /// with cookies received from a client's HTTP message.
132 ///
133 /// For accurate `delta` computations, this method should not be called
134 /// after calling `remove`.
135 ///
136 /// # Example
137 ///
138 /// ```rust
139 /// use actori_http::cookie::{CookieJar, Cookie};
140 ///
141 /// let mut jar = CookieJar::new();
142 /// jar.add_original(Cookie::new("name", "value"));
143 /// jar.add_original(Cookie::new("second", "two"));
144 ///
145 /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
146 /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
147 /// assert_eq!(jar.iter().count(), 2);
148 /// assert_eq!(jar.delta().count(), 0);
149 /// ```
150 pub fn add_original(&mut self, cookie: Cookie<'static>) {
151 self.original_cookies.replace(DeltaCookie::added(cookie));
152 }
153
154 /// Adds `cookie` to this jar. If a cookie with the same name already
155 /// exists, it is replaced with `cookie`.
156 ///
157 /// # Example
158 ///
159 /// ```rust
160 /// use actori_http::cookie::{CookieJar, Cookie};
161 ///
162 /// let mut jar = CookieJar::new();
163 /// jar.add(Cookie::new("name", "value"));
164 /// jar.add(Cookie::new("second", "two"));
165 ///
166 /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
167 /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
168 /// assert_eq!(jar.iter().count(), 2);
169 /// assert_eq!(jar.delta().count(), 2);
170 /// ```
171 pub fn add(&mut self, cookie: Cookie<'static>) {
172 self.delta_cookies.replace(DeltaCookie::added(cookie));
173 }
174
175 /// Removes `cookie` from this jar. If an _original_ cookie with the same
176 /// name as `cookie` is present in the jar, a _removal_ cookie will be
177 /// present in the `delta` computation. To properly generate the removal
178 /// cookie, `cookie` must contain the same `path` and `domain` as the cookie
179 /// that was initially set.
180 ///
181 /// A "removal" cookie is a cookie that has the same name as the original
182 /// cookie but has an empty value, a max-age of 0, and an expiration date
183 /// far in the past.
184 ///
185 /// # Example
186 ///
187 /// Removing an _original_ cookie results in a _removal_ cookie:
188 ///
189 /// ```rust
190 /// use actori_http::cookie::{CookieJar, Cookie};
191 /// use chrono::Duration;
192 ///
193 /// let mut jar = CookieJar::new();
194 ///
195 /// // Assume this cookie originally had a path of "/" and domain of "a.b".
196 /// jar.add_original(Cookie::new("name", "value"));
197 ///
198 /// // If the path and domain were set, they must be provided to `remove`.
199 /// jar.remove(Cookie::build("name", "").path("/").domain("a.b").finish());
200 ///
201 /// // The delta will contain the removal cookie.
202 /// let delta: Vec<_> = jar.delta().collect();
203 /// assert_eq!(delta.len(), 1);
204 /// assert_eq!(delta[0].name(), "name");
205 /// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0)));
206 /// ```
207 ///
208 /// Removing a new cookie does not result in a _removal_ cookie:
209 ///
210 /// ```rust
211 /// use actori_http::cookie::{CookieJar, Cookie};
212 ///
213 /// let mut jar = CookieJar::new();
214 /// jar.add(Cookie::new("name", "value"));
215 /// assert_eq!(jar.delta().count(), 1);
216 ///
217 /// jar.remove(Cookie::named("name"));
218 /// assert_eq!(jar.delta().count(), 0);
219 /// ```
220 pub fn remove(&mut self, mut cookie: Cookie<'static>) {
221 if self.original_cookies.contains(cookie.name()) {
222 cookie.set_value("");
223 cookie.set_max_age(Duration::seconds(0));
224 cookie.set_expires(time::now() - Duration::days(365));
225 self.delta_cookies.replace(DeltaCookie::removed(cookie));
226 } else {
227 self.delta_cookies.remove(cookie.name());
228 }
229 }
230
231 /// Removes `cookie` from this jar completely. This method differs from
232 /// `remove` in that no delta cookie is created under any condition. Neither
233 /// the `delta` nor `iter` methods will return a cookie that is removed
234 /// using this method.
235 ///
236 /// # Example
237 ///
238 /// Removing an _original_ cookie; no _removal_ cookie is generated:
239 ///
240 /// ```rust
241 /// use actori_http::cookie::{CookieJar, Cookie};
242 /// use chrono::Duration;
243 ///
244 /// let mut jar = CookieJar::new();
245 ///
246 /// // Add an original cookie and a new cookie.
247 /// jar.add_original(Cookie::new("name", "value"));
248 /// jar.add(Cookie::new("key", "value"));
249 /// assert_eq!(jar.delta().count(), 1);
250 /// assert_eq!(jar.iter().count(), 2);
251 ///
252 /// // Now force remove the original cookie.
253 /// jar.force_remove(Cookie::new("name", "value"));
254 /// assert_eq!(jar.delta().count(), 1);
255 /// assert_eq!(jar.iter().count(), 1);
256 ///
257 /// // Now force remove the new cookie.
258 /// jar.force_remove(Cookie::new("key", "value"));
259 /// assert_eq!(jar.delta().count(), 0);
260 /// assert_eq!(jar.iter().count(), 0);
261 /// ```
262 pub fn force_remove<'a>(&mut self, cookie: Cookie<'a>) {
263 self.original_cookies.remove(cookie.name());
264 self.delta_cookies.remove(cookie.name());
265 }
266
267 /// Removes all cookies from this cookie jar.
268 #[deprecated(
269 since = "0.7.0",
270 note = "calling this method may not remove \
271 all cookies since the path and domain are not specified; use \
272 `remove` instead"
273 )]
274 pub fn clear(&mut self) {
275 self.delta_cookies.clear();
276 for delta in replace(&mut self.original_cookies, HashSet::new()) {
277 self.remove(delta.cookie);
278 }
279 }
280
281 /// Returns an iterator over cookies that represent the changes to this jar
282 /// over time. These cookies can be rendered directly as `Set-Cookie` header
283 /// values to affect the changes made to this jar on the client.
284 ///
285 /// # Example
286 ///
287 /// ```rust
288 /// use actori_http::cookie::{CookieJar, Cookie};
289 ///
290 /// let mut jar = CookieJar::new();
291 /// jar.add_original(Cookie::new("name", "value"));
292 /// jar.add_original(Cookie::new("second", "two"));
293 ///
294 /// // Add new cookies.
295 /// jar.add(Cookie::new("new", "third"));
296 /// jar.add(Cookie::new("another", "fourth"));
297 /// jar.add(Cookie::new("yac", "fifth"));
298 ///
299 /// // Remove some cookies.
300 /// jar.remove(Cookie::named("name"));
301 /// jar.remove(Cookie::named("another"));
302 ///
303 /// // Delta contains two new cookies ("new", "yac") and a removal ("name").
304 /// assert_eq!(jar.delta().count(), 3);
305 /// ```
306 pub fn delta(&self) -> Delta<'_> {
307 Delta {
308 iter: self.delta_cookies.iter(),
309 }
310 }
311
312 /// Returns an iterator over all of the cookies present in this jar.
313 ///
314 /// # Example
315 ///
316 /// ```rust
317 /// use actori_http::cookie::{CookieJar, Cookie};
318 ///
319 /// let mut jar = CookieJar::new();
320 ///
321 /// jar.add_original(Cookie::new("name", "value"));
322 /// jar.add_original(Cookie::new("second", "two"));
323 ///
324 /// jar.add(Cookie::new("new", "third"));
325 /// jar.add(Cookie::new("another", "fourth"));
326 /// jar.add(Cookie::new("yac", "fifth"));
327 ///
328 /// jar.remove(Cookie::named("name"));
329 /// jar.remove(Cookie::named("another"));
330 ///
331 /// // There are three cookies in the jar: "second", "new", and "yac".
332 /// # assert_eq!(jar.iter().count(), 3);
333 /// for cookie in jar.iter() {
334 /// match cookie.name() {
335 /// "second" => assert_eq!(cookie.value(), "two"),
336 /// "new" => assert_eq!(cookie.value(), "third"),
337 /// "yac" => assert_eq!(cookie.value(), "fifth"),
338 /// _ => unreachable!("there are only three cookies in the jar")
339 /// }
340 /// }
341 /// ```
342 pub fn iter(&self) -> Iter<'_> {
343 Iter {
344 delta_cookies: self
345 .delta_cookies
346 .iter()
347 .chain(self.original_cookies.difference(&self.delta_cookies)),
348 }
349 }
350
351 /// Returns a `PrivateJar` with `self` as its parent jar using the key `key`
352 /// to sign/encrypt and verify/decrypt cookies added/retrieved from the
353 /// child jar.
354 ///
355 /// Any modifications to the child jar will be reflected on the parent jar,
356 /// and any retrievals from the child jar will be made from the parent jar.
357 ///
358 /// This method is only available when the `secure` feature is enabled.
359 ///
360 /// # Example
361 ///
362 /// ```rust
363 /// use actori_http::cookie::{Cookie, CookieJar, Key};
364 ///
365 /// // Generate a secure key.
366 /// let key = Key::generate();
367 ///
368 /// // Add a private (signed + encrypted) cookie.
369 /// let mut jar = CookieJar::new();
370 /// jar.private(&key).add(Cookie::new("private", "text"));
371 ///
372 /// // The cookie's contents are encrypted.
373 /// assert_ne!(jar.get("private").unwrap().value(), "text");
374 ///
375 /// // They can be decrypted and verified through the child jar.
376 /// assert_eq!(jar.private(&key).get("private").unwrap().value(), "text");
377 ///
378 /// // A tampered with cookie does not validate but still exists.
379 /// let mut cookie = jar.get("private").unwrap().clone();
380 /// jar.add(Cookie::new("private", cookie.value().to_string() + "!"));
381 /// assert!(jar.private(&key).get("private").is_none());
382 /// assert!(jar.get("private").is_some());
383 /// ```
384 #[cfg(feature = "secure-cookies")]
385 pub fn private(&mut self, key: &Key) -> PrivateJar<'_> {
386 PrivateJar::new(self, key)
387 }
388
389 /// Returns a `SignedJar` with `self` as its parent jar using the key `key`
390 /// to sign/verify cookies added/retrieved from the child jar.
391 ///
392 /// Any modifications to the child jar will be reflected on the parent jar,
393 /// and any retrievals from the child jar will be made from the parent jar.
394 ///
395 /// This method is only available when the `secure` feature is enabled.
396 ///
397 /// # Example
398 ///
399 /// ```rust
400 /// use actori_http::cookie::{Cookie, CookieJar, Key};
401 ///
402 /// // Generate a secure key.
403 /// let key = Key::generate();
404 ///
405 /// // Add a signed cookie.
406 /// let mut jar = CookieJar::new();
407 /// jar.signed(&key).add(Cookie::new("signed", "text"));
408 ///
409 /// // The cookie's contents are signed but still in plaintext.
410 /// assert_ne!(jar.get("signed").unwrap().value(), "text");
411 /// assert!(jar.get("signed").unwrap().value().contains("text"));
412 ///
413 /// // They can be verified through the child jar.
414 /// assert_eq!(jar.signed(&key).get("signed").unwrap().value(), "text");
415 ///
416 /// // A tampered with cookie does not validate but still exists.
417 /// let mut cookie = jar.get("signed").unwrap().clone();
418 /// jar.add(Cookie::new("signed", cookie.value().to_string() + "!"));
419 /// assert!(jar.signed(&key).get("signed").is_none());
420 /// assert!(jar.get("signed").is_some());
421 /// ```
422 #[cfg(feature = "secure-cookies")]
423 pub fn signed(&mut self, key: &Key) -> SignedJar<'_> {
424 SignedJar::new(self, key)
425 }
426}
427
428use std::collections::hash_set::Iter as HashSetIter;
429
430/// Iterator over the changes to a cookie jar.
431pub struct Delta<'a> {
432 iter: HashSetIter<'a, DeltaCookie>,
433}
434
435impl<'a> Iterator for Delta<'a> {
436 type Item = &'a Cookie<'static>;
437
438 fn next(&mut self) -> Option<&'a Cookie<'static>> {
439 self.iter.next().map(|c| &c.cookie)
440 }
441}
442
443use std::collections::hash_map::RandomState;
444use std::collections::hash_set::Difference;
445use std::iter::Chain;
446
447/// Iterator over all of the cookies in a jar.
448pub struct Iter<'a> {
449 delta_cookies:
450 Chain<HashSetIter<'a, DeltaCookie>, Difference<'a, DeltaCookie, RandomState>>,
451}
452
453impl<'a> Iterator for Iter<'a> {
454 type Item = &'a Cookie<'static>;
455
456 fn next(&mut self) -> Option<&'a Cookie<'static>> {
457 for cookie in self.delta_cookies.by_ref() {
458 if !cookie.removed {
459 return Some(&*cookie);
460 }
461 }
462
463 None
464 }
465}
466
467#[cfg(test)]
468mod test {
469 #[cfg(feature = "secure-cookies")]
470 use super::Key;
471 use super::{Cookie, CookieJar};
472
473 #[test]
474 #[allow(deprecated)]
475 fn simple() {
476 let mut c = CookieJar::new();
477
478 c.add(Cookie::new("test", ""));
479 c.add(Cookie::new("test2", ""));
480 c.remove(Cookie::named("test"));
481
482 assert!(c.get("test").is_none());
483 assert!(c.get("test2").is_some());
484
485 c.add(Cookie::new("test3", ""));
486 c.clear();
487
488 assert!(c.get("test").is_none());
489 assert!(c.get("test2").is_none());
490 assert!(c.get("test3").is_none());
491 }
492
493 #[test]
494 fn jar_is_send() {
495 fn is_send<T: Send>(_: T) -> bool {
496 true
497 }
498
499 assert!(is_send(CookieJar::new()))
500 }
501
502 #[test]
503 #[cfg(feature = "secure-cookies")]
504 fn iter() {
505 let key = Key::generate();
506 let mut c = CookieJar::new();
507
508 c.add_original(Cookie::new("original", "original"));
509
510 c.add(Cookie::new("test", "test"));
511 c.add(Cookie::new("test2", "test2"));
512 c.add(Cookie::new("test3", "test3"));
513 assert_eq!(c.iter().count(), 4);
514
515 c.signed(&key).add(Cookie::new("signed", "signed"));
516 c.private(&key).add(Cookie::new("encrypted", "encrypted"));
517 assert_eq!(c.iter().count(), 6);
518
519 c.remove(Cookie::named("test"));
520 assert_eq!(c.iter().count(), 5);
521
522 c.remove(Cookie::named("signed"));
523 c.remove(Cookie::named("test2"));
524 assert_eq!(c.iter().count(), 3);
525
526 c.add(Cookie::new("test2", "test2"));
527 assert_eq!(c.iter().count(), 4);
528
529 c.remove(Cookie::named("test2"));
530 assert_eq!(c.iter().count(), 3);
531 }
532
533 #[test]
534 #[cfg(feature = "secure-cookies")]
535 fn delta() {
536 use chrono::Duration;
537 use std::collections::HashMap;
538
539 let mut c = CookieJar::new();
540
541 c.add_original(Cookie::new("original", "original"));
542 c.add_original(Cookie::new("original1", "original1"));
543
544 c.add(Cookie::new("test", "test"));
545 c.add(Cookie::new("test2", "test2"));
546 c.add(Cookie::new("test3", "test3"));
547 c.add(Cookie::new("test4", "test4"));
548
549 c.remove(Cookie::named("test"));
550 c.remove(Cookie::named("original"));
551
552 assert_eq!(c.delta().count(), 4);
553
554 let names: HashMap<_, _> = c.delta().map(|c| (c.name(), c.max_age())).collect();
555
556 assert!(names.get("test2").unwrap().is_none());
557 assert!(names.get("test3").unwrap().is_none());
558 assert!(names.get("test4").unwrap().is_none());
559 assert_eq!(names.get("original").unwrap(), &Some(Duration::seconds(0)));
560 }
561
562 #[test]
563 fn replace_original() {
564 let mut jar = CookieJar::new();
565 jar.add_original(Cookie::new("original_a", "a"));
566 jar.add_original(Cookie::new("original_b", "b"));
567 assert_eq!(jar.get("original_a").unwrap().value(), "a");
568
569 jar.add(Cookie::new("original_a", "av2"));
570 assert_eq!(jar.get("original_a").unwrap().value(), "av2");
571 }
572
573 #[test]
574 fn empty_delta() {
575 let mut jar = CookieJar::new();
576 jar.add(Cookie::new("name", "val"));
577 assert_eq!(jar.delta().count(), 1);
578
579 jar.remove(Cookie::named("name"));
580 assert_eq!(jar.delta().count(), 0);
581
582 jar.add_original(Cookie::new("name", "val"));
583 assert_eq!(jar.delta().count(), 0);
584
585 jar.remove(Cookie::named("name"));
586 assert_eq!(jar.delta().count(), 1);
587
588 jar.add(Cookie::new("name", "val"));
589 assert_eq!(jar.delta().count(), 1);
590
591 jar.remove(Cookie::named("name"));
592 assert_eq!(jar.delta().count(), 1);
593 }
594
595 #[test]
596 fn add_remove_add() {
597 let mut jar = CookieJar::new();
598 jar.add_original(Cookie::new("name", "val"));
599 assert_eq!(jar.delta().count(), 0);
600
601 jar.remove(Cookie::named("name"));
602 assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
603 assert_eq!(jar.delta().count(), 1);
604
605 // The cookie's been deleted. Another original doesn't change that.
606 jar.add_original(Cookie::new("name", "val"));
607 assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
608 assert_eq!(jar.delta().count(), 1);
609
610 jar.remove(Cookie::named("name"));
611 assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
612 assert_eq!(jar.delta().count(), 1);
613
614 jar.add(Cookie::new("name", "val"));
615 assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
616 assert_eq!(jar.delta().count(), 1);
617
618 jar.remove(Cookie::named("name"));
619 assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
620 assert_eq!(jar.delta().count(), 1);
621 }
622
623 #[test]
624 fn replace_remove() {
625 let mut jar = CookieJar::new();
626 jar.add_original(Cookie::new("name", "val"));
627 assert_eq!(jar.delta().count(), 0);
628
629 jar.add(Cookie::new("name", "val"));
630 assert_eq!(jar.delta().count(), 1);
631 assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
632
633 jar.remove(Cookie::named("name"));
634 assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
635 }
636
637 #[test]
638 fn remove_with_path() {
639 let mut jar = CookieJar::new();
640 jar.add_original(Cookie::build("name", "val").finish());
641 assert_eq!(jar.iter().count(), 1);
642 assert_eq!(jar.delta().count(), 0);
643 assert_eq!(jar.iter().filter(|c| c.path().is_none()).count(), 1);
644
645 jar.remove(Cookie::build("name", "").path("/").finish());
646 assert_eq!(jar.iter().count(), 0);
647 assert_eq!(jar.delta().count(), 1);
648 assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
649 assert_eq!(jar.delta().filter(|c| c.path() == Some("/")).count(), 1);
650 }
651}