1#![deny(missing_docs)]
6
7use std::collections::HashMap;
11use std::ops::Bound;
12use std::sync::Mutex;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::time::{Duration, Instant, SystemTime};
15
16use headers::{
17 CacheControl, ContentRange, Expires, HeaderMapExt, LastModified, Pragma, Range, Vary,
18};
19use http::header::HeaderValue;
20use http::{HeaderMap, Method, StatusCode, header};
21use log::debug;
22use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};
23use malloc_size_of_derive::MallocSizeOf;
24use net_traits::http_status::HttpStatus;
25use net_traits::request::Request;
26use net_traits::response::{HttpsState, Response, ResponseBody};
27use net_traits::{FetchMetadata, Metadata, ResourceFetchTiming};
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().unwrap() {
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
294 .awaiting_body
295 .lock()
296 .unwrap()
297 .push(done_sender);
298 }
299 response
300 .location_url
301 .clone_from(&cached_resource.location_url);
302 response.status.clone_from(&cached_resource.status);
303 response.url_list.clone_from(&cached_resource.url_list);
304 response.https_state = cached_resource.https_state;
305 response.referrer = request.referrer.to_url().cloned();
306 response.referrer_policy = request.referrer_policy;
307 response.aborted = cached_resource.aborted.clone();
308
309 let expires = cached_resource.expires;
310 let adjusted_expires = get_expiry_adjustment_from_request_headers(request, expires);
311 let time_since_validated = Instant::now() - cached_resource.last_validated;
312
313 let has_expired = adjusted_expires <= time_since_validated;
317 let cached_response = CachedResponse {
318 response,
319 needs_validation: has_expired,
320 };
321 Some(cached_response)
322}
323
324fn create_resource_with_bytes_from_resource(
327 bytes: &[u8],
328 resource: &CachedResource,
329) -> CachedResource {
330 CachedResource {
331 request_headers: resource.request_headers.clone(),
332 body: Arc::new(Mutex::new(ResponseBody::Done(bytes.to_owned()))),
333 aborted: Arc::new(AtomicBool::new(false)),
334 awaiting_body: Arc::new(Mutex::new(vec![])),
335 metadata: resource.metadata.clone(),
336 location_url: resource.location_url.clone(),
337 https_state: resource.https_state,
338 status: StatusCode::PARTIAL_CONTENT.into(),
339 url_list: resource.url_list.clone(),
340 expires: resource.expires,
341 last_validated: resource.last_validated,
342 }
343}
344
345fn handle_range_request(
347 request: &Request,
348 candidates: &[&CachedResource],
349 range_spec: &Range,
350 done_chan: &mut DoneChannel,
351) -> Option<CachedResponse> {
352 let mut complete_cached_resources = candidates
353 .iter()
354 .filter(|resource| resource.status == StatusCode::OK);
355 let partial_cached_resources = candidates
356 .iter()
357 .filter(|resource| resource.status == StatusCode::PARTIAL_CONTENT);
358 if let Some(complete_resource) = complete_cached_resources.next() {
359 let body_len = match *complete_resource.body.lock().unwrap() {
368 ResponseBody::Done(ref body) => body.len(),
369 _ => 0,
370 };
371 let bound = range_spec
372 .satisfiable_ranges(body_len.try_into().unwrap())
373 .next()
374 .unwrap();
375 match bound {
376 (Bound::Included(beginning), Bound::Included(end)) => {
377 if let ResponseBody::Done(ref body) = *complete_resource.body.lock().unwrap() {
378 if end == u64::MAX {
379 return None;
381 }
382 let b = beginning as usize;
383 let e = end as usize + 1;
384 let requested = body.get(b..e);
385 if let Some(bytes) = requested {
386 let new_resource =
387 create_resource_with_bytes_from_resource(bytes, complete_resource);
388 let cached_headers = new_resource.metadata.headers.lock().unwrap();
389 let cached_response = create_cached_response(
390 request,
391 &new_resource,
392 &cached_headers,
393 done_chan,
394 );
395 if let Some(cached_response) = cached_response {
396 return Some(cached_response);
397 }
398 }
399 }
400 },
401 (Bound::Included(beginning), Bound::Unbounded) => {
402 if let ResponseBody::Done(ref body) = *complete_resource.body.lock().unwrap() {
403 let b = beginning as usize;
404 let requested = body.get(b..);
405 if let Some(bytes) = requested {
406 let new_resource =
407 create_resource_with_bytes_from_resource(bytes, complete_resource);
408 let cached_headers = new_resource.metadata.headers.lock().unwrap();
409 let cached_response = create_cached_response(
410 request,
411 &new_resource,
412 &cached_headers,
413 done_chan,
414 );
415 if let Some(cached_response) = cached_response {
416 return Some(cached_response);
417 }
418 }
419 }
420 },
421 _ => return None,
422 }
423 } else {
424 for partial_resource in partial_cached_resources {
425 let headers = partial_resource.metadata.headers.lock().unwrap();
426 let content_range = headers.typed_get::<ContentRange>();
427
428 let Some(body_len) = content_range.as_ref().and_then(|range| range.bytes_len()) else {
429 continue;
430 };
431 match range_spec.satisfiable_ranges(body_len - 1).next().unwrap() {
432 (Bound::Included(beginning), Bound::Included(end)) => {
433 let (res_beginning, res_end) = match content_range {
434 Some(range) => {
435 if let Some(bytes_range) = range.bytes_range() {
436 bytes_range
437 } else {
438 continue;
439 }
440 },
441 _ => continue,
442 };
443 if res_beginning <= beginning && res_end >= end {
444 let resource_body = &*partial_resource.body.lock().unwrap();
445 let requested = match resource_body {
446 ResponseBody::Done(body) => {
447 let b = beginning as usize - res_beginning as usize;
448 let e = end as usize - res_beginning as usize + 1;
449 body.get(b..e)
450 },
451 _ => continue,
452 };
453 if let Some(bytes) = requested {
454 let new_resource =
455 create_resource_with_bytes_from_resource(bytes, partial_resource);
456 let cached_response =
457 create_cached_response(request, &new_resource, &headers, done_chan);
458 if let Some(cached_response) = cached_response {
459 return Some(cached_response);
460 }
461 }
462 }
463 },
464
465 (Bound::Included(beginning), Bound::Unbounded) => {
466 let (res_beginning, res_end, total) = if let Some(range) = content_range {
467 match (range.bytes_range(), range.bytes_len()) {
468 (Some(bytes_range), Some(total)) => {
469 (bytes_range.0, bytes_range.1, total)
470 },
471 _ => continue,
472 }
473 } else {
474 continue;
475 };
476 if total == 0 {
477 continue;
479 };
480 if res_beginning <= beginning && res_end == total - 1 {
481 let resource_body = &*partial_resource.body.lock().unwrap();
482 let requested = match resource_body {
483 ResponseBody::Done(body) => {
484 let from_byte = beginning as usize - res_beginning as usize;
485 body.get(from_byte..)
486 },
487 _ => continue,
488 };
489 if let Some(bytes) = requested {
490 let new_resource =
491 create_resource_with_bytes_from_resource(bytes, partial_resource);
492 let cached_response =
493 create_cached_response(request, &new_resource, &headers, done_chan);
494 if let Some(cached_response) = cached_response {
495 return Some(cached_response);
496 }
497 }
498 }
499 },
500
501 _ => continue,
502 }
503 }
504 }
505
506 None
507}
508
509impl HttpCache {
510 pub fn construct_response(
513 &self,
514 request: &Request,
515 done_chan: &mut DoneChannel,
516 ) -> Option<CachedResponse> {
517 if pref!(network_http_cache_disabled) {
518 return None;
519 }
520
521 debug!("trying to construct cache response for {:?}", request.url());
523 if request.method != Method::GET {
524 debug!("non-GET method, not caching");
526 return None;
527 }
528 let entry_key = CacheKey::new(request);
529 let resources = self
530 .entries
531 .get(&entry_key)?
532 .iter()
533 .filter(|r| !r.aborted.load(Ordering::Relaxed));
534 let mut candidates = vec![];
535 for cached_resource in resources {
536 let mut can_be_constructed = true;
537 let cached_headers = cached_resource.metadata.headers.lock().unwrap();
538 let original_request_headers = cached_resource.request_headers.lock().unwrap();
539 if let Some(vary_value) = cached_headers.typed_get::<Vary>() {
540 if vary_value.is_any() {
541 debug!("vary value is any, not caching");
542 can_be_constructed = false
543 } else {
544 for vary_val in vary_value.iter_strs() {
547 match request.headers.get(vary_val) {
548 Some(header_data) => {
549 if let Some(original_header_data) =
551 original_request_headers.get(vary_val)
552 {
553 if original_header_data != header_data {
556 debug!("headers don't match, not caching");
557 can_be_constructed = false;
558 break;
559 }
560 }
561 },
562 None => {
563 can_be_constructed =
567 original_request_headers.get(vary_val).is_none();
568 if !can_be_constructed {
569 debug!("vary header present, not caching");
570 }
571 },
572 }
573 if !can_be_constructed {
574 break;
575 }
576 }
577 }
578 }
579 if can_be_constructed {
580 candidates.push(cached_resource);
581 }
582 }
583 if let Some(range_spec) = request.headers.typed_get::<Range>() {
585 return handle_range_request(request, candidates.as_slice(), &range_spec, done_chan);
586 }
587 while let Some(cached_resource) = candidates.pop() {
588 match cached_resource.status.try_code() {
600 Some(ref code) => {
601 if *code == StatusCode::PARTIAL_CONTENT {
602 continue;
603 }
604 },
605 None => continue,
606 }
607 let cached_headers = cached_resource.metadata.headers.lock().unwrap();
611 let cached_response =
612 create_cached_response(request, cached_resource, &cached_headers, done_chan);
613 if let Some(cached_response) = cached_response {
614 return Some(cached_response);
615 }
616 }
617 debug!("couldn't find an appropriate response, not caching");
618 None
620 }
621
622 pub fn update_awaiting_consumers(&self, request: &Request, response: &Response) {
626 let entry_key = CacheKey::new(request);
627
628 let cached_resources = match self.entries.get(&entry_key) {
629 None => return,
630 Some(resources) => resources,
631 };
632
633 let actual_response = response.actual_response();
634
635 let relevant_cached_resources = cached_resources.iter().filter(|resource| {
638 if actual_response.is_network_error() {
639 return *resource.body.lock().unwrap() == ResponseBody::Empty;
640 }
641 resource.status == actual_response.status
642 });
643
644 for cached_resource in relevant_cached_resources {
645 let mut awaiting_consumers = cached_resource.awaiting_body.lock().unwrap();
646 if awaiting_consumers.is_empty() {
647 continue;
648 }
649 let to_send = if cached_resource.aborted.load(Ordering::Acquire) {
650 Data::Cancelled
655 } else {
656 match *cached_resource.body.lock().unwrap() {
657 ResponseBody::Done(_) | ResponseBody::Empty => Data::Done,
658 ResponseBody::Receiving(_) => {
659 continue;
660 },
661 }
662 };
663 for done_sender in awaiting_consumers.drain(..) {
664 let _ = done_sender.send(to_send.clone());
665 }
666 }
667 }
668
669 pub fn refresh(
672 &mut self,
673 request: &Request,
674 response: Response,
675 done_chan: &mut DoneChannel,
676 ) -> Option<Response> {
677 assert_eq!(response.status, StatusCode::NOT_MODIFIED);
678 let entry_key = CacheKey::new(request);
679 if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
680 if let Some(cached_resource) = cached_resources.iter_mut().next() {
681 let in_progress_channel = match *cached_resource.body.lock().unwrap() {
686 ResponseBody::Receiving(..) => Some(unbounded()),
687 ResponseBody::Empty | ResponseBody::Done(..) => None,
688 };
689 match in_progress_channel {
690 Some((done_sender, done_receiver)) => {
691 *done_chan = Some((done_sender.clone(), done_receiver));
692 cached_resource
693 .awaiting_body
694 .lock()
695 .unwrap()
696 .push(done_sender);
697 },
698 None => *done_chan = None,
699 }
700 let resource_timing = ResourceFetchTiming::new(request.timing_type());
704 let mut constructed_response =
705 Response::new(cached_resource.metadata.final_url.clone(), resource_timing);
706 constructed_response.body = cached_resource.body.clone();
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 cached_resource.expires = get_response_expiry(&constructed_response);
720 let mut stored_headers = cached_resource.metadata.headers.lock().unwrap();
721 stored_headers.extend(response.headers);
722 constructed_response.headers = stored_headers.clone();
723 return Some(constructed_response);
724 }
725 }
726 None
727 }
728
729 fn invalidate_for_url(&mut self, url: &ServoUrl) {
730 let entry_key = CacheKey::from_servo_url(url);
731 if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
732 for cached_resource in cached_resources.iter_mut() {
733 cached_resource.expires = Duration::ZERO;
734 }
735 }
736 }
737
738 pub fn invalidate(&mut self, request: &Request, response: &Response) {
741 if let Some(Ok(location)) = response
743 .headers
744 .get(header::LOCATION)
745 .map(HeaderValue::to_str)
746 {
747 if let Ok(url) = request.current_url().join(location) {
748 self.invalidate_for_url(&url);
749 }
750 }
751 if let Some(Ok(content_location)) = response
752 .headers
753 .get(header::CONTENT_LOCATION)
754 .map(HeaderValue::to_str)
755 {
756 if let Ok(url) = request.current_url().join(content_location) {
757 self.invalidate_for_url(&url);
758 }
759 }
760 self.invalidate_for_url(&request.url());
761 }
762
763 pub fn store(&mut self, request: &Request, response: &Response) {
766 if pref!(network_http_cache_disabled) {
767 return;
768 }
769 if request.method != Method::GET {
770 return;
772 }
773 if request.headers.contains_key(header::AUTHORIZATION) {
774 return;
781 };
782 let entry_key = CacheKey::new(request);
783 let metadata = match response.metadata() {
784 Ok(FetchMetadata::Filtered {
785 filtered: _,
786 unsafe_: metadata,
787 }) |
788 Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
789 _ => return,
790 };
791 if !response_is_cacheable(&metadata) {
792 return;
793 }
794 let expiry = get_response_expiry(response);
795 let cacheable_metadata = CachedMetadata {
796 headers: Arc::new(Mutex::new(response.headers.clone())),
797 final_url: metadata.final_url,
798 content_type: metadata.content_type.map(|v| v.0.to_string()),
799 charset: metadata.charset,
800 status: metadata.status,
801 };
802 let entry_resource = CachedResource {
803 request_headers: Arc::new(Mutex::new(request.headers.clone())),
804 body: response.body.clone(),
805 aborted: response.aborted.clone(),
806 awaiting_body: Arc::new(Mutex::new(vec![])),
807 metadata: cacheable_metadata,
808 location_url: response.location_url.clone(),
809 https_state: response.https_state,
810 status: response.status.clone(),
811 url_list: response.url_list.clone(),
812 expires: expiry,
813 last_validated: Instant::now(),
814 };
815 let entry = self.entries.entry(entry_key).or_default();
816 entry.push(entry_resource);
817 }
821
822 pub fn clear(&mut self) {
824 self.entries.clear();
825 }
826}