1#![deny(missing_docs)]
6
7use std::collections::HashMap;
11use std::ops::Bound;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::time::{Duration, Instant, SystemTime};
14
15use headers::{
16 CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary,
17};
18use http::header::HeaderValue;
19use http::{HeaderMap, Method, StatusCode, header};
20use log::debug;
21use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
22use malloc_size_of_derive::MallocSizeOf;
23use net_traits::http_status::HttpStatus;
24use net_traits::request::Request;
25use net_traits::response::{HttpsState, Response, ResponseBody};
26use net_traits::{FetchMetadata, Metadata, ResourceFetchTiming};
27use parking_lot::Mutex;
28use servo_arc::Arc;
29use servo_config::pref;
30use servo_url::ServoUrl;
31use tokio::sync::mpsc::{UnboundedSender as TokioSender, unbounded_channel as unbounded};
32
33use crate::fetch::methods::{Data, DoneChannel};
34
35#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq)]
37pub struct CacheKey {
38 url: ServoUrl,
39}
40
41impl CacheKey {
42 pub(crate) fn new(request: &Request) -> CacheKey {
44 CacheKey {
45 url: request.current_url(),
46 }
47 }
48
49 fn from_servo_url(servo_url: &ServoUrl) -> CacheKey {
50 CacheKey {
51 url: servo_url.clone(),
52 }
53 }
54}
55
56#[derive(Clone)]
58struct CachedResource {
59 request_headers: Arc<Mutex<HeaderMap>>,
60 body: Arc<Mutex<ResponseBody>>,
61 aborted: Arc<AtomicBool>,
62 awaiting_body: Arc<Mutex<Vec<TokioSender<Data>>>>,
63 metadata: CachedMetadata,
64 location_url: Option<Result<ServoUrl, String>>,
65 https_state: HttpsState,
66 status: HttpStatus,
67 url_list: Vec<ServoUrl>,
68 expires: Duration,
69 last_validated: Instant,
70}
71
72impl MallocSizeOf for CachedResource {
73 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
74 self.body.unconditional_size_of(ops) +
76 self.aborted.unconditional_size_of(ops) +
77 self.awaiting_body.unconditional_size_of(ops) +
78 self.metadata.size_of(ops) +
79 self.location_url.size_of(ops) +
80 self.https_state.size_of(ops) +
81 self.status.size_of(ops) +
82 self.url_list.size_of(ops) +
83 self.expires.size_of(ops) +
84 self.last_validated.size_of(ops)
85 }
86}
87
88#[derive(Clone, MallocSizeOf)]
90struct CachedMetadata {
91 #[ignore_malloc_size_of = "Defined in `http` and has private members"]
93 pub headers: Arc<Mutex<HeaderMap>>,
94 pub final_url: ServoUrl,
96 pub content_type: Option<String>,
98 pub charset: Option<String>,
100 pub status: HttpStatus,
102}
103pub struct CachedResponse {
105 pub response: Response,
107 pub needs_validation: bool,
109}
110
111#[derive(Default, MallocSizeOf)]
113pub struct HttpCache {
114 entries: HashMap<CacheKey, Vec<CachedResource>>,
116}
117
118fn is_cacheable_by_default(status_code: StatusCode) -> bool {
120 matches!(
121 status_code.as_u16(),
122 200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501
123 )
124}
125
126fn response_is_cacheable(metadata: &Metadata) -> bool {
129 let mut is_cacheable = false;
133 let headers = metadata.headers.as_ref().unwrap();
134 if headers.contains_key(header::EXPIRES) ||
135 headers.contains_key(header::LAST_MODIFIED) ||
136 headers.contains_key(header::ETAG)
137 {
138 is_cacheable = true;
139 }
140 if let Some(ref directive) = headers.typed_get::<CacheControl>() {
141 if directive.no_store() {
142 return false;
143 }
144 if directive.public() ||
145 directive.s_max_age().is_some() ||
146 directive.max_age().is_some() ||
147 directive.no_cache()
148 {
149 return true;
151 }
152 }
153 if let Some(pragma) = headers.typed_get::<Pragma>() {
154 if pragma.is_no_cache() {
155 return false;
156 }
157 }
158 is_cacheable
159}
160
161fn calculate_response_age(response: &Response) -> Duration {
164 response
166 .headers
167 .get(header::AGE)
168 .and_then(|age_header| age_header.to_str().ok())
169 .and_then(|age_string| age_string.parse::<u64>().ok())
170 .map(Duration::from_secs)
171 .unwrap_or_default()
172}
173
174fn get_response_expiry(response: &Response) -> Duration {
177 let age = calculate_response_age(response);
179 let now = SystemTime::now();
180 if let Some(directives) = response.headers.typed_get::<CacheControl>() {
181 if directives.no_cache() {
182 return Duration::ZERO;
184 }
185 if let Some(max_age) = directives.max_age().or(directives.s_max_age()) {
186 return max_age.saturating_sub(age);
187 }
188 }
189 match response.headers.typed_get::<Expires>() {
190 Some(expiry) => {
191 let expiry_time: SystemTime = expiry.into();
194 return expiry_time.duration_since(now).unwrap_or(Duration::ZERO);
195 },
196 None if response.headers.contains_key(header::EXPIRES) => return Duration::ZERO,
198 _ => {},
199 }
200 if let Some(ref code) = response.status.try_code() {
203 let max_heuristic = Duration::from_secs(24 * 60 * 60).saturating_sub(age);
207 let heuristic_freshness = if let Some(last_modified) =
208 response.headers.typed_get::<LastModified>()
212 {
213 let last_modified: SystemTime = last_modified.into();
216 let time_since_last_modified = now.duration_since(last_modified).unwrap_or_default();
217
218 let raw_heuristic_calc = time_since_last_modified / 10;
220 if raw_heuristic_calc < max_heuristic {
221 raw_heuristic_calc
222 } else {
223 max_heuristic
224 }
225 } else {
226 max_heuristic
227 };
228 if is_cacheable_by_default(*code) {
229 return heuristic_freshness;
231 }
232 if let Some(ref directives) = response.headers.typed_get::<CacheControl>() {
234 if directives.public() {
235 return heuristic_freshness;
236 }
237 }
238 }
239 Duration::ZERO
241}
242
243fn get_expiry_adjustment_from_request_headers(request: &Request, expires: Duration) -> Duration {
246 let Some(directive) = request.headers.typed_get::<CacheControl>() else {
247 return expires;
248 };
249
250 if let Some(max_age) = directive.max_stale() {
251 return expires + max_age;
252 }
253
254 match directive.max_age() {
255 Some(max_age) if expires > max_age => return Duration::ZERO,
256 Some(max_age) => return expires - max_age,
257 None => {},
258 };
259
260 if let Some(min_fresh) = directive.min_fresh() {
261 if expires < min_fresh {
262 return Duration::ZERO;
263 }
264 return expires - min_fresh;
265 }
266
267 if directive.no_cache() || directive.no_store() {
268 return Duration::ZERO;
269 }
270
271 expires
272}
273
274fn create_cached_response(
276 request: &Request,
277 cached_resource: &CachedResource,
278 cached_headers: &HeaderMap,
279 done_chan: &mut DoneChannel,
280) -> Option<CachedResponse> {
281 debug!("creating a cached response for {:?}", request.url());
282 if cached_resource.aborted.load(Ordering::Acquire) {
283 return None;
284 }
285 let resource_timing = ResourceFetchTiming::new(request.timing_type());
286 let mut response = Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
287 response.headers = cached_headers.clone();
288 response.body = cached_resource.body.clone();
289 if let ResponseBody::Receiving(_) = *cached_resource.body.lock() {
290 debug!("existing body is in progress");
291 let (done_sender, done_receiver) = unbounded();
292 *done_chan = Some((done_sender.clone(), done_receiver));
293 cached_resource.awaiting_body.lock().push(done_sender);
294 }
295 response
296 .location_url
297 .clone_from(&cached_resource.location_url);
298 response.status.clone_from(&cached_resource.status);
299 response.url_list.clone_from(&cached_resource.url_list);
300 response.https_state = cached_resource.https_state;
301 response.referrer = request.referrer.to_url().cloned();
302 response.referrer_policy = request.referrer_policy;
303 response.aborted = cached_resource.aborted.clone();
304
305 let expires = cached_resource.expires;
306 let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
307 let time_since_validated = Instant::now() - cached_resource.last_validated;
308
309 let has_expired = adjusted_expires <= time_since_validated;
313 let cached_response = CachedResponse {
314 response,
315 needs_validation: has_expired,
316 };
317 Some(cached_response)
318}
319
320fn create_resource_with_bytes_from_resource(
323 bytes: &[u8],
324 resource: &CachedResource,
325) -> CachedResource {
326 CachedResource {
327 request_headers: resource.request_headers.clone(),
328 body: Arc::new(Mutex::new(ResponseBody::Done(bytes.to_owned()))),
329 aborted: Arc::new(AtomicBool::new(false)),
330 awaiting_body: Arc::new(Mutex::new(vec![])),
331 metadata: resource.metadata.clone(),
332 location_url: resource.location_url.clone(),
333 https_state: resource.https_state,
334 status: StatusCode::PARTIAL_CONTENT.into(),
335 url_list: resource.url_list.clone(),
336 expires: resource.expires,
337 last_validated: resource.last_validated,
338 }
339}
340
341fn handle_range_request(
343 request: &Request,
344 candidates: &[&CachedResource],
345 range_spec: &Range,
346 done_chan: &mut DoneChannel,
347) -> Option<CachedResponse> {
348 let mut complete_cached_resources = candidates
349 .iter()
350 .filter(|resource| resource.status == StatusCode::OK);
351 let partial_cached_resources = candidates
352 .iter()
353 .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
354 if let Some(complete_resource) = complete_cached_resources.next() {
355 let body_len = match *complete_resource.body.lock() {
364 ResponseBody::Done(ref body) => body.len(),
365 _ => 0,
366 };
367 let bound = range_spec
368 .satisfiable_ranges(body_len.try_into().unwrap())
369 .next()
370 .unwrap();
371 match bound {
372 (Bound::Included(beginning), Bound::Included(end)) => {
373 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
374 if end == u64::MAX {
375 return None;
377 }
378 let b = beginning as usize;
379 let e = end as usize + 1;
380 let requested = body.get(b..e);
381 if let Some(bytes) = requested {
382 let new_resource =
383 create_resource_with_bytes_from_resource(bytes, complete_resource);
384 let cached_headers = new_resource.metadata.headers.lock();
385 let cached_response = create_cached_response(
386 request,
387 &new_resource,
388 &cached_headers,
389 done_chan,
390 );
391 if let Some(cached_response) = cached_response {
392 return Some(cached_response);
393 }
394 }
395 }
396 },
397 (Bound::Included(beginning), Bound::Unbounded) => {
398 if let ResponseBody::Done(ref body) = *complete_resource.body.lock() {
399 let b = beginning as usize;
400 let requested = body.get(b..);
401 if let Some(bytes) = requested {
402 let new_resource =
403 create_resource_with_bytes_from_resource(bytes, complete_resource);
404 let cached_headers = new_resource.metadata.headers.lock();
405 let cached_response = create_cached_response(
406 request,
407 &new_resource,
408 &cached_headers,
409 done_chan,
410 );
411 if let Some(cached_response) = cached_response {
412 return Some(cached_response);
413 }
414 }
415 }
416 },
417 _ => return None,
418 }
419 } else {
420 for partial_resource in partial_cached_resources {
421 let headers = partial_resource.metadata.headers.lock();
422 let content_range = headers.typed_get::<ContentRange>();
423
424 let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
425 continue;
426 };
427 match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
428 (Bound::Included(beginning), Bound::Included(end)) => {
429 let (res_beginning, res_end) = match content_range {
430 Some(range) => {
431 if let Some(bytes_range) = range.bytes_range() {
432 bytes_range
433 } else {
434 continue;
435 }
436 },
437 _ => continue,
438 };
439 if res_beginning <= beginning && res_end >= end {
440 let resource_body = &*partial_resource.body.lock();
441 let requested = match resource_body {
442 ResponseBody::Done(body) => {
443 let b = beginning as usize - res_beginning as usize;
444 let e = end as usize - res_beginning as usize + 1;
445 body.get(b..e)
446 },
447 _ => continue,
448 };
449 if let Some(bytes) = requested {
450 let new_resource =
451 create_resource_with_bytes_from_resource(bytes, partial_resource);
452 let cached_response =
453 create_cached_response(request, &new_resource, &headers, done_chan);
454 if let Some(cached_response) = cached_response {
455 return Some(cached_response);
456 }
457 }
458 }
459 },
460
461 (Bound::Included(beginning), Bound::Unbounded) => {
462 let (res_beginning, res_end, total) = if let Some(range) = content_range {
463 match (range.bytes_range(), range.bytes_len()) {
464 (Some(bytes_range), Some(total)) => {
465 (bytes_range.0, bytes_range.1, total)
466 },
467 _ => continue,
468 }
469 } else {
470 continue;
471 };
472 if total == 0 {
473 continue;
475 };
476 if res_beginning <= beginning && res_end == total - 1 {
477 let resource_body = &*partial_resource.body.lock();
478 let requested = match resource_body {
479 ResponseBody::Done(body) => {
480 let from_byte = beginning as usize - res_beginning as usize;
481 body.get(from_byte..)
482 },
483 _ => continue,
484 };
485 if let Some(bytes) = requested {
486 let new_resource =
487 create_resource_with_bytes_from_resource(bytes, partial_resource);
488 let cached_response =
489 create_cached_response(request, &new_resource, &headers, done_chan);
490 if let Some(cached_response) = cached_response {
491 return Some(cached_response);
492 }
493 }
494 }
495 },
496
497 _ => continue,
498 }
499 }
500 }
501
502 None
503}
504
505impl HttpCache {
506 pub fn construct_response(
509 &self,
510 request: &Request,
511 done_chan: &mut DoneChannel,
512 ) -> Option<CachedResponse> {
513 if pref!(network_http_cache_disabled) {
514 return None;
515 }
516
517 debug!("trying to construct cache response for {:?}", request.url());
519 if request.method != Method::GET {
520 debug!("non-GET method, not caching");
522 return None;
523 }
524 let entry_key = CacheKey::new(request);
525 let resources = self
526 .entries
527 .get(&entry_key)?
528 .iter()
529 .filter(|r| !r.aborted.load(Ordering::Relaxed));
530 let mut candidates = vec![];
531 for cached_resource in resources {
532 let mut can_be_constructed = true;
533 let cached_headers = cached_resource.metadata.headers.lock();
534 let original_request_headers = cached_resource.request_headers.lock();
535 if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
536 if vary_value.is_any() {
537 debug!("vary value is any, not caching");
538 can_be_constructed = false
539 } else {
540 for vary_val in vary_value.iter_strs() {
543 match request.headers.get(vary_val) {
544 Some(header_data) => {
545 if let Some(original_header_data) =
547 original_request_headers.get(vary_val)
548 {
549 if original_header_data != header_data {
552 debug!("headers don't match, not caching");
553 can_be_constructed = false;
554 break;
555 }
556 }
557 },
558 None => {
559 can_be_constructed =
563 original_request_headers.get(vary_val).is_none();
564 if !can_be_constructed {
565 debug!("vary header present, not caching");
566 }
567 },
568 }
569 if !can_be_constructed {
570 break;
571 }
572 }
573 }
574 }
575 if can_be_constructed {
576 candidates.push(cached_resource);
577 }
578 }
579 if let Some(range_spec) = request.headers.typed_get::<Range>() {
581 return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
582 }
583 while let Some(cached_resource) = candidates.pop() {
584 match cached_resource.status.try_code() {
596 Some(ref code) => {
597 if *code == StatusCode::PARTIAL_CONTENT {
598 continue;
599 }
600 },
601 None => continue,
602 }
603 let cached_headers = cached_resource.metadata.headers.lock();
607 let cached_response =
608 create_cached_response(request, cached_resource, &cached_headers, done_chan);
609 if let Some(cached_response) = cached_response {
610 return Some(cached_response);
611 }
612 }
613 debug!("couldn't find an appropriate response, not caching");
614 None
616 }
617
618 pub fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
622 let entry_key = CacheKey::new(request);
623
624 let cached_resources = match self.entries.get(&entry_key) {
625 None => return,
626 Some(resources) => resources,
627 };
628
629 let actual_response = response.actual_response();
630
631 let relevant_cached_resources = cached_resources.iter().filter(|resource| {
634 if actual_response.is_network_error() {
635 return *resource.body.lock() == ResponseBody::Empty;
636 }
637 resource.status == actual_response.status
638 });
639
640 for cached_resource in relevant_cached_resources {
641 let mut awaiting_consumers = cached_resource.awaiting_body.lock();
642 if awaiting_consumers.is_empty() {
643 continue;
644 }
645 let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
646 Data::Cancelled
651 } else {
652 match *cached_resource.body.lock() {
653 ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
654 ResponseBody::Receiving(_) => {
655 continue;
656 },
657 }
658 };
659 for done_sender in awaiting_consumers.drain(..) {
660 let _ = done_sender.send(to_send.clone());
661 }
662 }
663 }
664
665 pub fn refresh(
668 &mut self,
669 request: &Request,
670 response: Response,
671 done_chan: &mut DoneChannel,
672 ) -> Option<Response> {
673 assert_eq!(response.status, StatusCode::NOT_MODIFIED);
674 let entry_key = CacheKey::new(request);
675 if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
676 if let Some(cached_resource) = cached_resources.iter_mut().next() {
677 let mut constructed_response = if let Some(range_spec) =
678 request.headers.typed_get::<Range>()
679 {
680 handle_range_request(request, &[cached_resource], &range_spec, done_chan)
681 .map(|cached_response| cached_response.response)
682 } else {
683 let in_progress_channel = match &*cached_resource.body.lock() {
688 ResponseBody::Receiving(..) => Some(unbounded()),
689 ResponseBody::Empty | ResponseBody::Done(..) => None,
690 };
691 match in_progress_channel {
692 Some((done_sender, done_receiver)) => {
693 *done_chan = Some((done_sender.clone(), done_receiver));
694 cached_resource.awaiting_body.lock().push(done_sender);
695 },
696 None => *done_chan = None,
697 }
698 let resource_timing = ResourceFetchTiming::new(request.timing_type());
702 let mut constructed_response =
703 Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
704
705 constructed_response.body = cached_resource.body.clone();
706
707 constructed_response
708 .status
709 .clone_from(&cached_resource.status);
710 constructed_response.https_state = cached_resource.https_state;
711 constructed_response.referrer = request.referrer.to_url().cloned();
712 constructed_response.referrer_policy = request.referrer_policy;
713 constructed_response
714 .status
715 .clone_from(&cached_resource.status);
716 constructed_response
717 .url_list
718 .clone_from(&cached_resource.url_list);
719 Some(constructed_response)
720 };
721 if let Some(constructed_response) = constructed_response.as_mut() {
723 cached_resource.expires = get_response_expiry(constructed_response);
724 let mut stored_headers = cached_resource.metadata.headers.lock();
725 stored_headers.extend(response.headers);
726 constructed_response.headers = stored_headers.clone();
727 }
728
729 return constructed_response;
730 }
731 }
732 None
733 }
734
735 fn invalidate_for_url(&mut self, url: &ServoUrl) {
736 let entry_key = CacheKey::from_servo_url(url);
737 if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
738 for cached_resource in cached_resources.iter_mut() {
739 cached_resource.expires = Duration::ZERO;
740 }
741 }
742 }
743
744 pub fn invalidate(&mut self, request: &Request, response: &Response) {
747 if let Some(Ok(location)) = response
749 .headers
750 .get(header::LOCATION)
751 .map(HeaderValue::to_str)
752 {
753 if let Ok(url) = request.current_url().join(location) {
754 self.invalidate_for_url(&url);
755 }
756 }
757 if let Some(Ok(content_location)) = response
758 .headers
759 .get(header::CONTENT_LOCATION)
760 .map(HeaderValue::to_str)
761 {
762 if let Ok(url) = request.current_url().join(content_location) {
763 self.invalidate_for_url(&url);
764 }
765 }
766 self.invalidate_for_url(&request.url());
767 }
768
769 pub fn store(&mut self, request: &Request, response: &Response) {
772 if pref!(network_http_cache_disabled) {
773 return;
774 }
775 if request.method != Method::GET {
776 return;
778 }
779 if request.headers.contains_key(header::AUTHORIZATION) {
780 return;
787 };
788 let entry_key = CacheKey::new(request);
789 let metadata = match response.metadata() {
790 Ok(FetchMetadata::Filtered {
791 filtered: _,
792 unsafe_: metadata,
793 }) |
794 Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
795 _ => return,
796 };
797 if !response_is_cacheable(&metadata) {
798 return;
799 }
800 let expiry = get_response_expiry(response);
801 let cacheable_metadata = CachedMetadata {
802 headers: Arc::new(Mutex::new(response.headers.clone())),
803 final_url: metadata.final_url,
804 content_type: metadata.content_type.map(|v| v.0.to_string()),
805 charset: metadata.charset,
806 status: metadata.status,
807 };
808 let entry_resource = CachedResource {
809 request_headers: Arc::new(Mutex::new(request.headers.clone())),
810 body: response.body.clone(),
811 aborted: response.aborted.clone(),
812 awaiting_body: Arc::new(Mutex::new(vec![])),
813 metadata: cacheable_metadata,
814 location_url: response.location_url.clone(),
815 https_state: response.https_state,
816 status: response.status.clone(),
817 url_list: response.url_list.clone(),
818 expires: expiry,
819 last_validated: Instant::now(),
820 };
821 let entry = self.entries.entry(entry_key).or_default();
822 entry.push(entry_resource);
823 }
827
828 pub fn clear(&mut self) {
830 self.entries.clear();
831 }
832}