Skip to content

Commit

Permalink
feat(traits): Derive more traits, including Hash, and fix line endings
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Feb 19, 2023
1 parent 0d6c019 commit 9dbb1e8
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 154 deletions.
118 changes: 59 additions & 59 deletions src/algorithm.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,59 @@
use std::fmt;

use crate::errors::Error;

/**
Valid algorithms for integrity strings.
`Sha1` is a special case in this library -- it's not allowed by the
current SRI spec, but it's useful enough that having first-class support
makes sense. It should also be completely harmless to have in your strings
if you do use it in a browser context.
*/
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Algorithm {
Sha512,
Sha384,
Sha256,
Sha1,
}

impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", format!("{:?}", self).to_lowercase())
}
}

impl std::str::FromStr for Algorithm {
type Err = Error;

fn from_str(s: &str) -> Result<Algorithm, Self::Err> {
match s {
"sha1" => Ok(Algorithm::Sha1),
"sha256" => Ok(Algorithm::Sha256),
"sha384" => Ok(Algorithm::Sha384),
"sha512" => Ok(Algorithm::Sha512),
_ => Err(Error::ParseIntegrityError(s.into())),
}
}
}

#[cfg(test)]
mod tests {
use super::Algorithm::*;

#[test]
fn algorithm_formatting() {
assert_eq!(format!("{}", Sha1), "sha1");
assert_eq!(format!("{}", Sha256), "sha256");
assert_eq!(format!("{}", Sha384), "sha384");
assert_eq!(format!("{}", Sha512), "sha512");
}

#[test]
fn ordering() {
let mut arr = [Sha1, Sha256, Sha384, Sha512];
arr.sort_unstable();
assert_eq!(arr, [Sha512, Sha384, Sha256, Sha1,])
}
}
use std::fmt;

use crate::errors::Error;

/**
Valid algorithms for integrity strings.
`Sha1` is a special case in this library -- it's not allowed by the
current SRI spec, but it's useful enough that having first-class support
makes sense. It should also be completely harmless to have in your strings
if you do use it in a browser context.
*/
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Algorithm {
Sha512,
Sha384,
Sha256,
Sha1,
}

impl fmt::Display for Algorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", format!("{:?}", self).to_lowercase())
}
}

impl std::str::FromStr for Algorithm {
type Err = Error;

fn from_str(s: &str) -> Result<Algorithm, Self::Err> {
match s {
"sha1" => Ok(Algorithm::Sha1),
"sha256" => Ok(Algorithm::Sha256),
"sha384" => Ok(Algorithm::Sha384),
"sha512" => Ok(Algorithm::Sha512),
_ => Err(Error::ParseIntegrityError(s.into())),
}
}
}

#[cfg(test)]
mod tests {
use super::Algorithm::*;

#[test]
fn algorithm_formatting() {
assert_eq!(format!("{}", Sha1), "sha1");
assert_eq!(format!("{}", Sha256), "sha256");
assert_eq!(format!("{}", Sha384), "sha384");
assert_eq!(format!("{}", Sha512), "sha512");
}

#[test]
fn ordering() {
let mut arr = [Sha1, Sha256, Sha384, Sha512];
arr.sort_unstable();
assert_eq!(arr, [Sha512, Sha384, Sha256, Sha1,])
}
}
155 changes: 78 additions & 77 deletions src/checker.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,78 @@
use crate::algorithm::Algorithm;
use crate::integrity::Integrity;
use crate::opts::IntegrityOpts;
use crate::Error;

/**
Check data against an [`Integrity`](struct.Integrity.html).
# Examples
```
# use ssri::{Algorithm, Integrity, IntegrityChecker};
let data = b"hello world";
let sri = Integrity::from(&data);
let checker = IntegrityChecker::new(sri).chain(&data);
assert_eq!(checker.result().unwrap(), Algorithm::Sha256);
```
*/
pub struct IntegrityChecker {
sri: Integrity,
builder: IntegrityOpts,
}

impl IntegrityChecker {
/// Creates a new `IntegrityChecker` builder. Use this to verify chunked
/// data.
pub fn new(sri: Integrity) -> IntegrityChecker {
let builder = IntegrityOpts::new().algorithm(sri.pick_algorithm());
IntegrityChecker { sri, builder }
}
/// Add some data to the running checker.
pub fn input<B: AsRef<[u8]>>(&mut self, data: B) {
self.builder.input(data);
}
/// Same as `IntegrityChecker::input`, but allows chained calls.
pub fn chain<B: AsRef<[u8]>>(mut self, data: B) -> Self {
self.builder.input(data);
self
}
/// Returns the matching algorithm if the inputted data matches the input `Integrity`.
pub fn result(self) -> Result<Algorithm, Error> {
let sri = self.builder.result();
let algo = self.sri.pick_algorithm();
let wanted = self.sri;
wanted
.hashes
.iter()
.take_while(|h| h.algorithm == algo)
.find(|&h| *h == sri.hashes[0])
.map(|_| algo)
.ok_or(Error::IntegrityCheckError(wanted, sri))
}
}

#[cfg(test)]
mod tests {
use super::Algorithm;
use super::Integrity;
use super::IntegrityChecker;

#[test]
fn basic_test() {
let sri = Integrity::from(b"hello world");
let result = IntegrityChecker::new(sri).chain(b"hello world").result();
assert_eq!(result.unwrap(), Algorithm::Sha256)
}
#[test]
fn multi_hash() {
let sri = "sha256-deadbeef"
.parse::<Integrity>()
.unwrap()
.concat(Integrity::from(b"hello world"));
eprintln!("\n{}", sri);
let result = IntegrityChecker::new(sri).chain(b"hello world").result();
assert_eq!(result.unwrap(), Algorithm::Sha256)
}
}
use crate::algorithm::Algorithm;
use crate::integrity::Integrity;
use crate::opts::IntegrityOpts;
use crate::Error;

/**
Check data against an [`Integrity`](struct.Integrity.html).
# Examples
```
# use ssri::{Algorithm, Integrity, IntegrityChecker};
let data = b"hello world";
let sri = Integrity::from(&data);
let checker = IntegrityChecker::new(sri).chain(&data);
assert_eq!(checker.result().unwrap(), Algorithm::Sha256);
```
*/
#[derive(Debug)]
pub struct IntegrityChecker {
sri: Integrity,
builder: IntegrityOpts,
}

impl IntegrityChecker {
/// Creates a new `IntegrityChecker` builder. Use this to verify chunked
/// data.
pub fn new(sri: Integrity) -> IntegrityChecker {
let builder = IntegrityOpts::new().algorithm(sri.pick_algorithm());
IntegrityChecker { sri, builder }
}
/// Add some data to the running checker.
pub fn input<B: AsRef<[u8]>>(&mut self, data: B) {
self.builder.input(data);
}
/// Same as `IntegrityChecker::input`, but allows chained calls.
pub fn chain<B: AsRef<[u8]>>(mut self, data: B) -> Self {
self.builder.input(data);
self
}
/// Returns the matching algorithm if the inputted data matches the input `Integrity`.
pub fn result(self) -> Result<Algorithm, Error> {
let sri = self.builder.result();
let algo = self.sri.pick_algorithm();
let wanted = self.sri;
wanted
.hashes
.iter()
.take_while(|h| h.algorithm == algo)
.find(|&h| *h == sri.hashes[0])
.map(|_| algo)
.ok_or(Error::IntegrityCheckError(wanted, sri))
}
}

#[cfg(test)]
mod tests {
use super::Algorithm;
use super::Integrity;
use super::IntegrityChecker;

#[test]
fn basic_test() {
let sri = Integrity::from(b"hello world");
let result = IntegrityChecker::new(sri).chain(b"hello world").result();
assert_eq!(result.unwrap(), Algorithm::Sha256)
}
#[test]
fn multi_hash() {
let sri = "sha256-deadbeef"
.parse::<Integrity>()
.unwrap()
.concat(Integrity::from(b"hello world"));
eprintln!("\n{}", sri);
let result = IntegrityChecker::new(sri).chain(b"hello world").result();
assert_eq!(result.unwrap(), Algorithm::Sha256)
}
}
28 changes: 14 additions & 14 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use thiserror::Error;

use crate::Integrity;

/// Integrity-related error values.
#[derive(Error, Debug)]
pub enum Error {
/// Error parsing an SRI string into an Integrity object.
#[error("Failed to parse subresource integrity string: {0}")]
ParseIntegrityError(String),
/// Error matching two Integrity values.
#[error("Integrity check failed.\n\tWanted: {0}\n\tActual: {1}")]
IntegrityCheckError(Integrity, Integrity),
}
use thiserror::Error;

use crate::Integrity;

/// Integrity-related error values.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum Error {
/// Error parsing an SRI string into an Integrity object.
#[error("Failed to parse subresource integrity string: {0}")]
ParseIntegrityError(String),
/// Error matching two Integrity values.
#[error("Integrity check failed.\n\tWanted: {0}\n\tActual: {1}")]
IntegrityCheckError(Integrity, Integrity),
}
2 changes: 1 addition & 1 deletion src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Represents a single algorithm/digest pair.
This is mostly internal, although users might interact with it directly on
occasion.
*/
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Hash {
pub algorithm: Algorithm,
pub digest: String,
Expand Down
2 changes: 1 addition & 1 deletion src/integrity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let parsed: Integrity = source.parse().unwrap();
assert_eq!(parsed.to_string(), source);
```
*/
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Integrity {
pub hashes: Vec<Hash>,
}
Expand Down
4 changes: 2 additions & 2 deletions src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use base64::Engine;
use digest::Digest;

#[allow(clippy::enum_variant_names)]
#[derive(Clone)]
#[derive(Clone, Debug)]
enum Hasher {
Sha1(sha1::Sha1),
Sha256(sha2::Sha256),
Expand All @@ -31,7 +31,7 @@ let sri = IntegrityOpts::new()
```
*/
#[derive(Clone, Default)]
#[derive(Clone, Debug, Default)]
pub struct IntegrityOpts {
hashers: Vec<Hasher>,
disturbed: bool,
Expand Down

0 comments on commit 9dbb1e8

Please sign in to comment.