mirror of
https://github.com/getzola/zola
synced 2024-11-10 14:24:27 +00:00
add get_hash function (#1962)
Co-authored-by: Newbyte <newbie13xd@gmail.com> Co-authored-by: n-tux-tmp <n-tux-tmp@example.com> Co-authored-by: n-tux-tmp <n-tux-tmp@noreply.example.org>
This commit is contained in:
parent
f1b5c2ea74
commit
baa473ceea
5 changed files with 176 additions and 59 deletions
|
@ -53,14 +53,13 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> {
|
|||
),
|
||||
);
|
||||
site.tera.register_function(
|
||||
"get_file_hash",
|
||||
global_fns::GetFileHash::new(
|
||||
"get_hash",
|
||||
global_fns::GetHash::new(
|
||||
site.base_path.clone(),
|
||||
site.config.theme.clone(),
|
||||
site.output_path.clone(),
|
||||
),
|
||||
);
|
||||
|
||||
site.tera.register_filter(
|
||||
"markdown",
|
||||
filters::MarkdownFilter::new(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, io, result};
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
|
||||
use crate::global_fns::helpers::search_for_file;
|
||||
use config::Config;
|
||||
|
@ -10,20 +11,20 @@ use libs::tera::{from_value, to_value, Function as TeraFn, Result, Value};
|
|||
use libs::url;
|
||||
use utils::site::resolve_internal_link;
|
||||
|
||||
fn compute_file_hash<D: digest::Digest>(
|
||||
mut file: fs::File,
|
||||
fn compute_hash<D: digest::Digest>(
|
||||
literal: String,
|
||||
as_base64: bool,
|
||||
) -> result::Result<String, io::Error>
|
||||
) -> String
|
||||
where
|
||||
digest::Output<D>: core::fmt::LowerHex,
|
||||
D: std::io::Write,
|
||||
{
|
||||
let mut hasher = D::new();
|
||||
io::copy(&mut file, &mut hasher)?;
|
||||
hasher.update(literal);
|
||||
if as_base64 {
|
||||
Ok(encode_b64(hasher.finalize()))
|
||||
encode_b64(hasher.finalize())
|
||||
} else {
|
||||
Ok(format!("{:x}", hasher.finalize()))
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +129,13 @@ impl TeraFn for GetUrl {
|
|||
)
|
||||
.map_err(|e| format!("`get_url`: {}", e))?
|
||||
.and_then(|(p, _)| fs::File::open(&p).ok())
|
||||
.and_then(|f| compute_file_hash::<Sha256>(f, false).ok())
|
||||
.and_then(|mut f| {
|
||||
let mut contents = String::new();
|
||||
|
||||
f.read_to_string(&mut contents).ok()?;
|
||||
|
||||
Some(compute_hash::<Sha256>(contents, false))
|
||||
})
|
||||
{
|
||||
Some(hash) => {
|
||||
permalink = format!("{}?h={}", permalink, hash);
|
||||
|
@ -166,71 +173,99 @@ impl TeraFn for GetUrl {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GetFileHash {
|
||||
pub struct GetHash {
|
||||
base_path: PathBuf,
|
||||
theme: Option<String>,
|
||||
output_path: PathBuf,
|
||||
}
|
||||
impl GetFileHash {
|
||||
impl GetHash {
|
||||
pub fn new(base_path: PathBuf, theme: Option<String>, output_path: PathBuf) -> Self {
|
||||
Self { base_path, theme, output_path }
|
||||
}
|
||||
}
|
||||
|
||||
impl TeraFn for GetFileHash {
|
||||
impl TeraFn for GetHash {
|
||||
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
|
||||
let path = required_arg!(
|
||||
let path = optional_arg!(
|
||||
String,
|
||||
args.get("path"),
|
||||
"`get_file_hash` requires a `path` argument with a string value"
|
||||
"`get_hash` requires either a `path` or a `literal` argument with a string value"
|
||||
);
|
||||
|
||||
let literal = optional_arg!(
|
||||
String,
|
||||
args.get("literal"),
|
||||
"`get_hash` requires either a `path` or a `literal` argument with a string value"
|
||||
);
|
||||
|
||||
let contents = match (path, literal) {
|
||||
(Some(_), Some(_)) => {
|
||||
return Err("`get_hash`: must have only one of `path` or `literal` argument".into());
|
||||
},
|
||||
(None, None) => {
|
||||
return Err("`get_hash`: must have at least one of `path` or `literal` argument".into());
|
||||
},
|
||||
(Some(path_v), None) => {
|
||||
let file_path =
|
||||
match search_for_file(&self.base_path, &path_v, &self.theme, &self.output_path)
|
||||
.map_err(|e| format!("`get_hash`: {}", e))?
|
||||
{
|
||||
Some((f, _)) => f,
|
||||
None => {
|
||||
return Err(format!("`get_hash`: Cannot find file: {}", path_v).into());
|
||||
}
|
||||
};
|
||||
|
||||
let mut f = match std::fs::File::open(file_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
return Err(format!("File {} could not be open: {}", path_v, e).into());
|
||||
}
|
||||
};
|
||||
|
||||
let mut contents = String::new();
|
||||
|
||||
match f.read_to_string(&mut contents) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
return Err(format!("File {} could not be read: {}", path_v, e).into());
|
||||
}
|
||||
};
|
||||
|
||||
contents
|
||||
}
|
||||
(None, Some(literal_v)) => { literal_v }
|
||||
};
|
||||
|
||||
|
||||
let sha_type = optional_arg!(
|
||||
u16,
|
||||
args.get("sha_type"),
|
||||
"`get_file_hash`: `sha_type` must be 256, 384 or 512"
|
||||
"`get_hash`: `sha_type` must be 256, 384 or 512"
|
||||
)
|
||||
.unwrap_or(384);
|
||||
|
||||
let base64 = optional_arg!(
|
||||
bool,
|
||||
args.get("base64"),
|
||||
"`get_file_hash`: `base64` must be true or false"
|
||||
"`get_hash`: `base64` must be true or false"
|
||||
)
|
||||
.unwrap_or(true);
|
||||
|
||||
let file_path =
|
||||
match search_for_file(&self.base_path, &path, &self.theme, &self.output_path)
|
||||
.map_err(|e| format!("`get_file_hash`: {}", e))?
|
||||
{
|
||||
Some((f, _)) => f,
|
||||
None => {
|
||||
return Err(format!("`get_file_hash`: Cannot find file: {}", path).into());
|
||||
}
|
||||
};
|
||||
|
||||
let f = match std::fs::File::open(file_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
return Err(format!("File {} could not be open: {}", path, e).into());
|
||||
}
|
||||
};
|
||||
|
||||
let hash = match sha_type {
|
||||
256 => compute_file_hash::<Sha256>(f, base64),
|
||||
384 => compute_file_hash::<Sha384>(f, base64),
|
||||
512 => compute_file_hash::<Sha512>(f, base64),
|
||||
_ => return Err("`get_file_hash`: Invalid sha value".into()),
|
||||
256 => compute_hash::<Sha256>(contents, base64),
|
||||
384 => compute_hash::<Sha384>(contents, base64),
|
||||
512 => compute_hash::<Sha512>(contents, base64),
|
||||
_ => return Err("`get_hash`: Invalid sha value".into()),
|
||||
};
|
||||
|
||||
match hash {
|
||||
Ok(digest) => Ok(to_value(digest).unwrap()),
|
||||
Err(_) => Err("`get_file_hash`: could no compute hash".into()),
|
||||
}
|
||||
Ok(to_value(hash).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{GetFileHash, GetUrl};
|
||||
use super::{GetHash, GetUrl};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::create_dir;
|
||||
|
@ -456,7 +491,7 @@ title = "A title"
|
|||
#[test]
|
||||
fn can_get_file_hash_sha256_no_base64() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||
args.insert("sha_type".to_string(), to_value(256).unwrap());
|
||||
|
@ -470,7 +505,7 @@ title = "A title"
|
|||
#[test]
|
||||
fn can_get_file_hash_sha256_base64() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||
args.insert("sha_type".to_string(), to_value(256).unwrap());
|
||||
|
@ -481,7 +516,7 @@ title = "A title"
|
|||
#[test]
|
||||
fn can_get_file_hash_sha384_no_base64() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||
args.insert("base64".to_string(), to_value(false).unwrap());
|
||||
|
@ -494,7 +529,7 @@ title = "A title"
|
|||
#[test]
|
||||
fn can_get_file_hash_sha384() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||
assert_eq!(
|
||||
|
@ -506,7 +541,7 @@ title = "A title"
|
|||
#[test]
|
||||
fn can_get_file_hash_sha512_no_base64() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
||||
|
@ -520,7 +555,7 @@ title = "A title"
|
|||
#[test]
|
||||
fn can_get_file_hash_sha512() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
||||
|
@ -530,6 +565,85 @@ title = "A title"
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_hash_sha256_no_base64() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("literal".to_string(), to_value("Hello World").unwrap());
|
||||
args.insert("sha_type".to_string(), to_value(256).unwrap());
|
||||
args.insert("base64".to_string(), to_value(false).unwrap());
|
||||
assert_eq!(
|
||||
static_fn.call(&args).unwrap(),
|
||||
"a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_hash_sha256_base64() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("literal".to_string(), to_value("Hello World").unwrap());
|
||||
args.insert("sha_type".to_string(), to_value(256).unwrap());
|
||||
args.insert("base64".to_string(), to_value(true).unwrap());
|
||||
assert_eq!(
|
||||
static_fn.call(&args).unwrap(),
|
||||
"pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_hash_sha384_no_base64() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("literal".to_string(), to_value("Hello World").unwrap());
|
||||
args.insert("base64".to_string(), to_value(false).unwrap());
|
||||
assert_eq!(
|
||||
static_fn.call(&args).unwrap(),
|
||||
"99514329186b2f6ae4a1329e7ee6c610a729636335174ac6b740f9028396fcc803d0e93863a7c3d90f86beee782f4f3f"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_hash_sha384() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("literal".to_string(), to_value("Hello World").unwrap());
|
||||
assert_eq!(
|
||||
static_fn.call(&args).unwrap(),
|
||||
"mVFDKRhrL2rkoTKefubGEKcpY2M1F0rGt0D5AoOW/MgD0Ok4Y6fD2Q+Gvu54L08/"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_hash_sha512_no_base64() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("literal".to_string(), to_value("Hello World").unwrap());
|
||||
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
||||
args.insert("base64".to_string(), to_value(false).unwrap());
|
||||
assert_eq!(
|
||||
static_fn.call(&args).unwrap(),
|
||||
"2c74fd17edafd80e8447b0d46741ee243b7eb74dd2149a0ab1b9246fb30382f27e853d8585719e0e67cbda0daa8f51671064615d645ae27acb15bfb1447f459b"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_hash_sha512() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("literal".to_string(), to_value("Hello World").unwrap());
|
||||
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
||||
assert_eq!(
|
||||
static_fn.call(&args).unwrap(),
|
||||
"LHT9F+2v2A6ER7DUZ0HuJDt+t03SFJoKsbkkb7MDgvJ+hT2FhXGeDmfL2g2qj1FnEGRhXWRa4nrLFb+xRH9Fmw=="
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_resolve_asset_path_to_valid_url() {
|
||||
let config = Config::parse(CONFIG_DATA).unwrap();
|
||||
|
@ -540,7 +654,7 @@ title = "A title"
|
|||
args.insert(
|
||||
"path".to_string(),
|
||||
to_value(dir.path().join("app.css").strip_prefix(std::env::temp_dir()).unwrap())
|
||||
.unwrap(),
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(
|
||||
static_fn.call(&args).unwrap(),
|
||||
|
@ -554,7 +668,7 @@ title = "A title"
|
|||
#[test]
|
||||
fn error_when_file_not_found_for_hash() {
|
||||
let dir = create_temp_dir();
|
||||
let static_fn = GetFileHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let static_fn = GetHash::new(dir.into_path(), None, PathBuf::new());
|
||||
let mut args = HashMap::new();
|
||||
args.insert("path".to_string(), to_value("doesnt-exist").unwrap());
|
||||
let err = format!("{}", static_fn.call(&args).unwrap_err());
|
||||
|
|
|
@ -9,7 +9,7 @@ mod images;
|
|||
mod load_data;
|
||||
|
||||
pub use self::content::{GetPage, GetSection, GetTaxonomy, GetTaxonomyTerm, GetTaxonomyUrl};
|
||||
pub use self::files::{GetFileHash, GetUrl};
|
||||
pub use self::files::{GetHash, GetUrl};
|
||||
pub use self::i18n::Trans;
|
||||
pub use self::images::{GetImageMetadata, ResizeImage};
|
||||
pub use self::load_data::LoadData;
|
||||
|
|
|
@ -234,17 +234,21 @@ In the case of non-internal links, you can also add a cachebust of the format `?
|
|||
by passing `cachebust=true` to the `get_url` function. In this case, the path will need to resolve to an actual file.
|
||||
See [File Searching Logic](@/documentation/templates/overview.md#file-searching-logic) for details.
|
||||
|
||||
### `get_file_hash`
|
||||
### `get_hash`
|
||||
|
||||
Returns the hash digest (SHA-256, SHA-384 or SHA-512) of a file.
|
||||
Returns the hash digest (SHA-256, SHA-384 or SHA-512) of a file or a string literal.
|
||||
|
||||
It can take the following arguments:
|
||||
- `path`: mandatory, see [File Searching Logic](@/documentation/templates/overview.md#file-searching-logic) for details
|
||||
- **or** `literal`: mandatory, the string value to be hashed
|
||||
- `sha_type`: optional, one of `256`, `384` or `512`, defaults to `384`
|
||||
- `base64`: optional, `true` or `false`, defaults to `true`. Whether to encode the hash as base64
|
||||
|
||||
Either `path` or `literal` must be given.
|
||||
|
||||
```jinja2
|
||||
{{/* get_file_hash(path="static/js/app.js", sha_type=256) */}}
|
||||
{{/* get_hash(literal="Hello World", sha_type=256) */}}
|
||||
{{/* get_hash(path="static/js/app.js", sha_type=256) */}}
|
||||
```
|
||||
|
||||
The function can also output a base64-encoded hash value when its `base64`
|
||||
|
@ -253,10 +257,10 @@ integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Int
|
|||
|
||||
```jinja2
|
||||
<script src="{{/* get_url(path="static/js/app.js") */}}"
|
||||
integrity="sha384-{{ get_file_hash(path="static/js/app.js", sha_type=384, base64=true) | safe }}"></script>
|
||||
integrity="sha384-{{ get_hash(path="static/js/app.js", sha_type=384, base64=true) | safe }}"></script>
|
||||
```
|
||||
|
||||
Do note that subresource integrity is typically used when using external scripts, which `get_file_hash` does not support.
|
||||
Do note that subresource integrity is typically used when using external scripts, which `get_hash` does not support.
|
||||
|
||||
### `get_image_metadata`
|
||||
|
||||
|
|
|
@ -14,5 +14,5 @@
|
|||
|
||||
{% block script %}
|
||||
<script src="{{ get_url(path="scripts/hello.js") | safe }}"
|
||||
integrity="sha384-{{ get_file_hash(path="scripts/hello.js", base64=true) | safe }}"></script>
|
||||
integrity="sha384-{{ get_hash(path="scripts/hello.js", base64=true) | safe }}"></script>
|
||||
{% endblock script %}
|
||||
|
|
Loading…
Reference in a new issue