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::atomic::{AtomicBool, Ordering};
13use std::time::{Duration, Instant, SystemTime};
14
15use headers::{
16    CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary,
17};
18use http::header::HeaderValue;
19use http::{HeaderMap, Method, StatusCode, header};
20use log::debug;
21use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
22use malloc_size_of_derive::MallocSizeOf;
23use net_traits::http_status::HttpStatus;
24use net_traits::request::Request;
25use net_traits::response::{HttpsState, Response, ResponseBody};
26use net_traits::{FetchMetadata, Metadata, ResourceFetchTiming};
27use parking_lot::Mutex;
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() {
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.awaiting_body.lock().push(done_sender);
294    }
295    response
296        .location_url
297        .clone_from(&cached_resource.location_url);
298    response.status.clone_from(&cached_resource.status);
299    response.url_list.clone_from(&cached_resource.url_list);
300    response.https_state = cached_resource.https_state;
301    response.referrer = request.referrer.to_url().cloned();
302    response.referrer_policy = request.referrer_policy;
303    response.aborted = cached_resource.aborted.clone();
304
305    let expires = cached_resource.expires;
306    let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
307    let time_since_validated = Instant::now() - cached_resource.last_validated;
308
309    // TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1>
310    // TODO: if this cache is to be considered shared, take proxy-revalidate into account
311    // <https://tools.ietf.org/html/rfc7234#section-5.2.2.7>
312    let has_expired = adjusted_expires <= time_since_validated;
313    let cached_response = CachedResponse {
314        response,
315        needs_validation: has_expired,
316    };
317    Some(cached_response)
318}
319
320/// Create a new resource, based on the bytes requested, and an existing resource,
321/// with a status-code of 206.
322fn create_resource_with_bytes_from_resource(
323    bytes: &[u8],
324    resource: &CachedResource,
325) -> CachedResource {
326    CachedResource {
327        request_headers: resource.request_headers.clone(),
328        body: Arc::new(Mutex::new(ResponseBody::Done(bytes.to_owned()))),
329        aborted: Arc::new(AtomicBool::new(false)),
330        awaiting_body: Arc::new(Mutex::new(vec![])),
331        metadata: resource.metadata.clone(),
332        location_url: resource.location_url.clone(),
333        https_state: resource.https_state,
334        status: StatusCode::PARTIAL_CONTENT.into(),
335        url_list: resource.url_list.clone(),
336        expires: resource.expires,
337        last_validated: resource.last_validated,
338    }
339}
340
341/// Support for range requests <https://tools.ietf.org/html/rfc7233>.
342fn handle_range_request(
343    request: &Request,
344    candidates: &[&CachedResource],
345    range_spec: &Range,
346    done_chan: &mut DoneChannel,
347) -> Option<CachedResponse> {
348    let mut complete_cached_resources = candidates
349        .iter()
350        .filter(|resource| resource.status == StatusCode::OK);
351    let partial_cached_resources = candidates
352        .iter()
353        .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
354    if let Some(complete_resource) = complete_cached_resources.next() {
355        // TODO: take the full range spec into account.
356        // If we have a complete resource, take the request range from the body.
357        // When there isn't a complete resource available, we loop over cached partials,
358        // and see if any individual partial response can fulfill the current request for a bytes range.
359        // TODO: combine partials that in combination could satisfy the requested range?
360        // see <https://tools.ietf.org/html/rfc7233#section-4.3>.
361        // TODO: add support for complete and partial resources,
362        // whose body is in the ResponseBody::Receiving state.
363        let body_len = match *complete_resource.body.lock() {
364            ResponseBody::Done(ref body) => body.len(),
365            _ => 0,
366        };
367        let bound = range_spec
368            .satisfiable_ranges(body_len.try_into().unwrap())
369            .next()
370            .unwrap();
371        match bound {
372            (Bound::Included(beginning), Bound::Included(end)) => {
373                if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
374                    if end == u64::MAX {
375                        // Prevent overflow on the addition below.
376                        return None;
377                    }
378                    let b = beginning as usize;
379                    let e = end as usize + 1;
380                    let requested = body.get(b..e);
381                    if let Some(bytes) = requested {
382                        let new_resource =
383                            create_resource_with_bytes_from_resource(bytes, complete_resource);
384                        let cached_headers = new_resource.metadata.headers.lock();
385                        let cached_response = create_cached_response(
386                            request,
387                            &new_resource,
388                            &cached_headers,
389                            done_chan,
390                        );
391                        if let Some(cached_response) = cached_response {
392                            return Some(cached_response);
393                        }
394                    }
395                }
396            },
397            (Bound::Included(beginning), Bound::Unbounded) => {
398                if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
399                    let b = beginning as usize;
400                    let requested = body.get(b..);
401                    if let Some(bytes) = requested {
402                        let new_resource =
403                            create_resource_with_bytes_from_resource(bytes, complete_resource);
404                        let cached_headers = new_resource.metadata.headers.lock();
405                        let cached_response = create_cached_response(
406                            request,
407                            &new_resource,
408                            &cached_headers,
409                            done_chan,
410                        );
411                        if let Some(cached_response) = cached_response {
412                            return Some(cached_response);
413                        }
414                    }
415                }
416            },
417            _ => return None,
418        }
419    } else {
420        for partial_resource in partial_cached_resources {
421            let headers = partial_resource.metadata.headers.lock();
422            let content_range = headers.typed_get::<ContentRange>();
423
424            let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
425                continue;
426            };
427            match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
428                (Bound::Included(beginning), Bound::Included(end)) => {
429                    let (res_beginning, res_end) = match content_range {
430                        Some(range) => {
431                            if let Some(bytes_range) = range.bytes_range() {
432                                bytes_range
433                            } else {
434                                continue;
435                            }
436                        },
437                        _ => continue,
438                    };
439                    if res_beginning <= beginning && res_end >= end {
440                        let resource_body = &*partial_resource.body.lock();
441                        let requested = match resource_body {
442                            ResponseBody::Done(body) => {
443                                let b = beginning as usize - res_beginning as usize;
444                                let e = end as usize - res_beginning as usize + 1;
445                                body.get(b..e)
446                            },
447                            _ => continue,
448                        };
449                        if let Some(bytes) = requested {
450                            let new_resource =
451                                create_resource_with_bytes_from_resource(bytes, partial_resource);
452                            let cached_response =
453                                create_cached_response(request, &new_resource, &headers, done_chan);
454                            if let Some(cached_response) = cached_response {
455                                return Some(cached_response);
456                            }
457                        }
458                    }
459                },
460
461                (Bound::Included(beginning), Bound::Unbounded) => {
462                    let (res_beginning, res_end, total) = if let Some(range) = content_range {
463                        match (range.bytes_range(), range.bytes_len()) {
464                            (Some(bytes_range), Some(total)) => {
465                                (bytes_range.0, bytes_range.1, total)
466                            },
467                            _ => continue,
468                        }
469                    } else {
470                        continue;
471                    };
472                    if total == 0 {
473                        // Prevent overflow in the below operations from occuring.
474                        continue;
475                    };
476                    if res_beginning <= beginning && res_end == total - 1 {
477                        let resource_body = &*partial_resource.body.lock();
478                        let requested = match resource_body {
479                            ResponseBody::Done(body) => {
480                                let from_byte = beginning as usize - res_beginning as usize;
481                                body.get(from_byte..)
482                            },
483                            _ => continue,
484                        };
485                        if let Some(bytes) = requested {
486                            let new_resource =
487                                create_resource_with_bytes_from_resource(bytes, partial_resource);
488                            let cached_response =
489                                create_cached_response(request, &new_resource, &headers, done_chan);
490                            if let Some(cached_response) = cached_response {
491                                return Some(cached_response);
492                            }
493                        }
494                    }
495                },
496
497                _ => continue,
498            }
499        }
500    }
501
502    None
503}
504
505impl HttpCache {
506    /// Constructing Responses from Caches.
507    /// <https://tools.ietf.org/html/rfc7234#section-4>
508    pub fn construct_response(
509        &self,
510        request: &Request,
511        done_chan: &mut DoneChannel,
512    ) -> Option<CachedResponse> {
513        if pref!(network_http_cache_disabled) {
514            return None;
515        }
516
517        // TODO: generate warning headers as appropriate <https://tools.ietf.org/html/rfc7234#section-5.5>
518        debug!("trying to construct cache response for {:?}", request.url());
519        if request.method != Method::GET {
520            // Only Get requests are cached, avoid a url based match for others.
521            debug!("non-GET method, not caching");
522            return None;
523        }
524        let entry_key = CacheKey::new(request);
525        let resources = self
526            .entries
527            .get(&entry_key)?
528            .iter()
529            .filter(|r| !r.aborted.load(Ordering::Relaxed));
530        let mut candidates = vec![];
531        for cached_resource in resources {
532            let mut can_be_constructed = true;
533            let cached_headers = cached_resource.metadata.headers.lock();
534            let original_request_headers = cached_resource.request_headers.lock();
535            if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
536                if vary_value.is_any() {
537                    debug!("vary value is any, not caching");
538                    can_be_constructed = false
539                } else {
540                    // For every header name found in the Vary header of the stored response.
541                    // Calculating Secondary Keys with Vary <https://tools.ietf.org/html/rfc7234#section-4.1>
542                    for vary_val in vary_value.iter_strs() {
543                        match request.headers.get(vary_val) {
544                            Some(header_data) => {
545                                // If the header is present in the request.
546                                if let Some(original_header_data) =
547                                    original_request_headers.get(vary_val)
548                                {
549                                    // Check that the value of the nominated header field,
550                                    // in the original request, matches the value in the current request.
551                                    if original_header_data != header_data {
552                                        debug!("headers don't match, not caching");
553                                        can_be_constructed = false;
554                                        break;
555                                    }
556                                }
557                            },
558                            None => {
559                                // If a header field is absent from a request,
560                                // it can only match a stored response if those headers,
561                                // were also absent in the original request.
562                                can_be_constructed =
563                                    original_request_headers.get(vary_val).is_none();
564                                if !can_be_constructed {
565                                    debug!("vary header present, not caching");
566                                }
567                            },
568                        }
569                        if !can_be_constructed {
570                            break;
571                        }
572                    }
573                }
574            }
575            if can_be_constructed {
576                candidates.push(cached_resource);
577            }
578        }
579        // Support for range requests
580        if let Some(range_spec) = request.headers.typed_get::<Range>() {
581            return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
582        }
583        while let Some(cached_resource) = candidates.pop() {
584            // Not a Range request.
585            // Do not allow 206 responses to be constructed.
586            //
587            // See https://tools.ietf.org/html/rfc7234#section-3.1
588            //
589            // A cache MUST NOT use an incomplete response to answer requests unless the
590            // response has been made complete or the request is partial and
591            // specifies a range that is wholly within the incomplete response.
592            //
593            // TODO: Combining partial content to fulfill a non-Range request
594            // see https://tools.ietf.org/html/rfc7234#section-3.3
595            match cached_resource.status.try_code() {
596                Some(ref code) => {
597                    if *code == StatusCode::PARTIAL_CONTENT {
598                        continue;
599                    }
600                },
601                None => continue,
602            }
603            // Returning a response that can be constructed
604            // TODO: select the most appropriate one, using a known mechanism from a selecting header field,
605            // or using the Date header to return the most recent one.
606            let cached_headers = cached_resource.metadata.headers.lock();
607            let cached_response =
608                create_cached_response(request, cached_resource, &cached_headers, done_chan);
609            if let Some(cached_response) = cached_response {
610                return Some(cached_response);
611            }
612        }
613        debug!("couldn't find an appropriate response, not caching");
614        // The cache wasn't able to construct anything.
615        None
616    }
617
618    /// Wake-up consumers of cached resources
619    /// whose response body was still receiving data when the resource was constructed,
620    /// and whose response has now either been completed or cancelled.
621    pub fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
622        let entry_key = CacheKey::new(request);
623
624        let cached_resources = match self.entries.get(&entry_key) {
625            None => return,
626            Some(resources) => resources,
627        };
628
629        let actual_response = response.actual_response();
630
631        // Ensure we only wake-up consumers of relevant resources,
632        // ie we don't want to wake-up 200 awaiting consumers with a 206.
633        let relevant_cached_resources = cached_resources.iter().filter(|resource| {
634            if actual_response.is_network_error() {
635                return *resource.body.lock() == ResponseBody::Empty;
636            }
637            resource.status == actual_response.status
638        });
639
640        for cached_resource in relevant_cached_resources {
641            let mut awaiting_consumers = cached_resource.awaiting_body.lock();
642            if awaiting_consumers.is_empty() {
643                continue;
644            }
645            let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
646                // In the case of an aborted fetch,
647                // wake-up all awaiting consumers.
648                // Each will then start a new network request.
649                // TODO: Wake-up only one consumer, and make it the producer on which others wait.
650                Data::Cancelled
651            } else {
652                match *cached_resource.body.lock() {
653                    ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
654                    ResponseBody::Receiving(_) => {
655                        continue;
656                    },
657                }
658            };
659            for done_sender in awaiting_consumers.drain(..) {
660                let _ = done_sender.send(to_send.clone());
661            }
662        }
663    }
664
665    /// Freshening Stored Responses upon Validation.
666    /// <https://tools.ietf.org/html/rfc7234#section-4.3.4>
667    pub fn refresh(
668        &mut self,
669        request: &Request,
670        response: Response,
671        done_chan: &mut DoneChannel,
672    ) -> Option<Response> {
673        assert_eq!(response.status, StatusCode::NOT_MODIFIED);
674        let entry_key = CacheKey::new(request);
675        if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
676            if let Some(cached_resource) = cached_resources.iter_mut().next() {
677                let mut constructed_response = if let Some(range_spec) =
678                    request.headers.typed_get::<Range>()
679                {
680                    handle_range_request(request, &[cached_resource], &range_spec, done_chan)
681                        .map(|cached_response| cached_response.response)
682                } else {
683                    // done_chan will have been set to Some(..) by http_network_fetch.
684                    // If the body is not receiving data, set the done_chan back to None.
685                    // Otherwise, create a new dedicated channel to update the consumer.
686                    // The response constructed here will replace the 304 one from the network.
687                    let in_progress_channel = match &*cached_resource.body.lock() {
688                        ResponseBody::Receiving(..) => Some(unbounded()),
689                        ResponseBody::Empty | ResponseBody::Done(..) => None,
690                    };
691                    match in_progress_channel {
692                        Some((done_sender, done_receiver)) => {
693                            *done_chan = Some((done_sender.clone(), done_receiver));
694                            cached_resource.awaiting_body.lock().push(done_sender);
695                        },
696                        None => *done_chan = None,
697                    }
698                    // Received a response with 304 status code, in response to a request that matches a cached resource.
699                    // 1. update the headers of the cached resource.
700                    // 2. return a response, constructed from the cached resource.
701                    let resource_timing = ResourceFetchTiming::new(request.timing_type());
702                    let mut constructed_response =
703                        Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
704
705                    constructed_response.body = cached_resource.body.clone();
706
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                    Some(constructed_response)
720                };
721                // Update cached Resource with response and constructed response.
722                if let Some(constructed_response) = constructed_response.as_mut() {
723                    cached_resource.expires = get_response_expiry(constructed_response);
724                    let mut stored_headers = cached_resource.metadata.headers.lock();
725                    stored_headers.extend(response.headers);
726                    constructed_response.headers = stored_headers.clone();
727                }
728
729                return constructed_response;
730            }
731        }
732        None
733    }
734
735    fn invalidate_for_url(&mut self, url: &ServoUrl) {
736        let entry_key = CacheKey::from_servo_url(url);
737        if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
738            for cached_resource in cached_resources.iter_mut() {
739                cached_resource.expires = Duration::ZERO;
740            }
741        }
742    }
743
744    /// Invalidation.
745    /// <https://tools.ietf.org/html/rfc7234#section-4.4>
746    pub fn invalidate(&mut self, request: &Request, response: &Response) {
747        // TODO(eijebong): Once headers support typed_get, update this to use them
748        if let Some(Ok(location)) = response
749            .headers
750            .get(header::LOCATION)
751            .map(HeaderValue::to_str)
752        {
753            if let Ok(url) = request.current_url().join(location) {
754                self.invalidate_for_url(&url);
755            }
756        }
757        if let Some(Ok(content_location)) = response
758            .headers
759            .get(header::CONTENT_LOCATION)
760            .map(HeaderValue::to_str)
761        {
762            if let Ok(url) = request.current_url().join(content_location) {
763                self.invalidate_for_url(&url);
764            }
765        }
766        self.invalidate_for_url(&request.url());
767    }
768
769    /// Storing Responses in Caches.
770    /// <https://tools.ietf.org/html/rfc7234#section-3>
771    pub fn store(&mut self, request: &Request, response: &Response) {
772        if pref!(network_http_cache_disabled) {
773            return;
774        }
775        if request.method != Method::GET {
776            // Only Get requests are cached.
777            return;
778        }
779        if request.headers.contains_key(header::AUTHORIZATION) {
780            // https://tools.ietf.org/html/rfc7234#section-3.1
781            // A shared cache MUST NOT use a cached response
782            // to a request with an Authorization header field
783            //
784            // TODO: unless a cache directive that allows such
785            // responses to be stored is present in the response.
786            return;
787        };
788        let entry_key = CacheKey::new(request);
789        let metadata = match response.metadata() {
790            Ok(FetchMetadata::Filtered {
791                filtered: _,
792                unsafe_: metadata,
793            }) |
794            Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
795            _ => return,
796        };
797        if !response_is_cacheable(&metadata) {
798            return;
799        }
800        let expiry = get_response_expiry(response);
801        let cacheable_metadata = CachedMetadata {
802            headers: Arc::new(Mutex::new(response.headers.clone())),
803            final_url: metadata.final_url,
804            content_type: metadata.content_type.map(|v| v.0.to_string()),
805            charset: metadata.charset,
806            status: metadata.status,
807        };
808        let entry_resource = CachedResource {
809            request_headers: Arc::new(Mutex::new(request.headers.clone())),
810            body: response.body.clone(),
811            aborted: response.aborted.clone(),
812            awaiting_body: Arc::new(Mutex::new(vec![])),
813            metadata: cacheable_metadata,
814            location_url: response.location_url.clone(),
815            https_state: response.https_state,
816            status: response.status.clone(),
817            url_list: response.url_list.clone(),
818            expires: expiry,
819            last_validated: Instant::now(),
820        };
821        let entry = self.entries.entry(entry_key).or_default();
822        entry.push(entry_resource);
823        // TODO: Complete incomplete responses, including 206 response, when stored here.
824        // See A cache MAY complete a stored incomplete response by making a subsequent range request
825        // https://tools.ietf.org/html/rfc7234#section-3.1
826    }
827
828    /// Clear the contents of this cache.
829    pub fn clear(&mut self) {
830        self.entries.clear();
831    }
832}