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