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