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    // Cached Resources that are not complete could get evicted which means they cannot fill their body.
314    // We allow unfinished resources to stay in the cache.
315    fn is_pinned(&self, _: &CacheKey, val: &CacheEntry) -> bool {
316        val.blocking_read()
317            .iter()
318            .any(|resource| !resource.is_done())
319    }
320
321    fn on_evict(&self, _state: &mut Self::RequestState, key: CacheKey, value: CacheEntry) {
322        if let Some(disk_cache_data) = &self.disk_cache {
323            let disk_cache_data = disk_cache_data.clone();
324            tokio::spawn(async move { disk_cache_data.store(key, value).await });
325        }
326    }
327}
328
329/// Determine if a response is cacheable by default <https://tools.ietf.org/html/rfc7231#section-6.1>
330fn is_cacheable_by_default(status_code: StatusCode) -> bool {
331    matches!(
332        status_code.as_u16(),
333        200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
334    )
335}
336
337/// Determine if a given response is cacheable.
338/// Based on <https://tools.ietf.org/html/rfc7234#section-3>
339fn response_is_cacheable(metadata: &Metadata) -> bool {
340    // TODO: if we determine that this cache should be considered shared:
341    // 1. check for absence of private response directive <https://tools.ietf.org/html/rfc7234#section-5.2.2.6>
342    // 2. check for absence of the Authorization header field.
343    let mut is_cacheable = false;
344    let headers = metadata.headers.as_ref().unwrap();
345    if headers.contains_key(header::EXPIRES) ||
346        headers.contains_key(header::LAST_MODIFIED) ||
347        headers.contains_key(header::ETAG)
348    {
349        is_cacheable = true;
350    }
351    if let Some(ref directive) = headers.typed_get::<CacheControl>() {
352        if directive.no_store() {
353            return false;
354        }
355        if directive.public() ||
356            directive.s_max_age().is_some() ||
357            directive.max_age().is_some() ||
358            directive.no_cache()
359        {
360            // If cache-control is understood, we can use it and ignore pragma.
361            return true;
362        }
363    }
364    if let Some(pragma) = headers.typed_get::<Pragma>() &&
365        pragma.is_no_cache()
366    {
367        return false;
368    }
369    is_cacheable
370}
371
372/// Calculating Age
373/// <https://tools.ietf.org/html/rfc7234#section-4.2.3>
374fn calculate_response_age(response: &Response) -> ApproxDuration {
375    // TODO: follow the spec more closely (Date headers, request/response lag, ...)
376    response
377        .headers
378        .get(header::AGE)
379        .and_then(|age_header| age_header.to_str().ok())
380        .and_then(|age_string| age_string.parse::<u64>().ok())
381        .map(ApproxDuration::from_secs)
382        .unwrap_or_default()
383}
384
385/// Determine the expiry date from relevant headers,
386/// or uses a heuristic if none are present.
387fn get_response_expiry(response: &Response) -> ApproxDuration {
388    // Calculating Freshness Lifetime <https://tools.ietf.org/html/rfc7234#section-4.2.1>
389    let age = calculate_response_age(response);
390    let now = SystemTime::now();
391    if let Some(directives) = response.headers.typed_get::<CacheControl>() {
392        if directives.no_cache() {
393            // Requires validation on first use.
394            return ApproxDuration::zero();
395        }
396        if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
397            let max_age: ApproxDuration = max_age.into();
398            return max_age.saturating_sub(age);
399        }
400    }
401    match response.headers.typed_get::<Expires>() {
402        Some(expiry) => {
403            // `duration_since` fails if `now` is later than `expiry_time` in which case,
404            // this whole thing return `Duration::ZERO`.
405            let expiry_time: SystemTime = expiry.into();
406            return expiry_time
407                .duration_since(now)
408                .map(|duration| duration.into())
409                .unwrap_or(ApproxDuration::zero());
410        },
411        // Malformed Expires header, shouldn't be used to construct a valid response.
412        None if response.headers.contains_key(header::EXPIRES) => return ApproxDuration::zero(),
413        _ => {},
414    }
415    // Calculating Heuristic Freshness
416    // <https://tools.ietf.org/html/rfc7234#section-4.2.2>
417    if let Some(ref code) = response.status.try_code() {
418        // <https://tools.ietf.org/html/rfc7234#section-5.5.4>
419        // Since presently we do not generate a Warning header field with a 113 warn-code,
420        // 24 hours minus response age is the max for heuristic calculation.
421        let max_heuristic = ApproxDuration::from_secs(24 * 60 * 60).saturating_sub(age);
422        let heuristic_freshness = if let Some(last_modified) =
423            // If the response has a Last-Modified header field,
424            // caches are encouraged to use a heuristic expiration value
425            // that is no more than some fraction of the interval since that time.
426            response.headers.typed_get::<LastModified>()
427        {
428            // `time_since_last_modified` will be `Duration::ZERO` if `last_modified` is
429            // after `now`.
430            let last_modified: SystemTime = last_modified.into();
431            let time_since_last_modified: ApproxDuration =
432                now.duration_since(last_modified).unwrap_or_default().into();
433
434            // A typical setting of this fraction might be 10%.
435            let raw_heuristic_calc = time_since_last_modified / 10;
436            if raw_heuristic_calc < max_heuristic {
437                raw_heuristic_calc
438            } else {
439                max_heuristic
440            }
441        } else {
442            // Compatible with other browsers.
443            ApproxDuration::zero()
444        };
445        if is_cacheable_by_default(*code) {
446            // Status codes that are cacheable by default can use heuristics to determine freshness.
447            return heuristic_freshness;
448        }
449        // Other status codes can only use heuristic freshness if the public cache directive is present.
450        if let Some(ref directives) = response.headers.typed_get::<CacheControl>() &&
451            directives.public()
452        {
453            return heuristic_freshness;
454        }
455    }
456    // Requires validation upon first use as default.
457    ApproxDuration::zero()
458}
459
460/// The `headers` crate's `CacheControl` does not understand `stale-while-revalidate` directive,
461/// so we need to parse the raw `Cache-Control` header values.
462/// <https://datatracker.ietf.org/doc/html/rfc5861#section-3>
463fn get_stale_while_revalidate(headers: &HeaderMap) -> ApproxDuration {
464    for value in headers.get_all(header::CACHE_CONTROL) {
465        let Ok(value) = value.to_str() else {
466            continue;
467        };
468        for directive in value.split(',') {
469            let directive = directive.trim();
470            let Some((name, argument)) = directive.split_once('=') else {
471                continue;
472            };
473            if !name.trim().eq_ignore_ascii_case("stale-while-revalidate") {
474                continue;
475            }
476            // The argument is a number of seconds, optionally quoted.
477            let argument = argument.trim().trim_matches('"');
478            if let Ok(seconds) = argument.parse::<u64>() {
479                return ApproxDuration::from_secs(seconds);
480            }
481        }
482    }
483    ApproxDuration::zero()
484}
485
486/// Determine whether the request itself demands revalidation.
487/// <https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1>
488fn request_demands_revalidation(request: &Request) -> bool {
489    if matches!(
490        request.cache_mode,
491        CacheMode::NoCache | CacheMode::Reload | CacheMode::NoStore
492    ) {
493        return true;
494    }
495    if let Some(directive) = request.headers.typed_get::<CacheControl>() {
496        // The request's `no-store` directive is deliberately *not* treated as
497        // demanding revalidation: https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.1.5
498        // > "does not apply to the already stored response".
499        if directive.no_cache() {
500            return true;
501        }
502
503        if directive.max_age() == Some(Duration::ZERO) {
504            return true;
505        }
506    }
507    false
508}
509
510/// Request Cache-Control Directives
511/// <https://tools.ietf.org/html/rfc7234#section-5.2.1>
512fn get_expiry_adjustment_from_request_headers(
513    request: &Request,
514    expires: ApproxDuration,
515) -> ApproxDuration {
516    let Some(directive) = request.headers.typed_get::<CacheControl>() else {
517        return expires;
518    };
519
520    if let Some(max_age) = directive.max_stale() {
521        let max_age: ApproxDuration = max_age.into();
522        return expires + max_age;
523    };
524
525    let max_age: Option<ApproxDuration> = directive.max_age().map(|max_age| max_age.into());
526    match max_age {
527        Some(max_age) if expires > max_age => return ApproxDuration::zero(),
528        Some(max_age) => return expires - max_age,
529        None => {},
530    };
531
532    if let Some(min_fresh) = directive.min_fresh() {
533        let min_fresh: ApproxDuration = min_fresh.into();
534        if expires < min_fresh {
535            return ApproxDuration::zero();
536        };
537        return expires - min_fresh;
538    }
539
540    if directive.no_cache() || directive.no_store() {
541        return ApproxDuration::zero();
542    }
543
544    expires
545}
546
547/// Create a CachedResponse from a request and a CachedResource.
548fn create_cached_response(
549    request: &Request,
550    cached_resource: &CachedResource,
551    cached_headers: &HeaderMap,
552    done_chan: &mut DoneChannel,
553) -> Option<CachedResponse> {
554    debug!("creating a cached response for {:?}", request.url());
555    if cached_resource.aborted.load(Ordering::Acquire) {
556        return None;
557    }
558    let resource_timing = ResourceFetchTiming::new(request.timing_type());
559    let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
560    response.headers = cached_headers.clone();
561    response.body = cached_resource.body.clone();
562    if let ResponseBody::Receiving(_) = *cached_resource.body.lock() {
563        debug!("existing body is in progress");
564        let (done_sender, done_receiver) = unbounded();
565        *done_chan = Some((done_sender.clone(), done_receiver));
566        cached_resource.awaiting_body.lock().push(done_sender);
567    }
568    response
569        .location_url
570        .clone_from(&cached_resource.location_url);
571    response.status.clone_from(&cached_resource.status);
572    response.url_list.clone_from(&cached_resource.url_list);
573    response.referrer = request.referrer.to_url().cloned();
574    response.referrer_policy = request.referrer_policy;
575    response.aborted = cached_resource.aborted.clone();
576
577    let expires = cached_resource.expires;
578    let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
579    let Ok(time_since_validated) = SystemTime::now().duration_since(cached_resource.last_validated)
580    else {
581        return None;
582    };
583
584    let time_since_validated: ApproxDuration = time_since_validated.into();
585    // TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1>
586    // TODO: if this cache is to be considered shared, take proxy-revalidate into account
587    // <https://tools.ietf.org/html/rfc7234#section-5.2.2.7>
588    let has_expired = adjusted_expires <= time_since_validated;
589
590    // - fresh: return immediately, no validation.
591    // - stale:
592    //    - within the stale-while-revalidate window: return immediately + revalidate in the background
593    //    - beyond the stale-while-revalidate window: synchronous validation is required.
594    let stale_for = time_since_validated.saturating_sub(adjusted_expires);
595    let within_stale_while_revalidate_window = stale_for <= cached_resource.stale_while_revalidate;
596    let validation_status = if !has_expired {
597        ValidationStatus::Valid
598    } else {
599        ValidationStatus::Stale {
600            revalidate_in_background: within_stale_while_revalidate_window &&
601                !cached_resource.stale_while_revalidate.is_zero() &&
602                !request_demands_revalidation(request),
603        }
604    };
605
606    let cached_response = CachedResponse {
607        response,
608        validation_status,
609        revalidation_guard: cached_resource.revalidating.clone(),
610    };
611    Some(cached_response)
612}
613
614/// Create a new resource, based on the bytes requested, and an existing resource,
615/// with a status-code of 206.
616fn create_resource_with_bytes_from_resource(
617    bytes: &[u8],
618    resource: &CachedResource,
619) -> CachedResource {
620    CachedResource {
621        request_headers: resource.request_headers.clone(),
622        body: Arc::new(ParkingLotMutex::new(ResponseBody::Done(bytes.to_owned()))),
623        aborted: Arc::new(AtomicBool::new(false)),
624        awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
625        metadata: resource.metadata.clone(),
626        location_url: resource.location_url.clone(),
627        status: StatusCode::PARTIAL_CONTENT.into(),
628        url_list: resource.url_list.clone(),
629        expires: resource.expires,
630        stale_while_revalidate: resource.stale_while_revalidate,
631        revalidating: resource.revalidating.clone(),
632        last_validated: resource.last_validated,
633    }
634}
635
636/// Support for range requests <https://tools.ietf.org/html/rfc7233>.
637fn handle_range_request(
638    request: &Request,
639    candidates: &[&CachedResource],
640    range_spec: &Range,
641    done_chan: &mut DoneChannel,
642) -> Option<CachedResponse> {
643    let mut complete_cached_resources = candidates
644        .iter()
645        .filter(|resource| resource.status == StatusCode::OK);
646    let partial_cached_resources = candidates
647        .iter()
648        .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
649    if let Some(complete_resource) = complete_cached_resources.next() {
650        // TODO: take the full range spec into account.
651        // If we have a complete resource, take the request range from the body.
652        // When there isn't a complete resource available, we loop over cached partials,
653        // and see if any individual partial response can fulfill the current request for a bytes range.
654        // TODO: combine partials that in combination could satisfy the requested range?
655        // see <https://tools.ietf.org/html/rfc7233#section-4.3>.
656        // TODO: add support for complete and partial resources,
657        // whose body is in the ResponseBody::Receiving state.
658        let body_len = match *complete_resource.body.lock() {
659            ResponseBody::Done(ref body) => body.len(),
660            _ => 0,
661        };
662        let bound = range_spec
663            .satisfiable_ranges(body_len.try_into().unwrap())
664            .next()
665            .unwrap();
666        match bound {
667            (Bound::Included(beginning), Bound::Included(end)) => {
668                if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
669                    if end == u64::MAX {
670                        // Prevent overflow on the addition below.
671                        return None;
672                    }
673                    let b = beginning as usize;
674                    let e = end as usize + 1;
675                    let requested = body.get(b..e);
676                    if let Some(bytes) = requested {
677                        let new_resource =
678                            create_resource_with_bytes_from_resource(bytes, complete_resource);
679                        let cached_headers = new_resource.metadata.headers.lock();
680                        let cached_response = create_cached_response(
681                            request,
682                            &new_resource,
683                            &cached_headers,
684                            done_chan,
685                        );
686                        if let Some(cached_response) = cached_response {
687                            return Some(cached_response);
688                        }
689                    }
690                }
691            },
692            (Bound::Included(beginning), Bound::Unbounded) => {
693                if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
694                    let b = beginning as usize;
695                    let requested = body.get(b..);
696                    if let Some(bytes) = requested {
697                        let new_resource =
698                            create_resource_with_bytes_from_resource(bytes, complete_resource);
699                        let cached_headers = new_resource.metadata.headers.lock();
700                        let cached_response = create_cached_response(
701                            request,
702                            &new_resource,
703                            &cached_headers,
704                            done_chan,
705                        );
706                        if let Some(cached_response) = cached_response {
707                            return Some(cached_response);
708                        }
709                    }
710                }
711            },
712            _ => return None,
713        }
714    } else {
715        for partial_resource in partial_cached_resources {
716            let headers = partial_resource.metadata.headers.lock();
717            let content_range = headers.typed_get::<ContentRange>();
718
719            let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
720                continue;
721            };
722            match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
723                (Bound::Included(beginning), Bound::Included(end)) => {
724                    let (res_beginning, res_end) = match content_range {
725                        Some(range) => {
726                            if let Some(bytes_range) = range.bytes_range() {
727                                bytes_range
728                            } else {
729                                continue;
730                            }
731                        },
732                        _ => continue,
733                    };
734                    if res_beginning <= beginning && res_end >= end {
735                        let resource_body = &*partial_resource.body.lock();
736                        let requested = match resource_body {
737                            ResponseBody::Done(body) => {
738                                let b = beginning as usize - res_beginning as usize;
739                                let e = end as usize - res_beginning as usize + 1;
740                                body.get(b..e)
741                            },
742                            _ => continue,
743                        };
744                        if let Some(bytes) = requested {
745                            let new_resource =
746                                create_resource_with_bytes_from_resource(bytes, partial_resource);
747                            let cached_response =
748                                create_cached_response(request, &new_resource, &headers, done_chan);
749                            if let Some(cached_response) = cached_response {
750                                return Some(cached_response);
751                            }
752                        }
753                    }
754                },
755
756                (Bound::Included(beginning), Bound::Unbounded) => {
757                    let (res_beginning, res_end, total) = if let Some(range) = content_range {
758                        match (range.bytes_range(), range.bytes_len()) {
759                            (Some(bytes_range), Some(total)) => {
760                                (bytes_range.0, bytes_range.1, total)
761                            },
762                            _ => continue,
763                        }
764                    } else {
765                        continue;
766                    };
767                    if total == 0 {
768                        // Prevent overflow in the below operations from occuring.
769                        continue;
770                    };
771                    if res_beginning <= beginning && res_end == total - 1 {
772                        let resource_body = &*partial_resource.body.lock();
773                        let requested = match resource_body {
774                            ResponseBody::Done(body) => {
775                                let from_byte = beginning as usize - res_beginning as usize;
776                                body.get(from_byte..)
777                            },
778                            _ => continue,
779                        };
780                        if let Some(bytes) = requested {
781                            if bytes.len() as u64 + beginning < total - 1 {
782                                // Requested range goes beyond the available range.
783                                continue;
784                            }
785                            let new_resource =
786                                create_resource_with_bytes_from_resource(bytes, partial_resource);
787                            let cached_response =
788                                create_cached_response(request, &new_resource, &headers, done_chan);
789                            if let Some(cached_response) = cached_response {
790                                return Some(cached_response);
791                            }
792                        }
793                    }
794                },
795
796                _ => continue,
797            }
798        }
799    }
800
801    None
802}
803
804/// Constructing Responses from Caches.
805/// <https://tools.ietf.org/html/rfc7234#section-4>
806pub(crate) fn construct_response(
807    request: &Request,
808    done_chan: &mut DoneChannel,
809    cache_result: &[CachedResource],
810) -> Option<CachedResponse> {
811    if pref!(network_http_cache_disabled) {
812        return None;
813    }
814
815    // TODO: generate warning headers as appropriate <https://tools.ietf.org/html/rfc7234#section-5.5>
816    debug!("trying to construct cache response for {:?}", request.url());
817    if request.method != Method::GET {
818        // Only Get requests are cached, avoid a url based match for others.
819        debug!("non-GET method, not caching");
820        return None;
821    }
822
823    let resources = cache_result
824        .iter()
825        .filter(|r| !r.aborted.load(Ordering::Relaxed));
826    let mut candidates = vec![];
827    for cached_resource in resources {
828        let mut can_be_constructed = true;
829        let cached_headers = cached_resource.metadata.headers.lock();
830        let original_request_headers = cached_resource.request_headers.lock();
831        if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
832            if vary_value.is_any() {
833                debug!("vary value is any, not caching");
834                can_be_constructed = false
835            } else {
836                // For every header name found in the Vary header of the stored response.
837                // Calculating Secondary Keys with Vary <https://tools.ietf.org/html/rfc7234#section-4.1>
838                for vary_val in vary_value.iter_strs() {
839                    match request.headers.get(vary_val) {
840                        Some(header_data) => {
841                            // If the header is present in the request.
842                            if let Some(original_header_data) =
843                                original_request_headers.get(vary_val)
844                            {
845                                // Check that the value of the nominated header field,
846                                // in the original request, matches the value in the current request.
847                                if original_header_data != header_data {
848                                    debug!("headers don't match, not caching");
849                                    can_be_constructed = false;
850                                    break;
851                                }
852                            }
853                        },
854                        None => {
855                            // If a header field is absent from a request,
856                            // it can only match a stored response if those headers,
857                            // were also absent in the original request.
858                            can_be_constructed = original_request_headers.get(vary_val).is_none();
859                            if !can_be_constructed {
860                                debug!("vary header present, not caching");
861                            }
862                        },
863                    }
864                    if !can_be_constructed {
865                        break;
866                    }
867                }
868            }
869        }
870        if can_be_constructed {
871            candidates.push(cached_resource);
872        }
873    }
874    // Support for range requests
875    if let Some(range_spec) = request.headers.typed_get::<Range>() {
876        return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
877    }
878    while let Some(cached_resource) = candidates.pop() {
879        // Not a Range request.
880        // Do not allow 206 responses to be constructed.
881        //
882        // See https://tools.ietf.org/html/rfc7234#section-3.1
883        //
884        // A cache MUST NOT use an incomplete response to answer requests unless the
885        // response has been made complete or the request is partial and
886        // specifies a range that is wholly within the incomplete response.
887        //
888        // TODO: Combining partial content to fulfill a non-Range request
889        // see https://tools.ietf.org/html/rfc7234#section-3.3
890        match cached_resource.status.try_code() {
891            Some(ref code) => {
892                if *code == StatusCode::PARTIAL_CONTENT {
893                    continue;
894                }
895            },
896            None => continue,
897        }
898        // Returning a response that can be constructed
899        // TODO: select the most appropriate one, using a known mechanism from a selecting header field,
900        // or using the Date header to return the most recent one.
901        let cached_headers = cached_resource.metadata.headers.lock();
902        let cached_response =
903            create_cached_response(request, cached_resource, &cached_headers, done_chan);
904        if let Some(cached_response) = cached_response {
905            return Some(cached_response);
906        }
907    }
908    debug!("couldn't find an appropriate response, not caching");
909    // The cache wasn't able to construct anything.
910    None
911}
912
913/// Freshening Stored Responses upon Validation.
914/// <https://tools.ietf.org/html/rfc7234#section-4.3.4>
915pub fn refresh(
916    request: &Request,
917    response: Response,
918    done_chan: &mut DoneChannel,
919    cached_resources: &mut [CachedResource],
920) -> Option<Response> {
921    assert_eq!(response.status, StatusCode::NOT_MODIFIED);
922
923    let cached_resource = cached_resources.iter_mut().next()?;
924
925    let mut constructed_response = if let Some(range_spec) = request.headers.typed_get::<Range>() {
926        handle_range_request(request, &[cached_resource], &range_spec, done_chan)
927            .map(|cached_response| cached_response.response)
928    } else {
929        // done_chan will have been set to Some(..) by http_network_fetch.
930        // If the body is not receiving data, set the done_chan back to None.
931        // Otherwise, create a new dedicated channel to update the consumer.
932        // The response constructed here will replace the 304 one from the network.
933        let in_progress_channel = match &*cached_resource.body.lock() {
934            ResponseBody::Receiving(..) => Some(unbounded()),
935            ResponseBody::Empty | ResponseBody::Done(..) => None,
936        };
937        match in_progress_channel {
938            Some((done_sender, done_receiver)) => {
939                *done_chan = Some((done_sender.clone(), done_receiver));
940                cached_resource.awaiting_body.lock().push(done_sender);
941            },
942            None => *done_chan = None,
943        }
944        // Received a response with 304 status code, in response to a request that matches a cached resource.
945        // 1. update the headers of the cached resource.
946        // 2. return a response, constructed from the cached resource.
947        let resource_timing = ResourceFetchTiming::new(request.timing_type());
948        let mut constructed_response =
949            Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
950
951        constructed_response.body = cached_resource.body.clone();
952
953        constructed_response
954            .status
955            .clone_from(&cached_resource.status);
956        constructed_response.referrer = request.referrer.to_url().cloned();
957        constructed_response.referrer_policy = request.referrer_policy;
958        constructed_response
959            .status
960            .clone_from(&cached_resource.status);
961        constructed_response
962            .url_list
963            .clone_from(&cached_resource.url_list);
964        Some(constructed_response)
965    };
966
967    // Update cached Resource with response and constructed response.
968    if let Some(constructed_response) = constructed_response.as_mut() {
969        // Bracket is to minimize lock duration.
970        {
971            let mut stored_headers = cached_resource.metadata.headers.lock();
972            stored_headers.extend(response.headers);
973            constructed_response.headers = stored_headers.clone();
974        }
975        cached_resource.expires = get_response_expiry(constructed_response);
976        cached_resource.stale_while_revalidate =
977            get_stale_while_revalidate(&constructed_response.headers);
978        cached_resource.last_validated = SystemTime::now();
979    }
980
981    constructed_response
982}
983
984pub(crate) fn invalidate_cached_resources(cached_resources: &mut [CachedResource]) {
985    for cached_resource in cached_resources.iter_mut() {
986        cached_resource.expires = ApproxDuration::zero();
987    }
988}
989
990fn resolve_location_url(
991    request: &Request,
992    response: &Response,
993    header_name: header::HeaderName,
994) -> Option<ServoUrl> {
995    response
996        .headers
997        .get(header_name)
998        .and_then(|value| value.to_str().ok())
999        .and_then(|location| request.current_url().join(location).ok())
1000}
1001
1002impl HttpCache {
1003    /// Wake-up consumers of cached resources
1004    /// whose response body was still receiving data when the resource was constructed,
1005    /// and whose response has now either been completed or cancelled.
1006    pub(crate) async fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
1007        let entry_key = CacheKey::new(request);
1008
1009        let cached_resources = match self.entries.get(&entry_key) {
1010            None => return,
1011            Some(resources) => resources,
1012        };
1013
1014        let actual_response = response.actual_response();
1015
1016        // Ensure we only wake-up consumers of relevant resources,
1017        // ie we don't want to wake-up 200 awaiting consumers with a 206.
1018        let lock = cached_resources.read().await;
1019        let relevant_cached_resources = lock.iter().filter(|resource| {
1020            if actual_response.is_network_error() {
1021                return *resource.body.lock() == ResponseBody::Empty;
1022            }
1023            resource.status == actual_response.status
1024        });
1025
1026        for cached_resource in relevant_cached_resources {
1027            let mut awaiting_consumers = cached_resource.awaiting_body.lock();
1028            if awaiting_consumers.is_empty() {
1029                continue;
1030            }
1031            let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
1032                // In the case of an aborted fetch,
1033                // wake-up all awaiting consumers.
1034                // Each will then start a new network request.
1035                // TODO: Wake-up only one consumer, and make it the producer on which others wait.
1036                Data::Cancelled
1037            } else {
1038                match *cached_resource.body.lock() {
1039                    ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
1040                    ResponseBody::Receiving(_) => {
1041                        continue;
1042                    },
1043                }
1044            };
1045            for done_sender in awaiting_consumers.drain(..) {
1046                let _ = done_sender.send(to_send.clone());
1047            }
1048        }
1049    }
1050
1051    /// Returns descriptors for cache entries currently stored in this cache.
1052    pub(crate) fn cache_entry_descriptors(&self) -> Vec<CacheEntryDescriptor> {
1053        self.entries
1054            .iter()
1055            .map(|(key, _)| CacheEntryDescriptor::new(key.url.to_string()))
1056            .collect()
1057    }
1058
1059    /// Clear the contents of this cache.
1060    pub(crate) fn clear(&self) {
1061        self.entries.clear();
1062        if let Some(disk_cache) = &self.disk_cache {
1063            disk_cache.clear();
1064        }
1065    }
1066
1067    /// Insert a response for `request` into the cache (used by tests that need direct access).
1068    pub async fn store(&self, request: &Request, response: &Response) {
1069        let guard = self.get_or_guard(CacheKey::new(request)).await;
1070        guard.insert(request, response);
1071    }
1072
1073    /// Try to construct a cached response for `request`.
1074    pub async fn construct_response(
1075        &self,
1076        request: &Request,
1077        done_chan: &mut DoneChannel,
1078    ) -> Option<Response> {
1079        let entry = self.entries.get(&CacheKey::new(request))?;
1080        let cached_resources = entry.read().await;
1081        construct_response(request, done_chan, cached_resources.as_slice())
1082            .map(|cached| cached.response)
1083    }
1084
1085    /// Like [`construct_response`](Self::construct_response), but additionally
1086    /// reports the [`ValidationStatus`] of the constructed response.
1087    #[cfg(feature = "test-util")]
1088    pub async fn construct_response_freshness(
1089        &self,
1090        request: &Request,
1091        done_chan: &mut DoneChannel,
1092    ) -> Option<ValidationStatus> {
1093        let entry = self.entries.get(&CacheKey::new(request))?;
1094        let cached_resources = entry.read().await;
1095        construct_response(request, done_chan, cached_resources.as_slice())
1096            .map(|cached| cached.validation_status)
1097    }
1098
1099    /// Invalidate cache entries referenced by Location/Content-Location headers.
1100    pub(crate) async fn invalidate_related_urls(
1101        &self,
1102        request: &Request,
1103        response: &Response,
1104        skip_key: &CacheKey,
1105    ) {
1106        for header_name in &[header::LOCATION, header::CONTENT_LOCATION] {
1107            if let Some(location_url) = resolve_location_url(request, response, header_name.clone())
1108            {
1109                let location_key = CacheKey::from_url(location_url);
1110                if &location_key != skip_key {
1111                    self.invalidate_entry(&location_key).await;
1112                }
1113            }
1114        }
1115    }
1116
1117    async fn invalidate_entry(&self, key: &CacheKey) {
1118        if let Some(entry) = self.entries.get(key) {
1119            let mut guarded_resources = entry.write().await;
1120            invalidate_cached_resources(guarded_resources.as_mut_slice());
1121        }
1122    }
1123
1124    /// 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.
1125    /// If the guard is alive, all other accesses to this function will block.
1126    #[servo_tracing::instrument(skip(self))]
1127    pub async fn get_or_guard(&self, entry_key: CacheKey) -> CachedResourcesOrGuard<'_> {
1128        let guard_or_value = self.entries.get_value_or_guard_async(&entry_key).await;
1129        if let Ok(value) = guard_or_value {
1130            return CachedResourcesOrGuard::Value(value.write_owned().await);
1131        }
1132        let guard = guard_or_value.unwrap_err();
1133
1134        if let Some(disk_cache) = &self.disk_cache {
1135            if let Some(response) = disk_cache.get(entry_key).await {
1136                if guard.insert(response.clone()).is_err() {
1137                    error!(
1138                        "Cache guard is invalid. This should not happen. Cache will be in inconsistent state."
1139                    );
1140                }
1141                let response = response.write_owned().await;
1142                CachedResourcesOrGuard::Value(response)
1143            } else {
1144                CachedResourcesOrGuard::Guard(guard)
1145            }
1146        } else {
1147            CachedResourcesOrGuard::Guard(guard)
1148        }
1149    }
1150}
1151
1152/// Returns an writeable entry into the cache or a guard for insertint an entry
1153/// The guard will block other queries to the cache entry in both cases.
1154pub enum CachedResourcesOrGuard<'a> {
1155    /// The value of the resource in the cache.
1156    Value(OwnedRwLockWriteGuard<Vec<CachedResource>>),
1157    /// A guard that blocks requests to the cache entry this guard is for.
1158    Guard(QuickCachePlaceholderGuard<'a>),
1159}
1160
1161impl<'a> CachedResourcesOrGuard<'a> {
1162    /// Insert into the cache according to http spec
1163    pub fn insert(self, request: &Request, response: &Response) {
1164        if pref!(network_http_cache_disabled) {
1165            return;
1166        }
1167
1168        if request.method != Method::GET {
1169            // Only Get requests are cached.
1170            return;
1171        }
1172        if request.headers.contains_key(header::AUTHORIZATION) {
1173            // https://tools.ietf.org/html/rfc7234#section-3.1
1174            // A shared cache MUST NOT use a cached response
1175            // to a request with an Authorization header field
1176            //
1177            // TODO: unless a cache directive that allows such
1178            // responses to be stored is present in the response.
1179            return;
1180        };
1181        let metadata = match response.metadata() {
1182            Ok(FetchMetadata::Filtered {
1183                filtered: _,
1184                unsafe_: metadata,
1185            }) |
1186            Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
1187            _ => return,
1188        };
1189        if !response_is_cacheable(&metadata) {
1190            return;
1191        }
1192        let expiry = get_response_expiry(response);
1193        let stale_while_revalidate = get_stale_while_revalidate(&response.headers);
1194        let cacheable_metadata = CachedMetadata {
1195            headers: Arc::new(ParkingLotMutex::new(response.headers.clone().into())),
1196            final_url: metadata.final_url,
1197            content_type: metadata.content_type.map(|v| v.0.to_string()),
1198            charset: metadata.charset,
1199            status: metadata.status,
1200        };
1201        let entry_resource = CachedResource {
1202            request_headers: Arc::new(ParkingLotMutex::new(request.headers.clone().into())),
1203            body: response.body.clone(),
1204            aborted: response.aborted.clone(),
1205            awaiting_body: Arc::new(ParkingLotMutex::new(vec![])),
1206            metadata: cacheable_metadata,
1207            location_url: response.location_url.clone(),
1208            status: response.status.clone(),
1209            url_list: response.url_list.clone(),
1210            expires: expiry,
1211            stale_while_revalidate,
1212            revalidating: StdArc::new(AtomicBool::new(false)),
1213            last_validated: SystemTime::now(),
1214        };
1215
1216        match self {
1217            CachedResourcesOrGuard::Value(mut owned_rw_lock_write_guard) => {
1218                owned_rw_lock_write_guard.push(entry_resource);
1219            },
1220            CachedResourcesOrGuard::Guard(placeholder_guard) => {
1221                if placeholder_guard
1222                    .insert(std::sync::Arc::new(TokioRwLock::new(vec![entry_resource])))
1223                    .is_err()
1224                {
1225                    error!("Could not insert into cache");
1226                }
1227            },
1228        }
1229    }
1230
1231    /// If the guard is a value, return it as a mut reference. If the guard is a guard, return None
1232    pub fn try_as_mut(&mut self) -> Option<&mut Vec<CachedResource>> {
1233        match self {
1234            CachedResourcesOrGuard::Value(owned_rw_lock_write_guard) => {
1235                Some(owned_rw_lock_write_guard.as_mut())
1236            },
1237            CachedResourcesOrGuard::Guard(_) => None,
1238        }
1239    }
1240}