[WIP] Add support for base64-encoded hash values to 'get_file_hash' (#1339)
* Add support for base64-encoded hash values The global template function 'get_file_hash' can now return a base64-encoded hash value when its 'base64' parameter is set to true. See discussion in #519. * Fix integrity attribute's value in test site SRI hash values must be base64-encoded. * Update documentation about 'get_file_hash' * Fix 'can_get_hash_for_static_files' unit test
This commit is contained in:
parent
6d6df45f23
commit
d3ab3936de
|
@ -761,8 +761,11 @@ fn can_get_hash_for_static_files() {
|
||||||
"index.html",
|
"index.html",
|
||||||
"src=\"https://replace-this-with-your-url.com/scripts/hello.js\""
|
"src=\"https://replace-this-with-your-url.com/scripts/hello.js\""
|
||||||
));
|
));
|
||||||
assert!(file_contains!(public, "index.html",
|
assert!(file_contains!(
|
||||||
"integrity=\"sha384-01422f31eaa721a6c4ac8c6fa09a27dd9259e0dfcf3c7593d7810d912a9de5ca2f582df978537bcd10f76896db61fbb9\""));
|
public,
|
||||||
|
"index.html",
|
||||||
|
"integrity=\"sha384-AUIvMeqnIabErIxvoJon3ZJZ4N/PPHWT14ENkSqd5covWC35eFN7zRD3aJbbYfu5\""
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::{fs, io, result};
|
use std::{fs, io, result};
|
||||||
|
|
||||||
|
use base64::encode as encode_b64;
|
||||||
use sha2::{Digest, Sha256, Sha384, Sha512};
|
use sha2::{Digest, Sha256, Sha384, Sha512};
|
||||||
use svg_metadata as svg;
|
use svg_metadata as svg;
|
||||||
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
|
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
|
||||||
|
@ -95,18 +96,36 @@ fn compute_file_sha256(mut file: fs::File) -> result::Result<String, io::Error>
|
||||||
Ok(format!("{:x}", hasher.finalize()))
|
Ok(format!("{:x}", hasher.finalize()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compute_file_sha256_base64(mut file: fs::File) -> result::Result<String, io::Error> {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
io::copy(&mut file, &mut hasher)?;
|
||||||
|
Ok(format!("{}", encode_b64(hasher.finalize())))
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_file_sha384(mut file: fs::File) -> result::Result<String, io::Error> {
|
fn compute_file_sha384(mut file: fs::File) -> result::Result<String, io::Error> {
|
||||||
let mut hasher = Sha384::new();
|
let mut hasher = Sha384::new();
|
||||||
io::copy(&mut file, &mut hasher)?;
|
io::copy(&mut file, &mut hasher)?;
|
||||||
Ok(format!("{:x}", hasher.finalize()))
|
Ok(format!("{:x}", hasher.finalize()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compute_file_sha384_base64(mut file: fs::File) -> result::Result<String, io::Error> {
|
||||||
|
let mut hasher = Sha384::new();
|
||||||
|
io::copy(&mut file, &mut hasher)?;
|
||||||
|
Ok(format!("{}", encode_b64(hasher.finalize())))
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_file_sha512(mut file: fs::File) -> result::Result<String, io::Error> {
|
fn compute_file_sha512(mut file: fs::File) -> result::Result<String, io::Error> {
|
||||||
let mut hasher = Sha512::new();
|
let mut hasher = Sha512::new();
|
||||||
io::copy(&mut file, &mut hasher)?;
|
io::copy(&mut file, &mut hasher)?;
|
||||||
Ok(format!("{:x}", hasher.finalize()))
|
Ok(format!("{:x}", hasher.finalize()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compute_file_sha512_base64(mut file: fs::File) -> result::Result<String, io::Error> {
|
||||||
|
let mut hasher = Sha512::new();
|
||||||
|
io::copy(&mut file, &mut hasher)?;
|
||||||
|
Ok(format!("{}", encode_b64(hasher.finalize())))
|
||||||
|
}
|
||||||
|
|
||||||
fn file_not_found_err(search_paths: &[PathBuf], url: &str) -> Result<Value> {
|
fn file_not_found_err(search_paths: &[PathBuf], url: &str) -> Result<Value> {
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"file `{}` not found; searched in{}",
|
"file `{}` not found; searched in{}",
|
||||||
|
@ -178,6 +197,7 @@ impl GetFileHash {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SHA_TYPE: u16 = 384;
|
const DEFAULT_SHA_TYPE: u16 = 384;
|
||||||
|
const DEFAULT_BASE64: bool = false;
|
||||||
|
|
||||||
impl TeraFn for GetFileHash {
|
impl TeraFn for GetFileHash {
|
||||||
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
|
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
|
||||||
|
@ -192,12 +212,21 @@ impl TeraFn for GetFileHash {
|
||||||
"`get_file_hash`: `sha_type` must be 256, 384 or 512"
|
"`get_file_hash`: `sha_type` must be 256, 384 or 512"
|
||||||
)
|
)
|
||||||
.unwrap_or(DEFAULT_SHA_TYPE);
|
.unwrap_or(DEFAULT_SHA_TYPE);
|
||||||
|
let base64 = optional_arg!(
|
||||||
|
bool,
|
||||||
|
args.get("base64"),
|
||||||
|
"`get_file_hash`: `base64` must be true or false"
|
||||||
|
)
|
||||||
|
.unwrap_or(DEFAULT_BASE64);
|
||||||
|
|
||||||
let compute_hash_fn = match sha_type {
|
let compute_hash_fn = match (sha_type, base64) {
|
||||||
256 => compute_file_sha256,
|
(256, true) => compute_file_sha256_base64,
|
||||||
384 => compute_file_sha384,
|
(256, false) => compute_file_sha256,
|
||||||
512 => compute_file_sha512,
|
(384, true) => compute_file_sha384_base64,
|
||||||
_ => return Err("`get_file_hash`: `sha_type` must be 256, 384 or 512".into()),
|
(384, false) => compute_file_sha384,
|
||||||
|
(512, true) => compute_file_sha512_base64,
|
||||||
|
(512, false) => compute_file_sha512,
|
||||||
|
_ => return Err("`get_file_hash`: bad arguments".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let hash = open_file(&self.search_paths, &path).and_then(compute_hash_fn);
|
let hash = open_file(&self.search_paths, &path).and_then(compute_hash_fn);
|
||||||
|
@ -819,12 +848,37 @@ title = "A title"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha256_base64() {
|
||||||
|
let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
|
||||||
|
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());
|
||||||
|
args.insert("base64".to_string(), to_value(true).unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "Vy5pHcaMP81lOuRjJhvbOPNdxvAXFdnOaHmTGd0ViEA=");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_get_file_hash_sha384() {
|
fn can_get_file_hash_sha384() {
|
||||||
let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
|
let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
|
||||||
let mut args = HashMap::new();
|
let mut args = HashMap::new();
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "141c09bd28899773b772bbe064d8b718fa1d6f2852b7eafd5ed6689d26b74883b79e2e814cd69d5b52ab476aa284c414");
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"141c09bd28899773b772bbe064d8b718fa1d6f2852b7eafd5ed6689d26b74883b79e2e814cd69d5b52ab476aa284c414"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha384_base64() {
|
||||||
|
let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("base64".to_string(), to_value(true).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"FBwJvSiJl3O3crvgZNi3GPodbyhSt+r9XtZonSa3SIO3ni6BTNadW1KrR2qihMQU"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -833,7 +887,23 @@ title = "A title"
|
||||||
let mut args = HashMap::new();
|
let mut args = HashMap::new();
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "379dfab35123b9159d9e4e92dc90e2be44cf3c2f7f09b2e2df80a1b219b461de3556c93e1a9ceb3008e999e2d6a54b4f1d65ee9be9be63fa45ec88931623372f");
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"379dfab35123b9159d9e4e92dc90e2be44cf3c2f7f09b2e2df80a1b219b461de3556c93e1a9ceb3008e999e2d6a54b4f1d65ee9be9be63fa45ec88931623372f"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha512_base64() {
|
||||||
|
let static_fn = GetFileHash::new(vec![TEST_CONTEXT.static_path.clone()]);
|
||||||
|
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());
|
||||||
|
args.insert("base64".to_string(), to_value(true).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"N536s1EjuRWdnk6S3JDivkTPPC9/CbLi34Chshm0Yd41Vsk+GpzrMAjpmeLWpUtPHWXum+m+Y/pF7IiTFiM3Lw=="
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -144,26 +144,34 @@ An example is:
|
||||||
In the case of non-internal links, you can also add a cachebust of the format `?h=<sha256>` at the end of a URL
|
In the case of non-internal links, you can also add a cachebust of the format `?h=<sha256>` at the end of a URL
|
||||||
by passing `cachebust=true` to the `get_url` function.
|
by passing `cachebust=true` to the `get_url` function.
|
||||||
|
|
||||||
|
|
||||||
### `get_file_hash`
|
### `get_file_hash`
|
||||||
|
|
||||||
Gets the hash digest for a static file. Supported hashes are SHA-256, SHA-384 (default) and SHA-512. Requires `path`. The `sha_type` key is optional and must be one of 256, 384 or 512.
|
Returns the hash digest of a static file. Supported hashing algorithms are
|
||||||
|
SHA-256, SHA-384 (default) and SHA-512. Requires `path`. The `sha_type`
|
||||||
|
parameter is optional and must be one of 256, 384 or 512.
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
{{/* get_file_hash(path="js/app.js", sha_type=256) */}}
|
{{/* get_file_hash(path="js/app.js", sha_type=256) */}}
|
||||||
```
|
```
|
||||||
|
|
||||||
This can be used to implement subresource integrity. Do note that subresource integrity is typically used when using external scripts, which `get_file_hash` does not support.
|
The function can also output a base64-encoded hash value when its `base64`
|
||||||
|
parameter is set to `true`. This can be used to implement [subresource
|
||||||
|
integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
|
||||||
|
|
||||||
```jinja2
|
```jinja2
|
||||||
<script src="{{/* get_url(path="js/app.js") */}}"
|
<script src="{{/* get_url(path="js/app.js") */}}"
|
||||||
integrity="sha384-{{/* get_file_hash(path="js/app.js", sha_type=384) */}}"></script>
|
integrity="sha384-{{ get_file_hash(path="js/app.js", sha_type=384, base64=true) | safe }}"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
Whenever hashing files, whether using `get_file_hash` or `get_url(..., cachebust=true)`, the file is searched for in three places: `static/`, `content/` and the output path (so e.g. compiled SASS can be hashed, too.)
|
Do note that subresource integrity is typically used when using external
|
||||||
|
scripts, which `get_file_hash` does not support.
|
||||||
|
|
||||||
|
Whenever hashing files, whether using `get_file_hash` or `get_url(...,
|
||||||
|
cachebust=true)`, the file is searched for in three places: `static/`,
|
||||||
|
`content/` and the output path (so e.g. compiled SASS can be hashed, too.)
|
||||||
|
|
||||||
### `get_image_metadata`
|
### `get_image_metadata`
|
||||||
|
|
||||||
Gets metadata for an image. This supports common formats like JPEG, PNG, as well as SVG.
|
Gets metadata for an image. This supports common formats like JPEG, PNG, as well as SVG.
|
||||||
Currently, the only supported keys are `width` and `height`.
|
Currently, the only supported keys are `width` and `height`.
|
||||||
|
|
||||||
|
|
|
@ -14,5 +14,5 @@
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
<script src="{{ get_url(path="scripts/hello.js") | safe }}"
|
<script src="{{ get_url(path="scripts/hello.js") | safe }}"
|
||||||
integrity="sha384-{{ get_file_hash(path="scripts/hello.js") }}"></script>
|
integrity="sha384-{{ get_file_hash(path="scripts/hello.js", base64=true) | safe }}"></script>
|
||||||
{% endblock script %}
|
{% endblock script %}
|
||||||
|
|
Loading…
Reference in a new issue