meta.rs (7177B)
1 //! The `meta` module is an optional feature which stores the MIME type value from the 2 //! `Content-Type` header of a client `PUT` request. 3 //! 4 //! The MIME type is stored on the server under the same file identifier as the content but with a 5 //! postfix '.meta'. 6 //! 7 //! A check is performed to validate that the specified value is a valid MIME type string. However, 8 //! no further check is performed to attempt to verify whether the declared MIME type correctly 9 //! describes the file contents. 10 //! 11 //! For subsequent `GET` requests for the same content, the stored MIME type will be used as the 12 //! `Content-Type` header. 13 //! 14 //! If no MIME type was specified for the content, or if the feature is not enabled, the 15 //! `Content-Type` header will always be `application/octet-stream` 16 //! 17 //! Any subsequent `PUT` for the same content specifying a `Content-Type` header will _overwrite_ 18 //! the previously stored MIME type. 19 //! 20 //! There is no feature to _delete_ the MIME type association from the server. However, setting the MIME 21 //! type explicitly to `application/octet-stream` will achieve the same result as for records that 22 //! do not have a MIME type association. 23 use std::fs::{ 24 File, 25 read, 26 }; 27 use std::path::{ 28 Path, 29 PathBuf, 30 }; 31 use std::io::Write; 32 use std::str::FromStr; 33 use mime::Mime; 34 use hex; 35 36 use log::{debug, error}; 37 38 39 fn meta_path(path: &Path, digest: &Vec<u8>) -> Result<PathBuf, std::io::Error> { 40 let digest_hex = hex::encode(digest); 41 let fp = path.join(digest_hex); 42 43 let mut path_canon = match fp.canonicalize() { 44 Ok(v) => { 45 v 46 }, 47 Err(e) => { 48 debug!("err {:?} {:?}", e, fp); 49 return Err(e); 50 } 51 }; 52 53 path_canon.set_extension("meta"); 54 Ok(path_canon) 55 } 56 57 fn filename_path(path: &Path, digest: &Vec<u8>) -> Result<PathBuf, std::io::Error> { 58 let digest_hex = hex::encode(digest); 59 let fp = path.join(digest_hex); 60 61 let mut path_canon = match fp.canonicalize() { 62 Ok(v) => { 63 v 64 }, 65 Err(e) => { 66 return Err(e); 67 } 68 }; 69 70 path_canon.set_extension("filename"); 71 Ok(path_canon) 72 } 73 74 75 /// Set a MIME type for the specified content. 76 /// 77 /// # Arguments 78 /// 79 /// * `path` - Absolute path to storage diectory. 80 /// * `digest` - Immutable reference to content. 81 /// * `typ` - MIME type to store for the content. 82 /// TODO: return none on write meta error 83 pub fn register_type(path: &Path, digest: &Vec<u8>, typ: Mime) -> Result<(), std::io::Error> { 84 match meta_path(path, digest) { 85 Ok(v) => { 86 match File::create(v) { 87 Ok(mut f) => { 88 match f.write(typ.as_ref().as_bytes()) { 89 Ok(_) => { 90 debug!("wrote meta to {:?}", f); 91 }, 92 Err(e) => { 93 error!("could not write meta to {:?} ({})", f, e); 94 }, 95 }; 96 } 97 Err(e) => { 98 return Err(e); 99 } 100 }; 101 }, 102 _ => {}, 103 }; 104 Ok(()) 105 } 106 107 108 /// Set a MIME type for the specified content. 109 /// 110 /// # Arguments 111 /// 112 /// * `path` - Absolute path to storage diectory. 113 /// * `digest` - Immutable reference to content. 114 /// * `typ` - MIME type to store for the content. 115 /// TODO: return none on write error 116 pub fn register_filename(path: &Path, digest: &Vec<u8>, name: String) -> Result<(), std::io::Error> { 117 match filename_path(path, digest) { 118 Ok(v) => { 119 match File::create(v) { 120 Ok(mut f) => { 121 match f.write(name.as_str().as_bytes()) { 122 Ok(_) => { 123 debug!("wrote to {:?}", f); 124 }, 125 Err(e) => { 126 error!("could not write to {:?} ({})", f, e); 127 }, 128 }; 129 } 130 Err(e) => { 131 return Err(e); 132 } 133 }; 134 }, 135 _ => {}, 136 }; 137 Ok(()) 138 } 139 140 /// Retrieve the MIME type for the specified content. 141 /// 142 /// # Arguments 143 /// 144 /// * `path` - Absolute path to storage diectory. 145 /// * `digest` - Immutable reference to content. 146 pub fn get_type(path: &Path, digest: &Vec<u8>) -> Option<Mime> { 147 let digest_hex = hex::encode(digest); 148 match meta_path(path, digest) { 149 Ok(v) => { 150 match read(v) { 151 Ok(r) => { 152 let mime_str = String::from_utf8(r).unwrap(); 153 debug!("content type {} retrieved for {}", &mime_str, &digest_hex); 154 let mime = Mime::from_str(mime_str.as_str()).unwrap(); 155 return Some(mime); 156 }, 157 Err(e) => { 158 debug!("meta type file not found for {}: {}", &digest_hex, e); 159 }, 160 }; 161 }, 162 _ => {}, 163 }; 164 None 165 } 166 167 168 /// Retrieve the alternate filename for the specified content. 169 /// 170 /// # Arguments 171 /// 172 /// * `path` - Absolute path to storage diectory. 173 /// * `digest` - Immutable reference to content. 174 pub fn get_filename(path: &Path, digest: &Vec<u8>) -> Option<String> { 175 let digest_hex = hex::encode(digest); 176 match filename_path(path, digest) { 177 Ok(v) => { 178 match read(v) { 179 Ok(r) => { 180 let filename_str = String::from_utf8(r).unwrap(); 181 debug!("filename {} retrieved for {}", &filename_str, &digest_hex); 182 return Some(filename_str); 183 }, 184 Err(e) => { 185 debug!("filename file not found for {}: {}", &digest_hex, e); 186 }, 187 }; 188 }, 189 _ => {}, 190 }; 191 None 192 } 193 194 #[cfg(test)] 195 mod tests { 196 use hex; 197 use std::str::FromStr; 198 use tempfile::tempdir; 199 use std::path::Path; 200 use std::fs::{ 201 write, 202 File, 203 }; 204 205 use mime::Mime; 206 207 use env_logger; 208 use log::{debug, info, error}; 209 210 use super::{ 211 register_type, 212 register_filename, 213 get_type, 214 get_filename, 215 }; 216 217 #[test] 218 fn test_meta_mime() { 219 let d = tempdir().unwrap(); 220 let dp = d.path(); 221 let url = "deadbeef"; 222 let digest = hex::decode(&url).unwrap(); 223 let mime_type = Mime::from_str("application/zip").unwrap(); 224 225 let fp = dp.join(&url); 226 write(&fp, b"foo"); 227 228 register_type(&dp, &digest, mime_type.clone()); 229 let mime_type_recovered = get_type(&dp, &digest).unwrap(); 230 assert_eq!(mime_type_recovered, mime_type); 231 } 232 233 #[test] 234 fn test_meta_filename() { 235 let d = tempdir().unwrap(); 236 let dp = d.path(); 237 let url = "deadbeef"; 238 let digest = hex::decode(&url).unwrap(); 239 let filename = "foo.zip"; 240 241 let fp = dp.join(&url); 242 write(&fp, b"foo"); 243 244 register_filename(&dp, &digest, String::from(filename)); 245 let filename_recovered = get_filename(&dp, &digest).unwrap(); 246 assert_eq!(filename_recovered, filename); 247 } 248 }