1use sha2::{Digest, Sha256};
4
5use crate::headers::Headers;
6use crate::problem::LinoHttpError;
7
8pub fn compute_etag(body: &str) -> String {
18 let digest = Sha256::digest(body.as_bytes());
19 let mut hex = String::with_capacity(64);
20 for byte in digest {
21 hex.push_str(&format!("{byte:02x}"));
22 }
23 format!("\"{hex}\"")
24}
25
26pub fn parse_etag_list(header_value: Option<&str>) -> Vec<String> {
30 let Some(header_value) = header_value else {
31 return Vec::new();
32 };
33 header_value
34 .split(',')
35 .map(|tag| {
36 tag.trim()
37 .strip_prefix("W/")
38 .unwrap_or(tag.trim())
39 .to_string()
40 })
41 .filter(|tag| !tag.is_empty())
42 .collect()
43}
44
45pub fn etag_matches(header_value: Option<&str>, current_etag: &str) -> bool {
47 parse_etag_list(header_value)
48 .iter()
49 .any(|tag| tag == "*" || tag == current_etag)
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct PreconditionResult {
55 pub not_modified: bool,
57}
58
59pub fn evaluate_preconditions(
65 headers: &Headers,
66 method: &str,
67 current_etag: &str,
68 require_precondition: bool,
69) -> Result<PreconditionResult, LinoHttpError> {
70 let safe = method == "GET" || method == "HEAD";
71 let if_match = headers.get("if-match");
72 let if_none_match = headers.get("if-none-match");
73
74 if !safe {
75 if let Some(if_match) = if_match {
76 if !etag_matches(Some(if_match), current_etag) {
77 return Err(LinoHttpError::new(
78 412,
79 Some("The entity tag in If-Match does not match the current representation"),
80 ));
81 }
82 } else if require_precondition {
83 return Err(LinoHttpError::new(
84 428,
85 Some("This request requires an If-Match precondition"),
86 ));
87 }
88 }
89
90 if let Some(if_none_match) = if_none_match {
91 if etag_matches(Some(if_none_match), current_etag) {
92 if safe {
93 return Ok(PreconditionResult { not_modified: true });
94 }
95 return Err(LinoHttpError::new(
96 412,
97 Some("The entity tag in If-None-Match matches the current representation"),
98 ));
99 }
100 }
101
102 Ok(PreconditionResult {
103 not_modified: false,
104 })
105}