/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::iter::Filter; use std::str::Split; use std::sync::MutexGuard; use base64::Engine; use generic_array::ArrayLength; use net_traits::response::{Response, ResponseBody, ResponseType}; use sha2::{Digest, Sha256, Sha384, Sha512}; const SUPPORTED_ALGORITHM: &[&str] = &["sha256", "sha384", "sha512"]; pub type StaticCharVec = &'static [char]; /// A "space character" according to: /// /// pub static HTML_SPACE_CHARACTERS: StaticCharVec = &['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}']; #[derive(Clone)] pub struct SriEntry { pub alg: String, pub val: String, // TODO : Current version of spec does not define any option. // Can be refactored into appropriate datastructure when future // spec has more details. pub opt: Option, } impl SriEntry { pub fn new(alg: &str, val: &str, opt: Option) -> SriEntry { SriEntry { alg: alg.to_owned(), val: val.to_owned(), opt, } } } /// pub fn parsed_metadata(integrity_metadata: &str) -> Vec { // Step 1 let mut result = vec![]; // Step 3 let tokens = split_html_space_chars(integrity_metadata); for token in tokens { let parsed_data: Vec<&str> = token.split('-').collect(); if parsed_data.len() > 1 { let alg = parsed_data[0]; if !SUPPORTED_ALGORITHM.contains(&alg) { continue; } let data: Vec<&str> = parsed_data[1].split('?').collect(); let digest = data[0]; let opt = if data.len() > 1 { Some(data[1].to_owned()) } else { None }; result.push(SriEntry::new(alg, digest, opt)); } } result } /// pub fn get_prioritized_hash_function( hash_func_left: &str, hash_func_right: &str, ) -> Option { let left_priority = SUPPORTED_ALGORITHM .iter() .position(|s| *s == hash_func_left) .unwrap(); let right_priority = SUPPORTED_ALGORITHM .iter() .position(|s| *s == hash_func_right) .unwrap(); if left_priority == right_priority { return None; } if left_priority > right_priority { Some(hash_func_left.to_owned()) } else { Some(hash_func_right.to_owned()) } } /// pub fn get_strongest_metadata(integrity_metadata_list: Vec) -> Vec { let mut result: Vec = vec![integrity_metadata_list[0].clone()]; let mut current_algorithm = result[0].alg.clone(); for integrity_metadata in &integrity_metadata_list[1..] { let prioritized_hash = get_prioritized_hash_function(&integrity_metadata.alg, ¤t_algorithm); if prioritized_hash.is_none() { result.push(integrity_metadata.clone()); } else if let Some(algorithm) = prioritized_hash { if algorithm != current_algorithm { result = vec![integrity_metadata.clone()]; current_algorithm = algorithm; } } } result } /// fn apply_algorithm_to_response, D: Digest>( body: MutexGuard, mut hasher: D, ) -> String { if let ResponseBody::Done(ref vec) = *body { hasher.update(vec); let response_digest = hasher.finalize(); //Now hash base64::engine::general_purpose::STANDARD.encode(&response_digest) } else { unreachable!("Tried to calculate digest of incomplete response body") } } /// fn is_eligible_for_integrity_validation(response: &Response) -> bool { matches!( response.response_type, ResponseType::Basic | ResponseType::Default | ResponseType::Cors ) } /// pub fn is_response_integrity_valid(integrity_metadata: &str, response: &Response) -> bool { let parsed_metadata_list: Vec = parsed_metadata(integrity_metadata); // Step 2 & 4 if parsed_metadata_list.is_empty() { return true; } // Step 3 if !is_eligible_for_integrity_validation(response) { return false; } // Step 5 let metadata: Vec = get_strongest_metadata(parsed_metadata_list); for item in metadata { let body = response.body.lock().unwrap(); let algorithm = item.alg; let digest = item.val; let hashed = match &*algorithm { "sha256" => apply_algorithm_to_response(body, Sha256::new()), "sha384" => apply_algorithm_to_response(body, Sha384::new()), "sha512" => apply_algorithm_to_response(body, Sha512::new()), _ => continue, }; if hashed == digest { return true; } } false } pub fn split_html_space_chars(s: &str) -> Filter, fn(&&str) -> bool> { fn not_empty(&split: &&str) -> bool { !split.is_empty() } s.split(HTML_SPACE_CHARACTERS) .filter(not_empty as fn(&&str) -> bool) }