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