Skip to main content

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::Arc as StdArc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::time::{Duration, SystemTime};
14
15use headers::{
16    CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary,
17};
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::{CacheMode, Request};
24use net_traits::response::{Response, ResponseBody};
25use net_traits::{CacheEntryDescriptor, FetchMetadata, Metadata, ResourceFetchTiming};
26use parking_lot::Mutex as ParkingLotMutex;
27use quick_cache::sync::{Cache, PlaceholderGuard};
28use quick_cache::{DefaultHashBuilder, Lifecycle, UnitWeighter};
29use serde::{Deserialize, Serialize};
30use servo_arc::Arc;
31use servo_config::pref;
32use servo_url::ServoUrl;
33use tokio::sync::mpsc::{UnboundedSender as TokioSender, unbounded_channel as unbounded};
34use tokio::sync::{OwnedRwLockWriteGuard, RwLock as TokioRwLock};
35
36use crate::disk_cache::DiskCache;
37use crate::fetch::methods::{Data, DoneChannel};
38
39/// A duration in seconds.
40///
41/// This is the same as std::time::Duration except that we do not care about nanosecond precision.
42#[derive(
43    Copy,
44    Clone,
45    Default,
46    Debug,
47    Deserialize,
48    MallocSizeOf,
49    Serialize,
50    PartialEq,
51    Eq,
52    PartialOrd,
53    Ord,
54)]
55struct ApproxDuration(u64);
56
57impl ApproxDuration {
58    const fn zero() -> Self {
59        Self(0)
60    }
61
62    fn is_zero(&self) -> bool {
63        self.0 == 0
64    }
65
66    fn saturating_sub(&self, rhs: ApproxDuration) -> Self {
67        Self(self.0.saturating_sub(rhs.0))
68    }
69
70    fn from_secs(seconds: u64) -> Self {
71        Self(seconds)
72    }
73}
74
75impl From<Duration> for ApproxDuration {
76    fn from(value: Duration) -> Self {
77        Self(value.as_secs())
78    }
79}
80
81impl std::ops::Add for ApproxDuration {
82    type Output = Self;
83
84    fn add(self, rhs: Self) -> Self::Output {
85        Self(self.0 + rhs.0)
86    }
87}
88
89impl std::ops::Sub for ApproxDuration {
90    type Output = Self;
91
92    fn sub(self, rhs: Self) -> Self::Output {
93        Self(self.0 - rhs.0)
94    }
95}
96
97impl std::ops::Div<u64> for ApproxDuration {
98    type Output = Self;
99
100    fn div(self, rhs: u64) -> Self::Output {
101        Self(self.0 / rhs)
102    }
103}
104
105/// The key used to differentiate requests in the cache.
106#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
107pub struct CacheKey {
108    url: ServoUrl,
109}
110
111impl AsRef<str> for CacheKey {
112    fn as_ref(&self) -> &str {
113        self.url.as_str()
114    }
115}
116
117impl CacheKey {
118    /// Create a cache-key from a request.
119    pub fn new(request: &Request) -> CacheKey {
120        CacheKey {
121            url: request.current_url(),
122        }
123    }
124
125    /// Create a cache-key from a resolved URL.
126    pub fn from_url(url: ServoUrl) -> CacheKey {
127        CacheKey { url }
128    }
129}
130
131/// A complete cached resource.
132#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
133pub struct CachedResource {
134    #[conditional_malloc_size_of]
135    request_headers: Arc<ParkingLotMutex<SerializeableHeaderMap>>,
136    #[conditional_malloc_size_of]
137    body: Arc<ParkingLotMutex<ResponseBody>>,
138    #[conditional_malloc_size_of]
139    aborted: Arc<AtomicBool>,
140    #[conditional_malloc_size_of]
141    #[serde(skip)]
142    awaiting_body: Arc<ParkingLotMutex<Vec<TokioSender<Data>>>>,
143    metadata: CachedMetadata,
144    location_url: Option<Result<ServoUrl, String>>,
145    status: HttpStatus,
146    url_list: Vec<ServoUrl>,
147    expires: ApproxDuration,
148    stale_while_revalidate: ApproxDuration,
149    #[conditional_malloc_size_of]
150    #[serde(skip)]
151    revalidating: StdArc<AtomicBool>,
152    last_validated: SystemTime,
153}
154
155impl CachedResource {
156    pub(crate) fn is_done(&self) -> bool {
157        self.body.lock().is_done()
158    }
159}
160
161#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
162/// Wrapper type for HeaderMap
163struct SerializeableHeaderMap(
164    #[serde(
165        deserialize_with = "hyper_serde::deserialize",
166        serialize_with = "hyper_serde::serialize"
167    )]
168    HeaderMap,
169);
170
171impl std::ops::Deref for SerializeableHeaderMap {
172    type Target = HeaderMap;
173
174    fn deref(&self) -> &Self::Target {
175        &self.0
176    }
177}
178
179impl std::ops::DerefMut for SerializeableHeaderMap {
180    fn deref_mut(&mut self) -> &mut Self::Target {
181        &mut self.0
182    }
183}
184
185impl From<HeaderMap> for SerializeableHeaderMap {
186    fn from(value: HeaderMap) -> Self {
187        SerializeableHeaderMap(value)
188    }
189}
190
191/// Metadata about a loaded resource, such as is obtained from HTTP headers.
192#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
193struct CachedMetadata {
194    /// Headers
195    #[conditional_malloc_size_of]
196    pub headers: Arc<ParkingLotMutex<SerializeableHeaderMap>>,
197    /// Final URL after redirects.
198    pub final_url: ServoUrl,
199    /// MIME type / subtype.
200    pub content_type: Option<String>,
201    /// Character set.
202    pub charset: Option<String>,
203    /// HTTP Status
204    pub status: HttpStatus,
205}
206
207/// Whether a cached response is fresh or requires validation before or after use.
208#[derive(Clone, Copy, Debug, Eq, PartialEq)]
209pub enum ValidationStatus {
210    /// The response is fresh and can be used without any revalidation.
211    Valid,
212    /// The response is stale.
213    Stale {
214        /// Whether the stale response can be served immediately, leaving the
215        /// caller responsible for revalidating it in the background.
216        revalidate_in_background: bool,
217    },
218}
219
220/// Wrapper around a cached response, including information on re-validation needs
221pub(crate) struct CachedResponse {
222    /// The response constructed from the cached resource
223    pub response: Response,
224    /// Whether the stored response is fresh or stale
225    pub validation_status: ValidationStatus,
226    /// Single-flight guard for the background revalidation.
227    pub revalidation_guard: StdArc<AtomicBool>,
228}
229
230pub(crate) type CacheEntry = std::sync::Arc<TokioRwLock<Vec<CachedResource>>>;
231type QuickCache =
232    Cache<CacheKey, CacheEntry, UnitWeighter, DefaultHashBuilder, MemoryCacheLifecycle>;
233type QuickCachePlaceholderGuard<'a> = PlaceholderGuard<
234    'a,
235    CacheKey,
236    CacheEntry,
237    UnitWeighter,
238    DefaultHashBuilder,
239    MemoryCacheLifecycle,
240>;
241
242/// Is this state assigned to the private or public side.
243#[derive(Debug, MallocSizeOf, PartialEq)]
244pub enum HttpCacheAssignment {
245    /// Public Cache State, possibly stored to disk.
246    Public,
247    /// Private Cache State, not stored to disk.
248    Private,
249}
250
251/// A simple memory cache.
252/// Elements will be evicted based on the cache heuristic. We weight elements
253/// by the number of entries per given url. We evict currently a whole url.
254/// The cache makes extensive use of `Arc::unwrap_or_clone or` and `Arc::into_inner`
255/// to modify the cached entries. This is ok because `CachedResource` are cheap to clone
256pub struct HttpCache {
257    /// cached responses.
258    entries: QuickCache,
259    disk_cache: Option<std::sync::Arc<DiskCache>>,
260}
261
262impl MallocSizeOf for HttpCache {
263    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
264        self.entries
265            .iter()
266            .map(|(_key, entry)| entry.blocking_read().size_of(ops))
267            .sum::<usize>() +
268            self.disk_cache
269                .as_ref()
270                .map(|data| data.size_of(ops))
271                .unwrap_or(0)
272    }
273}
274
275impl HttpCache {
276    /// Create a new HttpCache with [`HttpCacheAssignment`]
277    pub fn new(assignment: HttpCacheAssignment) -> Self {
278        let size = pref!(network_http_cache_size)
279            .try_into()
280            .expect("http_cache_size needs to fit into u64");
281        let (disk_cache, lifecycle) = DiskCache::new(assignment);
282        let memory_cache = Cache::with(
283            size,
284            size as u64,
285            UnitWeighter,
286            DefaultHashBuilder::default(),
287            lifecycle,
288        );
289
290        Self {
291            entries: memory_cache,
292            disk_cache,
293        }
294    }
295}
296
297#[derive(Clone)]
298/// The lifecycle hooks of the HttpCache.
299/// Responsible for moving data to the disk.
300pub struct MemoryCacheLifecycle {
301    pub(crate) disk_cache: Option<std::sync::Arc<DiskCache>>,
302}
303
304impl MemoryCacheLifecycle {
305    pub(crate) fn empty() -> MemoryCacheLifecycle {
306        MemoryCacheLifecycle { disk_cache: None }
307    }
308}
309
310impl Lifecycle<CacheKey, CacheEntry> for MemoryCacheLifecycle {
311    type RequestState = ();
312
313    fn on_evict(&self, _state: &mut Self::RequestState, key: CacheKey, value: CacheEntry) {
314        if let Some(disk_cache_data) = &self.disk_cache {
315            let disk_cache_data = disk_cache_data.clone();
316            tokio::spawn(async move { disk_cache_data.store(key, value).await });
317        }
318    }
319}
320
321/// Determine if a response is cacheable by default <https://tools.ietf.org/html/rfc7231#section-6.1>
322fn is_cacheable_by_default(status_code: StatusCode) -> bool {
323    matches!(
324        status_code.as_u16(),
325        200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
326    )
327}
328
329/// Determine if a given response is cacheable.
330/// Based on <https://tools.ietf.org/html/rfc7234#section-3>
331fn response_is_cacheable(metadata: &Metadata) -> bool {
332    // TODO: if we determine that this cache should be considered shared:
333    // 1. check for absence of private response directive <https://tools.ietf.org/html/rfc7234#section-5.2.2.6>
334    // 2. check for absence of the Authorization header field.
335    let mut is_cacheable = false;
336    let headers = metadata.headers.as_ref().unwrap();
337    if headers.contains_key(header::EXPIRES) ||
338        headers.contains_key(header::LAST_MODIFIED) ||
339        headers.contains_key(header::ETAG)
340    {
341        is_cacheable = true;
342    }
343    if let Some(ref directive) = headers.typed_get::<CacheControl>() {
344        if directive.no_store() {
345            return false;
346        }
347        if directive.public() ||
348            directive.s_max_age().is_some() ||
349            directive.max_age().is_some() ||
350            directive.no_cache()
351        {
352            // If cache-control is understood, we can use it and ignore pragma.
353            return true;
354        }
355    }
356    if let Some(pragma) = headers.typed_get::<Pragma>() &&
357        pragma.is_no_cache()
358    {
359        return false;
360    }
361    is_cacheable
362}
363
364/// Calculating Age
365/// <https://tools.ietf.org/html/rfc7234#section-4.2.3>
366fn calculate_response_age(response: &Response) -> ApproxDuration {
367    // TODO: follow the spec more closely (Date headers, request/response lag, ...)
368    response
369        .headers
370        .get(header::AGE)
371        .and_then(|age_header| age_header.to_str().ok())
372        .and_then(|age_string| age_string.parse::<u64>().ok())
373        .map(ApproxDuration::from_secs)
374        .unwrap_or_default()
375}
376
377/// Determine the expiry date from relevant headers,
378/// or uses a heuristic if none are present.
379fn get_response_expiry(response: &Response) -> ApproxDuration {
380    // Calculating Freshness Lifetime <https://tools.ietf.org/html/rfc7234#section-4.2.1>
381    let age = calculate_response_age(response);
382    let now = SystemTime::now();
383    if let Some(directives) = response.headers.typed_get::<CacheControl>() {
384        if directives.no_cache() {
385            // Requires validation on first use.
386            return ApproxDuration::zero();
387        }
388        if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
389            let max_age: ApproxDuration = max_age.into();
390            return max_age.saturating_sub(age);
391        }
392    }
393    match response.headers.typed_get::<Expires>() {
394        Some(expiry) => {
395            // `duration_since` fails if `now` is later than `expiry_time` in which case,
396            // this whole thing return `Duration::ZERO`.
397            let expiry_time: SystemTime = expiry.into();
398            return expiry_time
399                .duration_since(now)
400                .map(|duration| duration.into())
401                .unwrap_or(ApproxDuration::zero());
402        },
403        // Malformed Expires header, shouldn't be used to construct a valid response.
404        None if response.headers.contains_key(header::EXPIRES) => return ApproxDuration::zero(),
405        _ => {},
406    }
407    // Calculating Heuristic Freshness
408    // <https://tools.ietf.org/html/rfc7234#section-4.2.2>
409    if let Some(ref code) = response.status.try_code() {
410        // <https://tools.ietf.org/html/rfc7234#section-5.5.4>
411        // Since presently we do not generate a Warning header field with a 113 warn-code,
412        // 24 hours minus response age is the max for heuristic calculation.
413        let max_heuristic = ApproxDuration::from_secs(24 * 60 * 60).saturating_sub(age);
414        let heuristic_freshness = if let Some(last_modified) =
415            // If the response has a Last-Modified header field,
416            // caches are encouraged to use a heuristic expiration value
417            // that is no more than some fraction of the interval since that time.
418            response.headers.typed_get::<LastModified>()
419        {
420            // `time_since_last_modified` will be `Duration::ZERO` if `last_modified` is
421            // after `now`.
422            let last_modified: SystemTime = last_modified.into();
423            let time_since_last_modified: ApproxDuration =
424                now.duration_since(last_modified).unwrap_or_default().into();
425
426            // A typical setting of this fraction might be 10%.
427            let raw_heuristic_calc = time_since_last_modified / 10;
428            if raw_heuristic_calc < max_heuristic {
429                raw_heuristic_calc
430            } else {
431                max_heuristic
432            }
433        } else {
434            // Compatible with other browsers.
435            ApproxDuration::zero()
436        };
437        if is_cacheable_by_default(*code) {
438            // Status codes that are cacheable by default can use heuristics to determine freshness.
439            return heuristic_freshness;
440        }
441        // Other status codes can only use heuristic freshness if the public cache directive is present.
442        if let Some(ref directives) = response.headers.typed_get::<CacheControl>() &&
443            directives.public()
444        {
445            return heuristic_freshness;
446        }
447    }
448    // Requires validation upon first use as default.
449    ApproxDuration::zero()
450}
451
452/// The `headers` crate's `CacheControl` does not understand `stale-while-revalidate` directive,
453/// so we need to parse the raw `Cache-Control` header values.
454/// <https://datatracker.ietf.org/doc/html/rfc5861#section-3>
455fn get_stale_while_revalidate(headers: &HeaderMap) -> ApproxDuration {
456    for value in headers.get_all(header::CACHE_CONTROL) {
457        let Ok(value) = value.to_str() else {
458            continue;
459        };
460        for directive in value.split(',') {
461            let directive = directive.trim();
462            let Some((name, argument)) = directive.split_once('=') else {
463                continue;
464            };
465            if !name.trim().eq_ignore_ascii_case("stale-while-revalidate") {
466                continue;
467            }
468            // The argument is a number of seconds, optionally quoted.
469            let argument = argument.trim().trim_matches('"');
470            if let Ok(seconds) = argument.parse::<u64>() {
471                return ApproxDuration::from_secs(seconds);
472            }
473        }
474    }
475    ApproxDuration::zero()
476}
477
478/// Determine whether the request itself demands revalidation.
479/// <https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1>
480fn request_demands_revalidation(request: &Request) -> bool {
481    if matches!(
482        request.cache_mode,
483        CacheMode::NoCache | CacheMode::Reload | CacheMode::NoStore
484    ) {
485        return true;
486    }
487    if let Some(directive) = request.headers.typed_get::<CacheControl>() {
488        // The request's `no-store` directive is deliberately *not* treated as
489        // demanding revalidation: https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.5
490        // > "does not apply to the already stored response".
491        if directive.no_cache() {
492            return true;
493        }
494
495        if directive.max_age() == Some(Duration::ZERO) {
496            return true;
497        }
498    }
499    false
500}
501
502/// Request Cache-Control Directives
503/// <https://tools.ietf.org/html/rfc7234#section-5.2.1>
504fn get_expiry_adjustment_from_request_headers(
505    request: &Request,
506    expires: ApproxDuration,
507) -> ApproxDuration {
508    let Some(directive) = request.headers.typed_get::<CacheControl>() else {
509        return expires;
510    };
511
512    if let Some(max_age) = directive.max_stale() {
513        let max_age: ApproxDuration = max_age.into();
514        return expires + max_age;
515    };
516
517    let max_age: Option<ApproxDuration> = directive.max_age().map(|max_age| max_age.into());
518    match max_age {
519        Some(max_age) if expires > max_age => return ApproxDuration::zero(),
520        Some(max_age) => return expires - max_age,
521        None => {},
522    };
523
524    if let Some(min_fresh) = directive.min_fresh() {
525        let min_fresh: ApproxDuration = min_fresh.into();
526        if expires < min_fresh {
527            return ApproxDuration::zero();
528        };
529        return expires - min_fresh;
530    }
531
532    if directive.no_cache() || directive.no_store() {
533        return ApproxDuration::zero();
534    }
535
536    expires
537}
538
539/// Create a CachedResponse from a request and a CachedResource.
540fn create_cached_response(
541    request: &Request,
542    cached_resource: &CachedResource,
543    cached_headers: &HeaderMap,
544    done_chan: &mut DoneChannel,
545) -> Option<CachedResponse> {
546    debug!("creating a cached response for {:?}", request.url());
547    if cached_resource.aborted.load(Ordering::Acquire) {
548        return None;
549    }
550    let resource_timing = ResourceFetchTiming::new(request.timing_type());
551    let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
552    response.headers = cached_headers.clone();
553    response.body = cached_resource.body.clone();
554    if let ResponseBody::Receiving(_) = *cached_resource.body.lock() {
555        debug!("existing body is in progress");
556        let (done_sender, done_receiver) = unbounded();
557        *done_chan = Some((done_sender.clone(), done_receiver));
558        cached_resource.awaiting_body.lock().push(done_sender);
559    }
560    response
561        .location_url
562        .clone_from(&cached_resource.location_url);
563    response.status.clone_from(&cached_resource.status);
564    response.url_list.clone_from(&cached_resource.url_list);
565    response.referrer = request.referrer.to_url().cloned();
566    response.referrer_policy = request.referrer_policy;
567    response.aborted = cached_resource.aborted.clone();
568
569    let expires = cached_resource.expires;
570    let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
571    let Ok(time_since_validated) = SystemTime::now().duration_since(cached_resource.last_validated)
572    else {
573        return None;
574    };
575
576    let time_since_validated: ApproxDuration = time_since_validated.into();
577    // TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1>
578    // TODO: if this cache is to be considered shared, take proxy-revalidate into account
579    // <https://tools.ietf.org/html/rfc7234#section-5.2.2.7>
580    let has_expired = adjusted_expires <= time_since_validated;
581
582    // - fresh: return immediately, no validation.
583    // - stale:
584    //    - within the stale-while-revalidate window: return immediately + revalidate in the background
585    //    - beyond the stale-while-revalidate window: synchronous validation is required.
586    let stale_for = time_since_validated.saturating_sub(adjusted_expires);
587    let within_stale_while_revalidate_window = stale_for <= cached_resource.stale_while_revalidate;
588    let validation_status = if !has_expired {
589        ValidationStatus::Valid
590    } else {
591        ValidationStatus::Stale {
592            revalidate_in_background: within_stale_while_revalidate_window &&
593                !cached_resource.stale_while_revalidate.is_zero() &&
594                !request_demands_revalidation(request),
595        }
596    };
597
598    let cached_response = CachedResponse {
599        response,
600        validation_status,
601        revalidation_guard: cached_resource.revalidating.clone(),
602    };
603    Some(cached_response)
604}
605
606/// Create a new resource, based on the bytes requested, and an existing resource,
607/// with a status-code of 206.
608fn create_resource_with_bytes_from_resource(
609    bytes: &[u8],
610    resource: &CachedResource,
611) -> CachedResource {
612    CachedResource {
613        request_headers: resource.request_headers.clone(),
614        body: Arc::new(ParkingLotMutex::new(ResponseBody::Done(bytes.to_owned()))),
615        aborted: Arc::new(AtomicBool::new(false)),
616        awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
617        metadata: resource.metadata.clone(),
618        location_url: resource.location_url.clone(),
619        status: StatusCode::PARTIAL_CONTENT.into(),
620        url_list: resource.url_list.clone(),
621        expires: resource.expires,
622        stale_while_revalidate: resource.stale_while_revalidate,
623        revalidating: resource.revalidating.clone(),
624        last_validated: resource.last_validated,
625    }
626}
627
628/// Support for range requests <https://tools.ietf.org/html/rfc7233>.
629fn handle_range_request(
630    request: &Request,
631    candidates: &[&CachedResource],
632    range_spec: &Range,
633    done_chan: &mut DoneChannel,
634) -> Option<CachedResponse> {
635    let mut complete_cached_resources = candidates
636        .iter()
637        .filter(|resource| resource.status == StatusCode::OK);
638    let partial_cached_resources = candidates
639        .iter()
640        .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
641    if let Some(complete_resource) = complete_cached_resources.next() {
642        // TODO: take the full range spec into account.
643        // If we have a complete resource, take the request range from the body.
644        // When there isn't a complete resource available, we loop over cached partials,
645        // and see if any individual partial response can fulfill the current request for a bytes range.
646        // TODO: combine partials that in combination could satisfy the requested range?
647        // see <https://tools.ietf.org/html/rfc7233#section-4.3>.
648        // TODO: add support for complete and partial resources,
649        // whose body is in the ResponseBody::Receiving state.
650        let body_len = match *complete_resource.body.lock() {
651            ResponseBody::Done(ref body) => body.len(),
652            _ => 0,
653        };
654        let bound = range_spec
655            .satisfiable_ranges(body_len.try_into().unwrap())
656            .next()
657            .unwrap();
658        match bound {
659            (Bound::Included(beginning), Bound::Included(end)) => {
660                if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
661                    if end == u64::MAX {
662                        // Prevent overflow on the addition below.
663                        return None;
664                    }
665                    let b = beginning as usize;
666                    let e = end as usize + 1;
667                    let requested = body.get(b..e);
668                    if let Some(bytes) = requested {
669                        let new_resource =
670                            create_resource_with_bytes_from_resource(bytes, complete_resource);
671                        let cached_headers = new_resource.metadata.headers.lock();
672                        let cached_response = create_cached_response(
673                            request,
674                            &new_resource,
675                            &cached_headers,
676                            done_chan,
677                        );
678                        if let Some(cached_response) = cached_response {
679                            return Some(cached_response);
680                        }
681                    }
682                }
683            },
684            (Bound::Included(beginning), Bound::Unbounded) => {
685                if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
686                    let b = beginning as usize;
687                    let requested = body.get(b..);
688                    if let Some(bytes) = requested {
689                        let new_resource =
690                            create_resource_with_bytes_from_resource(bytes, complete_resource);
691                        let cached_headers = new_resource.metadata.headers.lock();
692                        let cached_response = create_cached_response(
693                            request,
694                            &new_resource,
695                            &cached_headers,
696                            done_chan,
697                        );
698                        if let Some(cached_response) = cached_response {
699                            return Some(cached_response);
700                        }
701                    }
702                }
703            },
704            _ => return None,
705        }
706    } else {
707        for partial_resource in partial_cached_resources {
708            let headers = partial_resource.metadata.headers.lock();
709            let content_range = headers.typed_get::<ContentRange>();
710
711            let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
712                continue;
713            };
714            match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
715                (Bound::Included(beginning), Bound::Included(end)) => {
716                    let (res_beginning, res_end) = match content_range {
717                        Some(range) => {
718                            if let Some(bytes_range) = range.bytes_range() {
719                                bytes_range
720                            } else {
721                                continue;
722                            }
723                        },
724                        _ => continue,
725                    };
726                    if res_beginning <= beginning && res_end >= end {
727                        let resource_body = &*partial_resource.body.lock();
728                        let requested = match resource_body {
729                            ResponseBody::Done(body) => {
730                                let b = beginning as usize - res_beginning as usize;
731                                let e = end as usize - res_beginning as usize + 1;
732                                body.get(b..e)
733                            },
734                            _ => continue,
735                        };
736                        if let Some(bytes) = requested {
737                            let new_resource =
738                                create_resource_with_bytes_from_resource(bytes, partial_resource);
739                            let cached_response =
740                                create_cached_response(request, &new_resource, &headers, done_chan);
741                            if let Some(cached_response) = cached_response {
742                                return Some(cached_response);
743                            }
744                        }
745                    }
746                },
747
748                (Bound::Included(beginning), Bound::Unbounded) => {
749                    let (res_beginning, res_end, total) = if let Some(range) = content_range {
750                        match (range.bytes_range(), range.bytes_len()) {
751                            (Some(bytes_range), Some(total)) => {
752                                (bytes_range.0, bytes_range.1, total)
753                            },
754                            _ => continue,
755                        }
756                    } else {
757                        continue;
758                    };
759                    if total == 0 {
760                        // Prevent overflow in the below operations from occuring.
761                        continue;
762                    };
763                    if res_beginning <= beginning && res_end == total - 1 {
764                        let resource_body = &*partial_resource.body.lock();
765                        let requested = match resource_body {
766                            ResponseBody::Done(body) => {
767                                let from_byte = beginning as usize - res_beginning as usize;
768                                body.get(from_byte..)
769                            },
770                            _ => continue,
771                        };
772                        if let Some(bytes) = requested {
773                            if bytes.len() as u64 + beginning < total - 1 {
774                                // Requested range goes beyond the available range.
775                                continue;
776                            }
777                            let new_resource =
778                                create_resource_with_bytes_from_resource(bytes, partial_resource);
779                            let cached_response =
780                                create_cached_response(request, &new_resource, &headers, done_chan);
781                            if let Some(cached_response) = cached_response {
782                                return Some(cached_response);
783                            }
784                        }
785                    }
786                },
787
788                _ => continue,
789            }
790        }
791    }
792
793    None
794}
795
796/// Constructing Responses from Caches.
797/// <https://tools.ietf.org/html/rfc7234#section-4>
798pub(crate) fn construct_response(
799    request: &Request,
800    done_chan: &mut DoneChannel,
801    cache_result: &[CachedResource],
802) -> Option<CachedResponse> {
803    if pref!(network_http_cache_disabled) {
804        return None;
805    }
806
807    // TODO: generate warning headers as appropriate <https://tools.ietf.org/html/rfc7234#section-5.5>
808    debug!("trying to construct cache response for {:?}", request.url());
809    if request.method != Method::GET {
810        // Only Get requests are cached, avoid a url based match for others.
811        debug!("non-GET method, not caching");
812        return None;
813    }
814
815    let resources = cache_result
816        .iter()
817        .filter(|r| !r.aborted.load(Ordering::Relaxed));
818    let mut candidates = vec![];
819    for cached_resource in resources {
820        let mut can_be_constructed = true;
821        let cached_headers = cached_resource.metadata.headers.lock();
822        let original_request_headers = cached_resource.request_headers.lock();
823        if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
824            if vary_value.is_any() {
825                debug!("vary value is any, not caching");
826                can_be_constructed = false
827            } else {
828                // For every header name found in the Vary header of the stored response.
829                // Calculating Secondary Keys with Vary <https://tools.ietf.org/html/rfc7234#section-4.1>
830                for vary_val in vary_value.iter_strs() {
831                    match request.headers.get(vary_val) {
832                        Some(header_data) => {
833                            // If the header is present in the request.
834                            if let Some(original_header_data) =
835                                original_request_headers.get(vary_val)
836                            {
837                                // Check that the value of the nominated header field,
838                                // in the original request, matches the value in the current request.
839                                if original_header_data != header_data {
840                                    debug!("headers don't match, not caching");
841                                    can_be_constructed = false;
842                                    break;
843                                }
844                            }
845                        },
846                        None => {
847                            // If a header field is absent from a request,
848                            // it can only match a stored response if those headers,
849                            // were also absent in the original request.
850                            can_be_constructed = original_request_headers.get(vary_val).is_none();
851                            if !can_be_constructed {
852                                debug!("vary header present, not caching");
853                            }
854                        },
855                    }
856                    if !can_be_constructed {
857                        break;
858                    }
859                }
860            }
861        }
862        if can_be_constructed {
863            candidates.push(cached_resource);
864        }
865    }
866    // Support for range requests
867    if let Some(range_spec) = request.headers.typed_get::<Range>() {
868        return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
869    }
870    while let Some(cached_resource) = candidates.pop() {
871        // Not a Range request.
872        // Do not allow 206 responses to be constructed.
873        //
874        // See https://tools.ietf.org/html/rfc7234#section-3.1
875        //
876        // A cache MUST NOT use an incomplete response to answer requests unless the
877        // response has been made complete or the request is partial and
878        // specifies a range that is wholly within the incomplete response.
879        //
880        // TODO: Combining partial content to fulfill a non-Range request
881        // see https://tools.ietf.org/html/rfc7234#section-3.3
882        match cached_resource.status.try_code() {
883            Some(ref code) => {
884                if *code == StatusCode::PARTIAL_CONTENT {
885                    continue;
886                }
887            },
888            None => continue,
889        }
890        // Returning a response that can be constructed
891        // TODO: select the most appropriate one, using a known mechanism from a selecting header field,
892        // or using the Date header to return the most recent one.
893        let cached_headers = cached_resource.metadata.headers.lock();
894        let cached_response =
895            create_cached_response(request, cached_resource, &cached_headers, done_chan);
896        if let Some(cached_response) = cached_response {
897            return Some(cached_response);
898        }
899    }
900    debug!("couldn't find an appropriate response, not caching");
901    // The cache wasn't able to construct anything.
902    None
903}
904
905/// Freshening Stored Responses upon Validation.
906/// <https://tools.ietf.org/html/rfc7234#section-4.3.4>
907pub fn refresh(
908    request: &Request,
909    response: Response,
910    done_chan: &mut DoneChannel,
911    cached_resources: &mut [CachedResource],
912) -> Option<Response> {
913    assert_eq!(response.status, StatusCode::NOT_MODIFIED);
914
915    let cached_resource = cached_resources.iter_mut().next()?;
916
917    let mut constructed_response = if let Some(range_spec) = request.headers.typed_get::<Range>() {
918        handle_range_request(request, &[cached_resource], &range_spec, done_chan)
919            .map(|cached_response| cached_response.response)
920    } else {
921        // done_chan will have been set to Some(..) by http_network_fetch.
922        // If the body is not receiving data, set the done_chan back to None.
923        // Otherwise, create a new dedicated channel to update the consumer.
924        // The response constructed here will replace the 304 one from the network.
925        let in_progress_channel = match &*cached_resource.body.lock() {
926            ResponseBody::Receiving(..) => Some(unbounded()),
927            ResponseBody::Empty | ResponseBody::Done(..) => None,
928        };
929        match in_progress_channel {
930            Some((done_sender, done_receiver)) => {
931                *done_chan = Some((done_sender.clone(), done_receiver));
932                cached_resource.awaiting_body.lock().push(done_sender);
933            },
934            None => *done_chan = None,
935        }
936        // Received a response with 304 status code, in response to a request that matches a cached resource.
937        // 1. update the headers of the cached resource.
938        // 2. return a response, constructed from the cached resource.
939        let resource_timing = ResourceFetchTiming::new(request.timing_type());
940        let mut constructed_response =
941            Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
942
943        constructed_response.body = cached_resource.body.clone();
944
945        constructed_response
946            .status
947            .clone_from(&cached_resource.status);
948        constructed_response.referrer = request.referrer.to_url().cloned();
949        constructed_response.referrer_policy = request.referrer_policy;
950        constructed_response
951            .status
952            .clone_from(&cached_resource.status);
953        constructed_response
954            .url_list
955            .clone_from(&cached_resource.url_list);
956        Some(constructed_response)
957    };
958
959    // Update cached Resource with response and constructed response.
960    if let Some(constructed_response) = constructed_response.as_mut() {
961        // Bracket is to minimize lock duration.
962        {
963            let mut stored_headers = cached_resource.metadata.headers.lock();
964            stored_headers.extend(response.headers);
965            constructed_response.headers = stored_headers.clone();
966        }
967        cached_resource.expires = get_response_expiry(constructed_response);
968        cached_resource.stale_while_revalidate =
969            get_stale_while_revalidate(&constructed_response.headers);
970        cached_resource.last_validated = SystemTime::now();
971    }
972
973    constructed_response
974}
975
976pub(crate) fn invalidate_cached_resources(cached_resources: &mut [CachedResource]) {
977    for cached_resource in cached_resources.iter_mut() {
978        cached_resource.expires = ApproxDuration::zero();
979    }
980}
981
982fn resolve_location_url(
983    request: &Request,
984    response: &Response,
985    header_name: header::HeaderName,
986) -> Option<ServoUrl> {
987    response
988        .headers
989        .get(header_name)
990        .and_then(|value| value.to_str().ok())
991        .and_then(|location| request.current_url().join(location).ok())
992}
993
994impl HttpCache {
995    /// Wake-up consumers of cached resources
996    /// whose response body was still receiving data when the resource was constructed,
997    /// and whose response has now either been completed or cancelled.
998    pub(crate) async fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
999        let entry_key = CacheKey::new(request);
1000
1001        let cached_resources = match self.entries.get(&entry_key) {
1002            None => return,
1003            Some(resources) => resources,
1004        };
1005
1006        let actual_response = response.actual_response();
1007
1008        // Ensure we only wake-up consumers of relevant resources,
1009        // ie we don't want to wake-up 200 awaiting consumers with a 206.
1010        let lock = cached_resources.read().await;
1011        let relevant_cached_resources = lock.iter().filter(|resource| {
1012            if actual_response.is_network_error() {
1013                return *resource.body.lock() == ResponseBody::Empty;
1014            }
1015            resource.status == actual_response.status
1016        });
1017
1018        for cached_resource in relevant_cached_resources {
1019            let mut awaiting_consumers = cached_resource.awaiting_body.lock();
1020            if awaiting_consumers.is_empty() {
1021                continue;
1022            }
1023            let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
1024                // In the case of an aborted fetch,
1025                // wake-up all awaiting consumers.
1026                // Each will then start a new network request.
1027                // TODO: Wake-up only one consumer, and make it the producer on which others wait.
1028                Data::Cancelled
1029            } else {
1030                match *cached_resource.body.lock() {
1031                    ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
1032                    ResponseBody::Receiving(_) => {
1033                        continue;
1034                    },
1035                }
1036            };
1037            for done_sender in awaiting_consumers.drain(..) {
1038                let _ = done_sender.send(to_send.clone());
1039            }
1040        }
1041    }
1042
1043    /// Returns descriptors for cache entries currently stored in this cache.
1044    pub(crate) fn cache_entry_descriptors(&self) -> Vec<CacheEntryDescriptor> {
1045        self.entries
1046            .iter()
1047            .map(|(key, _)| CacheEntryDescriptor::new(key.url.to_string()))
1048            .collect()
1049    }
1050
1051    /// Clear the contents of this cache.
1052    pub(crate) fn clear(&self) {
1053        self.entries.clear();
1054        if let Some(disk_cache) = &self.disk_cache {
1055            disk_cache.clear();
1056        }
1057    }
1058
1059    /// Insert a response for `request` into the cache (used by tests that need direct access).
1060    pub async fn store(&self, request: &Request, response: &Response) {
1061        let guard = self.get_or_guard(CacheKey::new(request)).await;
1062        guard.insert(request, response);
1063    }
1064
1065    /// Try to construct a cached response for `request`.
1066    pub async fn construct_response(
1067        &self,
1068        request: &Request,
1069        done_chan: &mut DoneChannel,
1070    ) -> Option<Response> {
1071        let entry = self.entries.get(&CacheKey::new(request))?;
1072        let cached_resources = entry.read().await;
1073        construct_response(request, done_chan, cached_resources.as_slice())
1074            .map(|cached| cached.response)
1075    }
1076
1077    /// Like [`construct_response`](Self::construct_response), but additionally
1078    /// reports the [`ValidationStatus`] of the constructed response.
1079    #[cfg(feature = "test-util")]
1080    pub async fn construct_response_freshness(
1081        &self,
1082        request: &Request,
1083        done_chan: &mut DoneChannel,
1084    ) -> Option<ValidationStatus> {
1085        let entry = self.entries.get(&CacheKey::new(request))?;
1086        let cached_resources = entry.read().await;
1087        construct_response(request, done_chan, cached_resources.as_slice())
1088            .map(|cached| cached.validation_status)
1089    }
1090
1091    /// Invalidate cache entries referenced by Location/Content-Location headers.
1092    pub(crate) async fn invalidate_related_urls(
1093        &self,
1094        request: &Request,
1095        response: &Response,
1096        skip_key: &CacheKey,
1097    ) {
1098        for header_name in &[header::LOCATION, header::CONTENT_LOCATION] {
1099            if let Some(location_url) = resolve_location_url(request, response, header_name.clone())
1100            {
1101                let location_key = CacheKey::from_url(location_url);
1102                if &location_key != skip_key {
1103                    self.invalidate_entry(&location_key).await;
1104                }
1105            }
1106        }
1107    }
1108
1109    async fn invalidate_entry(&self, key: &CacheKey) {
1110        if let Some(entry) = self.entries.get(key) {
1111            let mut guarded_resources = entry.write().await;
1112            invalidate_cached_resources(guarded_resources.as_mut_slice());
1113        }
1114    }
1115
1116    /// 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.
1117    /// If the guard is alive, all other accesses to this function will block.
1118    #[servo_tracing::instrument(skip(self))]
1119    pub async fn get_or_guard(&self, entry_key: CacheKey) -> CachedResourcesOrGuard<'_> {
1120        let guard_or_value = self.entries.get_value_or_guard_async(&entry_key).await;
1121        if let Ok(value) = guard_or_value {
1122            return CachedResourcesOrGuard::Value(value.write_owned().await);
1123        }
1124        let guard = guard_or_value.unwrap_err();
1125
1126        if let Some(disk_cache) = &self.disk_cache {
1127            if let Some(response) = disk_cache.get(entry_key).await {
1128                if guard.insert(response.clone()).is_err() {
1129                    error!(
1130                        "Cache guard is invalid. This should not happen. Cache will be in inconsistent state."
1131                    );
1132                }
1133                let response = response.write_owned().await;
1134                CachedResourcesOrGuard::Value(response)
1135            } else {
1136                CachedResourcesOrGuard::Guard(guard)
1137            }
1138        } else {
1139            CachedResourcesOrGuard::Guard(guard)
1140        }
1141    }
1142}
1143
1144/// Returns an writeable entry into the cache or a guard for insertint an entry
1145/// The guard will block other queries to the cache entry in both cases.
1146pub enum CachedResourcesOrGuard<'a> {
1147    /// The value of the resource in the cache.
1148    Value(OwnedRwLockWriteGuard<Vec<CachedResource>>),
1149    /// A guard that blocks requests to the cache entry this guard is for.
1150    Guard(QuickCachePlaceholderGuard<'a>),
1151}
1152
1153impl<'a> CachedResourcesOrGuard<'a> {
1154    /// Insert into the cache according to http spec
1155    pub fn insert(self, request: &Request, response: &Response) {
1156        if pref!(network_http_cache_disabled) {
1157            return;
1158        }
1159
1160        if request.method != Method::GET {
1161            // Only Get requests are cached.
1162            return;
1163        }
1164        if request.headers.contains_key(header::AUTHORIZATION) {
1165            // https://tools.ietf.org/html/rfc7234#section-3.1
1166            // A shared cache MUST NOT use a cached response
1167            // to a request with an Authorization header field
1168            //
1169            // TODO: unless a cache directive that allows such
1170            // responses to be stored is present in the response.
1171            return;
1172        };
1173        let metadata = match response.metadata() {
1174            Ok(FetchMetadata::Filtered {
1175                filtered: _,
1176                unsafe_: metadata,
1177            }) |
1178            Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
1179            _ => return,
1180        };
1181        if !response_is_cacheable(&metadata) {
1182            return;
1183        }
1184        let expiry = get_response_expiry(response);
1185        let stale_while_revalidate = get_stale_while_revalidate(&response.headers);
1186        let cacheable_metadata = CachedMetadata {
1187            headers: Arc::new(ParkingLotMutex::new(response.headers.clone().into())),
1188            final_url: metadata.final_url,
1189            content_type: metadata.content_type.map(|v| v.0.to_string()),
1190            charset: metadata.charset,
1191            status: metadata.status,
1192        };
1193        let entry_resource = CachedResource {
1194            request_headers: Arc::new(ParkingLotMutex::new(request.headers.clone().into())),
1195            body: response.body.clone(),
1196            aborted: response.aborted.clone(),
1197            awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
1198            metadata: cacheable_metadata,
1199            location_url: response.location_url.clone(),
1200            status: response.status.clone(),
1201            url_list: response.url_list.clone(),
1202            expires: expiry,
1203            stale_while_revalidate,
1204            revalidating: StdArc::new(AtomicBool::new(false)),
1205            last_validated: SystemTime::now(),
1206        };
1207
1208        match self {
1209            CachedResourcesOrGuard::Value(mut owned_rw_lock_write_guard) => {
1210                owned_rw_lock_write_guard.push(entry_resource);
1211            },
1212            CachedResourcesOrGuard::Guard(placeholder_guard) => {
1213                if placeholder_guard
1214                    .insert(std::sync::Arc::new(TokioRwLock::new(vec![entry_resource])))
1215                    .is_err()
1216                {
1217                    error!("Could not insert into cache");
1218                }
1219            },
1220        }
1221    }
1222
1223    /// If the guard is a value, return it as a mut reference. If the guard is a guard, return None
1224    pub fn try_as_mut(&mut self) -> Option<&mut Vec<CachedResource>> {
1225        match self {
1226            CachedResourcesOrGuard::Value(owned_rw_lock_write_guard) => {
1227                Some(owned_rw_lock_write_guard.as_mut())
1228            },
1229            CachedResourcesOrGuard::Guard(_) => None,
1230        }
1231    }
1232}