net/
http_cache.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![deny(missing_docs)]
6
7//! A memory cache implementing the logic specified in <http://tools.ietf.org/html/rfc7234>
8//! and <http://tools.ietf.org/html/rfc7232>.
9
10use std::collections::HashMap;
11use std::ops::Bound;
12use std::sync::Mutex;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::time::{Duration, Instant, SystemTime};
15
16use headers::{
17    CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary,
18};
19use http::header::HeaderValue;
20use http::{HeaderMap, Method, StatusCode, header};
21use log::debug;
22use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
23use malloc_size_of_derive::MallocSizeOf;
24use net_traits::http_status::HttpStatus;
25use net_traits::request::Request;
26use net_traits::response::{HttpsState, Response, ResponseBody};
27use net_traits::{FetchMetadata, Metadata, ResourceFetchTiming};
28use servo_arc::Arc;
29use servo_config::pref;
30use servo_url::ServoUrl;
31use tokio::sync::mpsc::{UnboundedSender as TokioSender, unbounded_channel as unbounded};
32
33use crate::fetch::methods::{Data, DoneChannel};
34
35/// The key used to differentiate requests in the cache.
36#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq)]
37pub struct CacheKey {
38    url: ServoUrl,
39}
40
41impl CacheKey {
42    /// Create a cache-key from a request.
43    pub(crate) fn new(request: &Request) -> CacheKey {
44        CacheKey {
45            url: request.current_url(),
46        }
47    }
48
49    fn from_servo_url(servo_url: &ServoUrl) -> CacheKey {
50        CacheKey {
51            url: servo_url.clone(),
52        }
53    }
54}
55
56/// A complete cached resource.
57#[derive(Clone)]
58struct CachedResource {
59    request_headers: Arc<Mutex<HeaderMap>>,
60    body: Arc<Mutex<ResponseBody>>,
61    aborted: Arc<AtomicBool>,
62    awaiting_body: Arc<Mutex<Vec<TokioSender<Data>>>>,
63    metadata: CachedMetadata,
64    location_url: Option<Result<ServoUrl, String>>,
65    https_state: HttpsState,
66    status: HttpStatus,
67    url_list: Vec<ServoUrl>,
68    expires: Duration,
69    last_validated: Instant,
70}
71
72impl MallocSizeOf for CachedResource {
73    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
74        // TODO: self.request_headers.unconditional_size_of(ops) +
75        self.body.unconditional_size_of(ops) +
76            self.aborted.unconditional_size_of(ops) +
77            self.awaiting_body.unconditional_size_of(ops) +
78            self.metadata.size_of(ops) +
79            self.location_url.size_of(ops) +
80            self.https_state.size_of(ops) +
81            self.status.size_of(ops) +
82            self.url_list.size_of(ops) +
83            self.expires.size_of(ops) +
84            self.last_validated.size_of(ops)
85    }
86}
87
88/// Metadata about a loaded resource, such as is obtained from HTTP headers.
89#[derive(Clone, MallocSizeOf)]
90struct CachedMetadata {
91    /// Headers
92    #[ignore_malloc_size_of = "Defined in `http` and has private members"]
93    pub headers: Arc<Mutex<HeaderMap>>,
94    /// Final URL after redirects.
95    pub final_url: ServoUrl,
96    /// MIME type / subtype.
97    pub content_type: Option<String>,
98    /// Character set.
99    pub charset: Option<String>,
100    /// HTTP Status
101    pub status: HttpStatus,
102}
103/// Wrapper around a cached response, including information on re-validation needs
104pub struct CachedResponse {
105    /// The response constructed from the cached resource
106    pub response: Response,
107    /// The revalidation flag for the stored response
108    pub needs_validation: bool,
109}
110
111/// A memory cache.
112#[derive(Default, MallocSizeOf)]
113pub struct HttpCache {
114    /// cached responses.
115    entries: HashMap<CacheKey, Vec<CachedResource>>,
116}
117
118/// Determine if a response is cacheable by default <https://tools.ietf.org/html/rfc7231#section-6.1>
119fn is_cacheable_by_default(status_code: StatusCode) -> bool {
120    matches!(
121        status_code.as_u16(),
122        200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
123    )
124}
125
126/// Determine if a given response is cacheable.
127/// Based on <https://tools.ietf.org/html/rfc7234#section-3>
128fn response_is_cacheable(metadata: &Metadata) -> bool {
129    // TODO: if we determine that this cache should be considered shared:
130    // 1. check for absence of private response directive <https://tools.ietf.org/html/rfc7234#section-5.2.2.6>
131    // 2. check for absence of the Authorization header field.
132    let mut is_cacheable = false;
133    let headers = metadata.headers.as_ref().unwrap();
134    if headers.contains_key(header::EXPIRES) ||
135        headers.contains_key(header::LAST_MODIFIED) ||
136        headers.contains_key(header::ETAG)
137    {
138        is_cacheable = true;
139    }
140    if let Some(ref directive) = headers.typed_get::<CacheControl>() {
141        if directive.no_store() {
142            return false;
143        }
144        if directive.public() ||
145            directive.s_max_age().is_some() ||
146            directive.max_age().is_some() ||
147            directive.no_cache()
148        {
149            // If cache-control is understood, we can use it and ignore pragma.
150            return true;
151        }
152    }
153    if let Some(pragma) = headers.typed_get::<Pragma>() {
154        if pragma.is_no_cache() {
155            return false;
156        }
157    }
158    is_cacheable
159}
160
161/// Calculating Age
162/// <https://tools.ietf.org/html/rfc7234#section-4.2.3>
163fn calculate_response_age(response: &Response) -> Duration {
164    // TODO: follow the spec more closely (Date headers, request/response lag, ...)
165    response
166        .headers
167        .get(header::AGE)
168        .and_then(|age_header| age_header.to_str().ok())
169        .and_then(|age_string| age_string.parse::<u64>().ok())
170        .map(Duration::from_secs)
171        .unwrap_or_default()
172}
173
174/// Determine the expiry date from relevant headers,
175/// or uses a heuristic if none are present.
176fn get_response_expiry(response: &Response) -> Duration {
177    // Calculating Freshness Lifetime <https://tools.ietf.org/html/rfc7234#section-4.2.1>
178    let age = calculate_response_age(response);
179    let now = SystemTime::now();
180    if let Some(directives) = response.headers.typed_get::<CacheControl>() {
181        if directives.no_cache() {
182            // Requires validation on first use.
183            return Duration::ZERO;
184        }
185        if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
186            return max_age.saturating_sub(age);
187        }
188    }
189    match response.headers.typed_get::<Expires>() {
190        Some(expiry) => {
191            // `duration_since` fails if `now` is later than `expiry_time` in which case,
192            // this whole thing return `Duration::ZERO`.
193            let expiry_time: SystemTime = expiry.into();
194            return expiry_time.duration_since(now).unwrap_or(Duration::ZERO);
195        },
196        // Malformed Expires header, shouldn't be used to construct a valid response.
197        None if response.headers.contains_key(header::EXPIRES) => return Duration::ZERO,
198        _ => {},
199    }
200    // Calculating Heuristic Freshness
201    // <https://tools.ietf.org/html/rfc7234#section-4.2.2>
202    if let Some(ref code) = response.status.try_code() {
203        // <https://tools.ietf.org/html/rfc7234#section-5.5.4>
204        // Since presently we do not generate a Warning header field with a 113 warn-code,
205        // 24 hours minus response age is the max for heuristic calculation.
206        let max_heuristic = Duration::from_secs(24 * 60 * 60).saturating_sub(age);
207        let heuristic_freshness = if let Some(last_modified) =
208            // If the response has a Last-Modified header field,
209            // caches are encouraged to use a heuristic expiration value
210            // that is no more than some fraction of the interval since that time.
211            response.headers.typed_get::<LastModified>()
212        {
213            // `time_since_last_modified` will be `Duration::ZERO` if `last_modified` is
214            // after `now`.
215            let last_modified: SystemTime = last_modified.into();
216            let time_since_last_modified = now.duration_since(last_modified).unwrap_or_default();
217
218            // A typical setting of this fraction might be 10%.
219            let raw_heuristic_calc = time_since_last_modified / 10;
220            if raw_heuristic_calc < max_heuristic {
221                raw_heuristic_calc
222            } else {
223                max_heuristic
224            }
225        } else {
226            max_heuristic
227        };
228        if is_cacheable_by_default(*code) {
229            // Status codes that are cacheable by default can use heuristics to determine freshness.
230            return heuristic_freshness;
231        }
232        // Other status codes can only use heuristic freshness if the public cache directive is present.
233        if let Some(ref directives) = response.headers.typed_get::<CacheControl>() {
234            if directives.public() {
235                return heuristic_freshness;
236            }
237        }
238    }
239    // Requires validation upon first use as default.
240    Duration::ZERO
241}
242
243/// Request Cache-Control Directives
244/// <https://tools.ietf.org/html/rfc7234#section-5.2.1>
245fn get_expiry_adjustment_from_request_headers(request: &Request, expires: Duration) -> Duration {
246    let Some(directive) = request.headers.typed_get::<CacheControl>() else {
247        return expires;
248    };
249
250    if let Some(max_age) = directive.max_stale() {
251        return expires + max_age;
252    }
253
254    match directive.max_age() {
255        Some(max_age) if expires > max_age => return Duration::ZERO,
256        Some(max_age) => return expires - max_age,
257        None => {},
258    };
259
260    if let Some(min_fresh) = directive.min_fresh() {
261        if expires < min_fresh {
262            return Duration::ZERO;
263        }
264        return expires - min_fresh;
265    }
266
267    if directive.no_cache() || directive.no_store() {
268        return Duration::ZERO;
269    }
270
271    expires
272}
273
274/// Create a CachedResponse from a request and a CachedResource.
275fn create_cached_response(
276    request: &Request,
277    cached_resource: &CachedResource,
278    cached_headers: &HeaderMap,
279    done_chan: &mut DoneChannel,
280) -> Option<CachedResponse> {
281    debug!("creating a cached response for {:?}", request.url());
282    if cached_resource.aborted.load(Ordering::Acquire) {
283        return None;
284    }
285    let resource_timing = ResourceFetchTiming::new(request.timing_type());
286    let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
287    response.headers = cached_headers.clone();
288    response.body = cached_resource.body.clone();
289    if let ResponseBody::Receiving(_) = *cached_resource.body.lock().unwrap() {
290        debug!("existing body is in progress");
291        let (done_sender, done_receiver) = unbounded();
292        *done_chan = Some((done_sender.clone(), done_receiver));
293        cached_resource
294            .awaiting_body
295            .lock()
296            .unwrap()
297            .push(done_sender);
298    }
299    response
300        .location_url
301        .clone_from(&cached_resource.location_url);
302    response.status.clone_from(&cached_resource.status);
303    response.url_list.clone_from(&cached_resource.url_list);
304    response.https_state = cached_resource.https_state;
305    response.referrer = request.referrer.to_url().cloned();
306    response.referrer_policy = request.referrer_policy;
307    response.aborted = cached_resource.aborted.clone();
308
309    let expires = cached_resource.expires;
310    let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
311    let time_since_validated = Instant::now() - cached_resource.last_validated;
312
313    // TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1>
314    // TODO: if this cache is to be considered shared, take proxy-revalidate into account
315    // <https://tools.ietf.org/html/rfc7234#section-5.2.2.7>
316    let has_expired = adjusted_expires <= time_since_validated;
317    let cached_response = CachedResponse {
318        response,
319        needs_validation: has_expired,
320    };
321    Some(cached_response)
322}
323
324/// Create a new resource, based on the bytes requested, and an existing resource,
325/// with a status-code of 206.
326fn create_resource_with_bytes_from_resource(
327    bytes: &[u8],
328    resource: &CachedResource,
329) -> CachedResource {
330    CachedResource {
331        request_headers: resource.request_headers.clone(),
332        body: Arc::new(Mutex::new(ResponseBody::Done(bytes.to_owned()))),
333        aborted: Arc::new(AtomicBool::new(false)),
334        awaiting_body: Arc::new(Mutex::new(vec![])),
335        metadata: resource.metadata.clone(),
336        location_url: resource.location_url.clone(),
337        https_state: resource.https_state,
338        status: StatusCode::PARTIAL_CONTENT.into(),
339        url_list: resource.url_list.clone(),
340        expires: resource.expires,
341        last_validated: resource.last_validated,
342    }
343}
344
345/// Support for range requests <https://tools.ietf.org/html/rfc7233>.
346fn handle_range_request(
347    request: &Request,
348    candidates: &[&CachedResource],
349    range_spec: &Range,
350    done_chan: &mut DoneChannel,
351) -> Option<CachedResponse> {
352    let mut complete_cached_resources = candidates
353        .iter()
354        .filter(|resource| resource.status == StatusCode::OK);
355    let partial_cached_resources = candidates
356        .iter()
357        .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
358    if let Some(complete_resource) = complete_cached_resources.next() {
359        // TODO: take the full range spec into account.
360        // If we have a complete resource, take the request range from the body.
361        // When there isn't a complete resource available, we loop over cached partials,
362        // and see if any individual partial response can fulfill the current request for a bytes range.
363        // TODO: combine partials that in combination could satisfy the requested range?
364        // see <https://tools.ietf.org/html/rfc7233#section-4.3>.
365        // TODO: add support for complete and partial resources,
366        // whose body is in the ResponseBody::Receiving state.
367        let body_len = match *complete_resource.body.lock().unwrap() {
368            ResponseBody::Done(ref body) => body.len(),
369            _ => 0,
370        };
371        let bound = range_spec
372            .satisfiable_ranges(body_len.try_into().unwrap())
373            .next()
374            .unwrap();
375        match bound {
376            (Bound::Included(beginning), Bound::Included(end)) => {
377                if let ResponseBody::Done(ref body) = *complete_resource.body.lock().unwrap() {
378                    if end == u64::MAX {
379                        // Prevent overflow on the addition below.
380                        return None;
381                    }
382                    let b = beginning as usize;
383                    let e = end as usize + 1;
384                    let requested = body.get(b..e);
385                    if let Some(bytes) = requested {
386                        let new_resource =
387                            create_resource_with_bytes_from_resource(bytes, complete_resource);
388                        let cached_headers = new_resource.metadata.headers.lock().unwrap();
389                        let cached_response = create_cached_response(
390                            request,
391                            &new_resource,
392                            &cached_headers,
393                            done_chan,
394                        );
395                        if let Some(cached_response) = cached_response {
396                            return Some(cached_response);
397                        }
398                    }
399                }
400            },
401            (Bound::Included(beginning), Bound::Unbounded) => {
402                if let ResponseBody::Done(ref body) = *complete_resource.body.lock().unwrap() {
403                    let b = beginning as usize;
404                    let requested = body.get(b..);
405                    if let Some(bytes) = requested {
406                        let new_resource =
407                            create_resource_with_bytes_from_resource(bytes, complete_resource);
408                        let cached_headers = new_resource.metadata.headers.lock().unwrap();
409                        let cached_response = create_cached_response(
410                            request,
411                            &new_resource,
412                            &cached_headers,
413                            done_chan,
414                        );
415                        if let Some(cached_response) = cached_response {
416                            return Some(cached_response);
417                        }
418                    }
419                }
420            },
421            _ => return None,
422        }
423    } else {
424        for partial_resource in partial_cached_resources {
425            let headers = partial_resource.metadata.headers.lock().unwrap();
426            let content_range = headers.typed_get::<ContentRange>();
427
428            let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
429                continue;
430            };
431            match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
432                (Bound::Included(beginning), Bound::Included(end)) => {
433                    let (res_beginning, res_end) = match content_range {
434                        Some(range) => {
435                            if let Some(bytes_range) = range.bytes_range() {
436                                bytes_range
437                            } else {
438                                continue;
439                            }
440                        },
441                        _ => continue,
442                    };
443                    if res_beginning <= beginning && res_end >= end {
444                        let resource_body = &*partial_resource.body.lock().unwrap();
445                        let requested = match resource_body {
446                            ResponseBody::Done(body) => {
447                                let b = beginning as usize - res_beginning as usize;
448                                let e = end as usize - res_beginning as usize + 1;
449                                body.get(b..e)
450                            },
451                            _ => continue,
452                        };
453                        if let Some(bytes) = requested {
454                            let new_resource =
455                                create_resource_with_bytes_from_resource(bytes, partial_resource);
456                            let cached_response =
457                                create_cached_response(request, &new_resource, &headers, done_chan);
458                            if let Some(cached_response) = cached_response {
459                                return Some(cached_response);
460                            }
461                        }
462                    }
463                },
464
465                (Bound::Included(beginning), Bound::Unbounded) => {
466                    let (res_beginning, res_end, total) = if let Some(range) = content_range {
467                        match (range.bytes_range(), range.bytes_len()) {
468                            (Some(bytes_range), Some(total)) => {
469                                (bytes_range.0, bytes_range.1, total)
470                            },
471                            _ => continue,
472                        }
473                    } else {
474                        continue;
475                    };
476                    if total == 0 {
477                        // Prevent overflow in the below operations from occuring.
478                        continue;
479                    };
480                    if res_beginning <= beginning && res_end == total - 1 {
481                        let resource_body = &*partial_resource.body.lock().unwrap();
482                        let requested = match resource_body {
483                            ResponseBody::Done(body) => {
484                                let from_byte = beginning as usize - res_beginning as usize;
485                                body.get(from_byte..)
486                            },
487                            _ => continue,
488                        };
489                        if let Some(bytes) = requested {
490                            let new_resource =
491                                create_resource_with_bytes_from_resource(bytes, partial_resource);
492                            let cached_response =
493                                create_cached_response(request, &new_resource, &headers, done_chan);
494                            if let Some(cached_response) = cached_response {
495                                return Some(cached_response);
496                            }
497                        }
498                    }
499                },
500
501                _ => continue,
502            }
503        }
504    }
505
506    None
507}
508
509impl HttpCache {
510    /// Constructing Responses from Caches.
511    /// <https://tools.ietf.org/html/rfc7234#section-4>
512    pub fn construct_response(
513        &self,
514        request: &Request,
515        done_chan: &mut DoneChannel,
516    ) -> Option<CachedResponse> {
517        if pref!(network_http_cache_disabled) {
518            return None;
519        }
520
521        // TODO: generate warning headers as appropriate <https://tools.ietf.org/html/rfc7234#section-5.5>
522        debug!("trying to construct cache response for {:?}", request.url());
523        if request.method != Method::GET {
524            // Only Get requests are cached, avoid a url based match for others.
525            debug!("non-GET method, not caching");
526            return None;
527        }
528        let entry_key = CacheKey::new(request);
529        let resources = self
530            .entries
531            .get(&entry_key)?
532            .iter()
533            .filter(|r| !r.aborted.load(Ordering::Relaxed));
534        let mut candidates = vec![];
535        for cached_resource in resources {
536            let mut can_be_constructed = true;
537            let cached_headers = cached_resource.metadata.headers.lock().unwrap();
538            let original_request_headers = cached_resource.request_headers.lock().unwrap();
539            if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
540                if vary_value.is_any() {
541                    debug!("vary value is any, not caching");
542                    can_be_constructed = false
543                } else {
544                    // For every header name found in the Vary header of the stored response.
545                    // Calculating Secondary Keys with Vary <https://tools.ietf.org/html/rfc7234#section-4.1>
546                    for vary_val in vary_value.iter_strs() {
547                        match request.headers.get(vary_val) {
548                            Some(header_data) => {
549                                // If the header is present in the request.
550                                if let Some(original_header_data) =
551                                    original_request_headers.get(vary_val)
552                                {
553                                    // Check that the value of the nominated header field,
554                                    // in the original request, matches the value in the current request.
555                                    if original_header_data != header_data {
556                                        debug!("headers don't match, not caching");
557                                        can_be_constructed = false;
558                                        break;
559                                    }
560                                }
561                            },
562                            None => {
563                                // If a header field is absent from a request,
564                                // it can only match a stored response if those headers,
565                                // were also absent in the original request.
566                                can_be_constructed =
567                                    original_request_headers.get(vary_val).is_none();
568                                if !can_be_constructed {
569                                    debug!("vary header present, not caching");
570                                }
571                            },
572                        }
573                        if !can_be_constructed {
574                            break;
575                        }
576                    }
577                }
578            }
579            if can_be_constructed {
580                candidates.push(cached_resource);
581            }
582        }
583        // Support for range requests
584        if let Some(range_spec) = request.headers.typed_get::<Range>() {
585            return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
586        }
587        while let Some(cached_resource) = candidates.pop() {
588            // Not a Range request.
589            // Do not allow 206 responses to be constructed.
590            //
591            // See https://tools.ietf.org/html/rfc7234#section-3.1
592            //
593            // A cache MUST NOT use an incomplete response to answer requests unless the
594            // response has been made complete or the request is partial and
595            // specifies a range that is wholly within the incomplete response.
596            //
597            // TODO: Combining partial content to fulfill a non-Range request
598            // see https://tools.ietf.org/html/rfc7234#section-3.3
599            match cached_resource.status.try_code() {
600                Some(ref code) => {
601                    if *code == StatusCode::PARTIAL_CONTENT {
602                        continue;
603                    }
604                },
605                None => continue,
606            }
607            // Returning a response that can be constructed
608            // TODO: select the most appropriate one, using a known mechanism from a selecting header field,
609            // or using the Date header to return the most recent one.
610            let cached_headers = cached_resource.metadata.headers.lock().unwrap();
611            let cached_response =
612                create_cached_response(request, cached_resource, &cached_headers, done_chan);
613            if let Some(cached_response) = cached_response {
614                return Some(cached_response);
615            }
616        }
617        debug!("couldn't find an appropriate response, not caching");
618        // The cache wasn't able to construct anything.
619        None
620    }
621
622    /// Wake-up consumers of cached resources
623    /// whose response body was still receiving data when the resource was constructed,
624    /// and whose response has now either been completed or cancelled.
625    pub fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
626        let entry_key = CacheKey::new(request);
627
628        let cached_resources = match self.entries.get(&entry_key) {
629            None => return,
630            Some(resources) => resources,
631        };
632
633        let actual_response = response.actual_response();
634
635        // Ensure we only wake-up consumers of relevant resources,
636        // ie we don't want to wake-up 200 awaiting consumers with a 206.
637        let relevant_cached_resources = cached_resources.iter().filter(|resource| {
638            if actual_response.is_network_error() {
639                return *resource.body.lock().unwrap() == ResponseBody::Empty;
640            }
641            resource.status == actual_response.status
642        });
643
644        for cached_resource in relevant_cached_resources {
645            let mut awaiting_consumers = cached_resource.awaiting_body.lock().unwrap();
646            if awaiting_consumers.is_empty() {
647                continue;
648            }
649            let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
650                // In the case of an aborted fetch,
651                // wake-up all awaiting consumers.
652                // Each will then start a new network request.
653                // TODO: Wake-up only one consumer, and make it the producer on which others wait.
654                Data::Cancelled
655            } else {
656                match *cached_resource.body.lock().unwrap() {
657                    ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
658                    ResponseBody::Receiving(_) => {
659                        continue;
660                    },
661                }
662            };
663            for done_sender in awaiting_consumers.drain(..) {
664                let _ = done_sender.send(to_send.clone());
665            }
666        }
667    }
668
669    /// Freshening Stored Responses upon Validation.
670    /// <https://tools.ietf.org/html/rfc7234#section-4.3.4>
671    pub fn refresh(
672        &mut self,
673        request: &Request,
674        response: Response,
675        done_chan: &mut DoneChannel,
676    ) -> Option<Response> {
677        assert_eq!(response.status, StatusCode::NOT_MODIFIED);
678        let entry_key = CacheKey::new(request);
679        if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
680            if let Some(cached_resource) = cached_resources.iter_mut().next() {
681                // done_chan will have been set to Some(..) by http_network_fetch.
682                // If the body is not receiving data, set the done_chan back to None.
683                // Otherwise, create a new dedicated channel to update the consumer.
684                // The response constructed here will replace the 304 one from the network.
685                let in_progress_channel = match *cached_resource.body.lock().unwrap() {
686                    ResponseBody::Receiving(..) => Some(unbounded()),
687                    ResponseBody::Empty | ResponseBody::Done(..) => None,
688                };
689                match in_progress_channel {
690                    Some((done_sender, done_receiver)) => {
691                        *done_chan = Some((done_sender.clone(), done_receiver));
692                        cached_resource
693                            .awaiting_body
694                            .lock()
695                            .unwrap()
696                            .push(done_sender);
697                    },
698                    None => *done_chan = None,
699                }
700                // Received a response with 304 status code, in response to a request that matches a cached resource.
701                // 1. update the headers of the cached resource.
702                // 2. return a response, constructed from the cached resource.
703                let resource_timing = ResourceFetchTiming::new(request.timing_type());
704                let mut constructed_response =
705                    Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
706                constructed_response.body = cached_resource.body.clone();
707                constructed_response
708                    .status
709                    .clone_from(&cached_resource.status);
710                constructed_response.https_state = cached_resource.https_state;
711                constructed_response.referrer = request.referrer.to_url().cloned();
712                constructed_response.referrer_policy = request.referrer_policy;
713                constructed_response
714                    .status
715                    .clone_from(&cached_resource.status);
716                constructed_response
717                    .url_list
718                    .clone_from(&cached_resource.url_list);
719                cached_resource.expires = get_response_expiry(&constructed_response);
720                let mut stored_headers = cached_resource.metadata.headers.lock().unwrap();
721                stored_headers.extend(response.headers);
722                constructed_response.headers = stored_headers.clone();
723                return Some(constructed_response);
724            }
725        }
726        None
727    }
728
729    fn invalidate_for_url(&mut self, url: &ServoUrl) {
730        let entry_key = CacheKey::from_servo_url(url);
731        if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
732            for cached_resource in cached_resources.iter_mut() {
733                cached_resource.expires = Duration::ZERO;
734            }
735        }
736    }
737
738    /// Invalidation.
739    /// <https://tools.ietf.org/html/rfc7234#section-4.4>
740    pub fn invalidate(&mut self, request: &Request, response: &Response) {
741        // TODO(eijebong): Once headers support typed_get, update this to use them
742        if let Some(Ok(location)) = response
743            .headers
744            .get(header::LOCATION)
745            .map(HeaderValue::to_str)
746        {
747            if let Ok(url) = request.current_url().join(location) {
748                self.invalidate_for_url(&url);
749            }
750        }
751        if let Some(Ok(content_location)) = response
752            .headers
753            .get(header::CONTENT_LOCATION)
754            .map(HeaderValue::to_str)
755        {
756            if let Ok(url) = request.current_url().join(content_location) {
757                self.invalidate_for_url(&url);
758            }
759        }
760        self.invalidate_for_url(&request.url());
761    }
762
763    /// Storing Responses in Caches.
764    /// <https://tools.ietf.org/html/rfc7234#section-3>
765    pub fn store(&mut self, request: &Request, response: &Response) {
766        if pref!(network_http_cache_disabled) {
767            return;
768        }
769        if request.method != Method::GET {
770            // Only Get requests are cached.
771            return;
772        }
773        if request.headers.contains_key(header::AUTHORIZATION) {
774            // https://tools.ietf.org/html/rfc7234#section-3.1
775            // A shared cache MUST NOT use a cached response
776            // to a request with an Authorization header field
777            //
778            // TODO: unless a cache directive that allows such
779            // responses to be stored is present in the response.
780            return;
781        };
782        let entry_key = CacheKey::new(request);
783        let metadata = match response.metadata() {
784            Ok(FetchMetadata::Filtered {
785                filtered: _,
786                unsafe_: metadata,
787            }) |
788            Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
789            _ => return,
790        };
791        if !response_is_cacheable(&metadata) {
792            return;
793        }
794        let expiry = get_response_expiry(response);
795        let cacheable_metadata = CachedMetadata {
796            headers: Arc::new(Mutex::new(response.headers.clone())),
797            final_url: metadata.final_url,
798            content_type: metadata.content_type.map(|v| v.0.to_string()),
799            charset: metadata.charset,
800            status: metadata.status,
801        };
802        let entry_resource = CachedResource {
803            request_headers: Arc::new(Mutex::new(request.headers.clone())),
804            body: response.body.clone(),
805            aborted: response.aborted.clone(),
806            awaiting_body: Arc::new(Mutex::new(vec![])),
807            metadata: cacheable_metadata,
808            location_url: response.location_url.clone(),
809            https_state: response.https_state,
810            status: response.status.clone(),
811            url_list: response.url_list.clone(),
812            expires: expiry,
813            last_validated: Instant::now(),
814        };
815        let entry = self.entries.entry(entry_key).or_default();
816        entry.push(entry_resource);
817        // TODO: Complete incomplete responses, including 206 response, when stored here.
818        // See A cache MAY complete a stored incomplete response by making a subsequent range request
819        // https://tools.ietf.org/html/rfc7234#section-3.1
820    }
821
822    /// Clear the contents of this cache.
823    pub fn clear(&mut self) {
824        self.entries.clear();
825    }
826}