[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:
Skyper 2021-02-20 12:31:37 +00:00 committed by GitHub
parent 6d6df45f23
commit d3ab3936de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 96 additions and 15 deletions

View file

@ -761,8 +761,11 @@ fn can_get_hash_for_static_files() {
"index.html",
"src=\"https://replace-this-with-your-url.com/scripts/hello.js\""
));
assert!(file_contains!(public, "index.html",
"integrity=\"sha384-01422f31eaa721a6c4ac8c6fa09a27dd9259e0dfcf3c7593d7810d912a9de5ca2f582df978537bcd10f76896db61fbb9\""));
assert!(file_contains!(
public,
"index.html",
"integrity=\"sha384-AUIvMeqnIabErIxvoJon3ZJZ4N/PPHWT14ENkSqd5covWC35eFN7zRD3aJbbYfu5\""
));
}
#[test]

View file

@ -4,6 +4,7 @@ use std::path::PathBuf;
use std::sync::{Arc, Mutex, RwLock};
use std::{fs, io, result};
use base64::encode as encode_b64;
use sha2::{Digest, Sha256, Sha384, Sha512};
use svg_metadata as svg;
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()))
}
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> {
let mut hasher = Sha384::new();
io::copy(&mut file, &mut hasher)?;
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> {
let mut hasher = Sha512::new();
io::copy(&mut file, &mut hasher)?;
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> {
Err(format!(
"file `{}` not found; searched in{}",
@ -178,6 +197,7 @@ impl GetFileHash {
}
const DEFAULT_SHA_TYPE: u16 = 384;
const DEFAULT_BASE64: bool = false;
impl TeraFn for GetFileHash {
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"
)
.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 {
256 => compute_file_sha256,
384 => compute_file_sha384,
512 => compute_file_sha512,
_ => return Err("`get_file_hash`: `sha_type` must be 256, 384 or 512".into()),
let compute_hash_fn = match (sha_type, base64) {
(256, true) => compute_file_sha256_base64,
(256, false) => compute_file_sha256,
(384, true) => compute_file_sha384_base64,
(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);
@ -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]
fn can_get_file_hash_sha384() {
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());
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]
@ -833,7 +887,23 @@ title = "A title"
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());
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]

View file

@ -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
by passing `cachebust=true` to the `get_url` function.
### `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
{{/* 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
<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`
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`.

View file

@ -14,5 +14,5 @@
{% block script %}
<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 %}