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