dropbox_content_hasher/
lib.rs

1#![deny(unused_must_use, missing_debug_implementations)]
2#![warn(rust_2018_idioms)]
3
4use digest;
5
6use digest::generic_array::typenum::U64;
7use digest::generic_array::GenericArray;
8use digest::{Reset, Digest, FixedOutput, Input};
9use sha2::Sha256;
10
11use std::fs::File;
12use std::io::Read;
13use std::path::Path;
14
15pub const BLOCK_SIZE: usize = 4 * 1024 * 1024;
16
17/// Computes a hash using the same algorithm that the Dropbox API uses for the
18/// the "content_hash" metadata field.
19///
20/// Implements the `digest::Digest` trait, whose `result()` function returns a
21/// raw binary representation of the hash.  The "content_hash" field in the
22/// Dropbox API is a hexadecimal-encoded version of this value.
23///
24/// For examples see `hash_file` and `hash_reader`, for an using this object directly see the
25/// source of `hash_reader`.
26
27#[derive(Clone, Debug)]
28pub struct DropboxContentHasher {
29    overall_hasher: Sha256,
30    block_hasher: Sha256,
31    block_pos: usize,
32}
33
34impl DropboxContentHasher {
35    pub fn new() -> Self {
36        DropboxContentHasher {
37            overall_hasher: Sha256::new(),
38            block_hasher: Sha256::new(),
39            block_pos: 0,
40        }
41    }
42
43    /// Return the content_hash for a given file, or an io::Error from either opening or reading
44    /// the file.
45    ///
46    /// ```
47    /// extern crate digest;
48    /// use dropbox_content_hasher::DropboxContentHasher;
49    /// use std::path::PathBuf;
50    ///
51    /// let path = PathBuf::from("src/lib.rs");
52    ///
53    /// let hex_hash = format!("{:x}", DropboxContentHasher::hash_file(&path).unwrap());
54    /// println!("{}", hex_hash);
55    /// ```
56    pub fn hash_file<T>(path: T) -> std::io::Result<GenericArray<u8, <Self as FixedOutput>::OutputSize>>
57    where T: AsRef<Path> {
58        let file = File::open(&path)?;
59        return DropboxContentHasher::hash_reader(&file);
60    }
61
62    /// Return the content_hash for a given object implementing Read, or an io::Error resulting
63    /// from trying to read its contents.
64    ///
65    /// ```
66    /// extern crate digest;
67    /// use dropbox_content_hasher::DropboxContentHasher;
68    ///
69    /// let mut f = std::fs::File::open("src/lib.rs").unwrap();
70    ///
71    /// let hex_hash = format!("{:x}", DropboxContentHasher::hash_reader(&mut f).unwrap());
72    /// println!("{}", hex_hash);
73    /// ```
74    pub fn hash_reader<T>(mut reader: T) -> std::io::Result<GenericArray<u8, <Self as FixedOutput>::OutputSize>>
75    where T: Read {
76        let mut hasher = DropboxContentHasher::new();
77        let mut buf = vec![0; BLOCK_SIZE];
78        loop {
79            let len = reader.read(&mut buf)?;
80            if len == 0 { break; }
81            Input::input(&mut hasher, &buf[..len])
82        }
83        Ok(hasher.result())
84    }
85}
86
87impl Default for DropboxContentHasher {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93impl Reset for DropboxContentHasher {
94    fn reset(&mut self) {
95        self.overall_hasher = Sha256::new();
96        self.block_hasher = Sha256::new();
97        self.block_pos = 0;
98    }
99}
100
101impl Input for DropboxContentHasher {
102    fn input<B: AsRef<[u8]>>(&mut self, data: B) {
103        let mut input = data.as_ref();
104        while input.len() > 0 {
105            if self.block_pos == BLOCK_SIZE {
106                let block_hasher = self.block_hasher.clone();
107                Input::input(&mut self.overall_hasher, block_hasher.result().as_slice());
108                self.block_hasher = Sha256::new();
109                self.block_pos = 0;
110            }
111
112            let space_in_block = BLOCK_SIZE - self.block_pos;
113            let (head, rest) = input.split_at(::std::cmp::min(input.len(), space_in_block));
114            Input::input(&mut self.block_hasher, head);
115
116            self.block_pos += head.len();
117            input = rest;
118        }
119    }
120}
121
122impl FixedOutput for DropboxContentHasher {
123    type OutputSize = <Sha256 as FixedOutput>::OutputSize;
124
125    fn fixed_result(mut self) -> GenericArray<u8, Self::OutputSize> {
126        if self.block_pos > 0 {
127            Input::input(&mut self.overall_hasher, self.block_hasher.result().as_slice());
128        }
129        self.overall_hasher.result()
130    }
131}
132
133impl digest::BlockInput for DropboxContentHasher {
134    type BlockSize = U64;
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use std::fs::File;
141
142    #[test]
143    fn test_vector() {
144        let expected = "485291fa0ee50c016982abbfa943957bcd231aae0492ccbaa22c58e3997b35e0".to_string();
145        let mut file = File::open("test-data/milky-way-nasa.jpg").expect("Couldn't open test file");
146
147        let result = DropboxContentHasher::hash_reader(&mut file).expect("Couldn't hash test file");
148
149        let hex_hash = format!("{:x}", result);
150        assert_eq!(hex_hash, expected);
151    }
152}