From 732f71cd4380500b9fc81118b5fa10f2b0f3047a Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 3 Sep 2019 16:51:26 +0200 Subject: [PATCH 1/8] Finalize 0.18.0-beta --- src/bucket.rs | 95 +++++++++++++++++++++++++------------------------- src/command.rs | 2 +- src/request.rs | 4 +-- 3 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/bucket.rs b/src/bucket.rs index 36cd4f7af8..5e3c19908a 100644 --- a/src/bucket.rs +++ b/src/bucket.rs @@ -24,7 +24,7 @@ pub enum Error { }, ParseError { source: ::region::Error - } + }, } type S3Result = std::result::Result; @@ -386,10 +386,10 @@ impl Bucket { Ok((tagging, result.1)) } - fn _list(&self, + fn list_page(&self, prefix: &str, delimiter: Option<&str>, - continuation_token: Option<&str>) + continuation_token: Option) -> S3Result<(ListBucketResult, u16)> { let command = Command::ListBucket { prefix, @@ -403,10 +403,10 @@ impl Bucket { Ok((deserialized, result.1)) } - fn _list_async(&self, + pub fn list_page_async(&self, prefix: &str, delimiter: Option<&str>, - continuation_token: Option<&str>) + continuation_token: Option) -> impl Future { let command = Command::ListBucket { prefix, @@ -443,19 +443,19 @@ impl Bucket { /// let credentials = Credentials::default(); /// let bucket = Bucket::new(bucket_name, region, credentials).unwrap(); /// - /// let results = bucket.list("/", Some("/")).unwrap(); + /// let results = bucket.list_all("/", Some("/")).unwrap(); /// for (list, code) in results { /// assert_eq!(200, code); /// println!("{:?}", list); /// } /// ``` - pub fn list(&self, prefix: &str, delimiter: Option<&str>) -> S3Result> { + pub fn list_all(&self, prefix: &str, delimiter: Option<&str>) -> S3Result> { let mut results = Vec::new(); - let mut result = self._list(prefix, delimiter, None)?; + let mut result = self.list_page(prefix, delimiter, None)?; loop { results.push(result.clone()); match result.0.next_continuation_token { - Some(token) => result = self._list(prefix, delimiter, Some(&token))?, + Some(token) => result = self.list_page(prefix, delimiter, Some(token))?, None => break, } } @@ -463,44 +463,45 @@ impl Bucket { Ok(results) } -// pub fn list_async(&self, prefix: &str, delimiter: Option<&str>) { -// let mut done = false; -// let list_entire_bucket_future = loop_fn((None, Vec::new()), |(continuation_token, results): (Option, Vec)|{ -// let mut inner_results = results; -// let mut token: Option = None; -// let ct = if continuation_token.is_some() { -// Some(continuation_token.unwrap().as_str()) -// } else { -// None -// }; -// let result_future = self._list_async(prefix, delimiter, ct); -// result_future.map(|item_future| { -// item_future.map(|s3_result| { -// match s3_result { -// Ok((list_bucket_result, status_code)) => { -// inner_results.push(list_bucket_result.clone()); -// if let Some(tkn) = list_bucket_result.next_continuation_token { -// token = Some(tkn.to_string()); -// done = false -// } else { -// done = true -// } -// }, -// Err(e) => panic!(e) -// } -// }) -// }); -// if done { -// Ok(Loop::Break((None, inner_results))) -// } else { -// Ok(Loop::Continue((token, inner_results))) -// } -// -// }); -// -// -//// Ok(results); -// } + + /// List the contents of an S3 bucket. + /// + /// # Example + /// + /// ```rust,no_run + /// extern crate futures; + /// use std::str; + /// use s3::bucket::Bucket; + /// use s3::credentials::Credentials; + /// use futures::future::Future; + /// let bucket_name = &"rust-s3-test"; + /// let aws_access = &"access_key"; + /// let aws_secret = &"secret_key"; + /// + /// let bucket_name = &"rust-s3-test"; + /// let region = "us-east-1".parse().unwrap(); + /// let credentials = Credentials::default(); + /// let bucket = Bucket::new(bucket_name, region, credentials).unwrap(); + /// + /// let results = bucket.list_all_async("/", Some("/")).and_then(|list| { + /// println!("{:?}", list); + /// Ok(()) + /// }); + /// ``` + pub fn list_all_async<'a>(&'a self, prefix: &'a str, delimiter: Option<&'a str>) -> impl Future> + 'a { + let list_entire_bucket = loop_fn((None, Vec::new()), move |(continuation_token, results): (Option, Vec)| { + let mut inner_results = results; + self.list_page_async(prefix, delimiter, continuation_token).and_then(|(result, _status_code)| { + inner_results.push(result.clone()); + match result.next_continuation_token { + Some(token) => Ok(Loop::Continue((Some(token), inner_results))), + None => Ok(Loop::Break((None, inner_results))) + } + }) + }); + list_entire_bucket.and_then(|(_token, results): (Option<&str>, Vec)| Ok(results)) + } + /// Get a reference to the name of the S3 bucket. pub fn name(&self) -> String { diff --git a/src/command.rs b/src/command.rs index 35ce2a522b..915e16c692 100644 --- a/src/command.rs +++ b/src/command.rs @@ -16,7 +16,7 @@ pub enum Command<'a> { ListBucket { prefix: &'a str, delimiter: Option<&'a str>, - continuation_token: Option<&'a str> + continuation_token: Option }, GetBucketLocation } diff --git a/src/request.rs b/src/request.rs index 05464c89cb..b724375c66 100644 --- a/src/request.rs +++ b/src/request.rs @@ -108,13 +108,13 @@ impl<'a> Request<'a> { url.query_pairs_mut().append_pair(key, value); } - if let Command::ListBucket { prefix, delimiter, continuation_token } = self.command { + if let Command::ListBucket { prefix, delimiter, continuation_token } = &self.command { let mut query_pairs = url.query_pairs_mut(); delimiter.map(|d| query_pairs.append_pair("delimiter", d)); query_pairs.append_pair("prefix", prefix); query_pairs.append_pair("list-type", "2"); if let Some(token) = continuation_token { - query_pairs.append_pair("continuation-token", token); + query_pairs.append_pair("continuation-token", &token); } } From 596136a1df796c2576b85a24dca0444be7d5912e Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Fri, 6 Sep 2019 17:17:43 +0200 Subject: [PATCH 2/8] Mock and Error type to make async Send-able --- Cargo.toml | 2 +- src/bucket.rs | 9 +++++---- src/request.rs | 32 +++++++++++++++++++++++++++----- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4d8aad26e2..a60207aeb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-s3" -version = "0.18.0-beta" +version = "0.18.0-beta1" authors = ["Drazen Urch", "Nick Stevens"] description = "Tiny Rust library for working with Amazon S3." repository = "https://github.com/durch/rust-s3" diff --git a/src/bucket.rs b/src/bucket.rs index 5e3c19908a..59a9289828 100644 --- a/src/bucket.rs +++ b/src/bucket.rs @@ -7,7 +7,7 @@ use std::io::Write; use credentials::Credentials; use command::Command; use region::Region; -use request::{Request, Headers, Query}; +use request::{Request, Headers, Query, S3Error}; use serde_types::{ListBucketResult, BucketLocationResult}; use serde_types::Tagging; use futures::Future; @@ -25,6 +25,7 @@ pub enum Error { ParseError { source: ::region::Error }, + ResponseError } type S3Result = std::result::Result; @@ -407,7 +408,7 @@ impl Bucket { prefix: &str, delimiter: Option<&str>, continuation_token: Option) - -> impl Future { + -> impl Future + Send { let command = Command::ListBucket { prefix, delimiter, @@ -488,7 +489,7 @@ impl Bucket { /// Ok(()) /// }); /// ``` - pub fn list_all_async<'a>(&'a self, prefix: &'a str, delimiter: Option<&'a str>) -> impl Future> + 'a { + pub fn list_all_async<'a>(&'a self, prefix: &'a str, delimiter: Option<&'a str>) -> impl Future, Error=S3Error> + 'a + Send { let list_entire_bucket = loop_fn((None, Vec::new()), move |(continuation_token, results): (Option, Vec)| { let mut inner_results = results; self.list_page_async(prefix, delimiter, continuation_token).and_then(|(result, _status_code)| { @@ -499,7 +500,7 @@ impl Bucket { } }) }); - list_entire_bucket.and_then(|(_token, results): (Option<&str>, Vec)| Ok(results)) + list_entire_bucket.and_then(|(_token, results): (Option<&str>, Vec)| Ok(results)).map_err(|_| S3Error {}) } diff --git a/src/request.rs b/src/request.rs index b724375c66..f6f5d84e73 100644 --- a/src/request.rs +++ b/src/request.rs @@ -24,6 +24,27 @@ use signing; use EMPTY_PAYLOAD_SHA; use LONG_DATE; use reqwest::async::Response; +use core::fmt; +use std::error; + +#[derive(Debug, Default)] +pub struct S3Error {} + +impl fmt::Display for S3Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "S3Error!") + } +} + +impl error::Error for S3Error { + fn description(&self) -> &str { + "Description for ErrorB" + } + + fn cause(&self) -> Option<&error::Error> { + None + } +} #[derive(Debug, Snafu)] pub enum Error { @@ -34,6 +55,7 @@ pub enum Error { source: reqwest::header::InvalidHeaderValue }, ReqwestFuture, + ResponseError, ParseError { source: std::string::ParseError } @@ -265,7 +287,7 @@ impl<'a> Request<'a> { } } - pub fn response_future(&self) -> impl Future { + pub fn response_future(&self) -> impl Future { let client = if cfg!(feature = "no-verify-ssl") { async::Client::builder() .danger_accept_invalid_certs(true) @@ -292,7 +314,7 @@ impl<'a> Request<'a> { .headers(headers.to_owned()) .body(content.to_owned()); - request.send() + request.send().map_err(|_| S3Error {}) } // pub fn response_data_future2(&self) -> impl Future>, u16)>> { @@ -304,11 +326,11 @@ impl<'a> Request<'a> { // }) // } - pub fn response_data_future(&self) -> impl Future, u16)> { + pub fn response_data_future(&self) -> impl Future, u16), Error=S3Error> { self.response_future() - .and_then(|mut response| Ok((response.text(), response.status().as_u16()))) + .and_then(|mut response| Ok((response.text(), response.status().as_u16()))).map_err(|_| S3Error {}) .and_then(|(body_future, status_code)| { - body_future.and_then(move |body| Ok((body.as_bytes().to_vec(), status_code))) + body_future.and_then(move |body| Ok((body.as_bytes().to_vec(), status_code))).map_err(|_| S3Error {}) }) } From fd51b70a20f1112fb83f757b8ef5641b1a3f938f Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Sun, 8 Sep 2019 22:21:32 +0200 Subject: [PATCH 3/8] Fudge around with lifetimes --- Cargo.toml | 2 +- src/bucket.rs | 18 +++++++++--------- src/command.rs | 5 +++-- src/request.rs | 29 ++++------------------------- 4 files changed, 17 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a60207aeb4..14e9179c1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-s3" -version = "0.18.0-beta1" +version = "0.18.0-beta2" authors = ["Drazen Urch", "Nick Stevens"] description = "Tiny Rust library for working with Amazon S3." repository = "https://github.com/durch/rust-s3" diff --git a/src/bucket.rs b/src/bucket.rs index 59a9289828..fc1fdad963 100644 --- a/src/bucket.rs +++ b/src/bucket.rs @@ -388,8 +388,8 @@ impl Bucket { } fn list_page(&self, - prefix: &str, - delimiter: Option<&str>, + prefix: String, + delimiter: Option, continuation_token: Option) -> S3Result<(ListBucketResult, u16)> { let command = Command::ListBucket { @@ -405,8 +405,8 @@ impl Bucket { } pub fn list_page_async(&self, - prefix: &str, - delimiter: Option<&str>, + prefix: String, + delimiter: Option, continuation_token: Option) -> impl Future + Send { let command = Command::ListBucket { @@ -450,13 +450,13 @@ impl Bucket { /// println!("{:?}", list); /// } /// ``` - pub fn list_all(&self, prefix: &str, delimiter: Option<&str>) -> S3Result> { + pub fn list_all(&self, prefix: String, delimiter: Option) -> S3Result> { let mut results = Vec::new(); - let mut result = self.list_page(prefix, delimiter, None)?; + let mut result = self.list_page(prefix.clone(), delimiter.clone(), None)?; loop { results.push(result.clone()); match result.0.next_continuation_token { - Some(token) => result = self.list_page(prefix, delimiter, Some(token))?, + Some(token) => result = self.list_page(prefix.clone(), delimiter.clone(), Some(token))?, None => break, } } @@ -489,10 +489,10 @@ impl Bucket { /// Ok(()) /// }); /// ``` - pub fn list_all_async<'a>(&'a self, prefix: &'a str, delimiter: Option<&'a str>) -> impl Future, Error=S3Error> + 'a + Send { + pub fn list_all_async(&self, prefix: String, delimiter: Option) -> impl Future, Error=S3Error> + Send + '_ { let list_entire_bucket = loop_fn((None, Vec::new()), move |(continuation_token, results): (Option, Vec)| { let mut inner_results = results; - self.list_page_async(prefix, delimiter, continuation_token).and_then(|(result, _status_code)| { + self.list_page_async(prefix.clone(), delimiter.clone(), continuation_token).and_then(|(result, _status_code)| { inner_results.push(result.clone()); match result.next_continuation_token { Some(token) => Ok(Loop::Continue((Some(token), inner_results))), diff --git a/src/command.rs b/src/command.rs index 915e16c692..50b1e55cca 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,5 +1,6 @@ use reqwest::Method; +#[derive(Clone)] pub enum Command<'a> { DeleteObject, DeleteObjectTagging, @@ -14,8 +15,8 @@ pub enum Command<'a> { }, ListBucket { - prefix: &'a str, - delimiter: Option<&'a str>, + prefix: String, + delimiter: Option, continuation_token: Option }, GetBucketLocation diff --git a/src/request.rs b/src/request.rs index f6f5d84e73..6afeb9ab00 100644 --- a/src/request.rs +++ b/src/request.rs @@ -38,7 +38,7 @@ impl fmt::Display for S3Error { impl error::Error for S3Error { fn description(&self) -> &str { - "Description for ErrorB" + "Description for S3Error" } fn cause(&self) -> Option<&error::Error> { @@ -130,10 +130,10 @@ impl<'a> Request<'a> { url.query_pairs_mut().append_pair(key, value); } - if let Command::ListBucket { prefix, delimiter, continuation_token } = &self.command { + if let Command::ListBucket { prefix, delimiter, continuation_token } = self.command.clone() { let mut query_pairs = url.query_pairs_mut(); - delimiter.map(|d| query_pairs.append_pair("delimiter", d)); - query_pairs.append_pair("prefix", prefix); + delimiter.map(|d| query_pairs.append_pair("delimiter", &d.clone())); + query_pairs.append_pair("prefix", &prefix); query_pairs.append_pair("list-type", "2"); if let Some(token) = continuation_token { query_pairs.append_pair("continuation-token", &token); @@ -317,15 +317,6 @@ impl<'a> Request<'a> { request.send().map_err(|_| S3Error {}) } -// pub fn response_data_future2(&self) -> impl Future>, u16)>> { -// let response_future = self.response_future(); -// response_future.map(|mut response| { -// let response_code = response.status().as_u16(); -// let response_data = response.text().map(|body| body.as_bytes().to_vec()); -// Ok((response_data, response_code)) -// }) -// } - pub fn response_data_future(&self) -> impl Future, u16), Error=S3Error> { self.response_future() .and_then(|mut response| Ok((response.text(), response.status().as_u16()))).map_err(|_| S3Error {}) @@ -342,18 +333,6 @@ impl<'a> Request<'a> { }) } -// pub fn response_data_to_writer_future2<'b, T: Write>(&self, writer: &'b mut T) -> impl Future> + 'b, u16)>> + 'b { -// let future_response = self.response_data_future(); -// future_response.map(|response| { -// match response { -// Ok(success) => { -// let to_writer_future = success.0.map(move |body| Ok(writer.write_all(body.as_slice())?)); -// Ok((to_writer_future, success.1)) -// } -// Err(e) => Err(e) -// } -// }) -// } } #[cfg(test)] From aed7b054082e079bba97814bf6f24c10125e8cd1 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 9 Sep 2019 17:27:03 +0200 Subject: [PATCH 4/8] Fix tests --- src/bucket.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bucket.rs b/src/bucket.rs index fc1fdad963..ce1c08fbde 100644 --- a/src/bucket.rs +++ b/src/bucket.rs @@ -444,7 +444,7 @@ impl Bucket { /// let credentials = Credentials::default(); /// let bucket = Bucket::new(bucket_name, region, credentials).unwrap(); /// - /// let results = bucket.list_all("/", Some("/")).unwrap(); + /// let results = bucket.list_all("/".to_string(), Some("/".to_string())).unwrap(); /// for (list, code) in results { /// assert_eq!(200, code); /// println!("{:?}", list); @@ -484,7 +484,7 @@ impl Bucket { /// let credentials = Credentials::default(); /// let bucket = Bucket::new(bucket_name, region, credentials).unwrap(); /// - /// let results = bucket.list_all_async("/", Some("/")).and_then(|list| { + /// let results = bucket.list_all_async("/".to_string(), Some("/".to_string())).and_then(|list| { /// println!("{:?}", list); /// Ok(()) /// }); From cca4bebfeb21daae060daf8f2e2144c79abdd471 Mon Sep 17 00:00:00 2001 From: Stefan Seemayer Date: Fri, 13 Sep 2019 21:37:59 +0200 Subject: [PATCH 5/8] Remove lifetime im list_all_async --- src/bucket.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bucket.rs b/src/bucket.rs index ce1c08fbde..4a6a2b821e 100644 --- a/src/bucket.rs +++ b/src/bucket.rs @@ -489,10 +489,11 @@ impl Bucket { /// Ok(()) /// }); /// ``` - pub fn list_all_async(&self, prefix: String, delimiter: Option) -> impl Future, Error=S3Error> + Send + '_ { + pub fn list_all_async(&self, prefix: String, delimiter: Option) -> impl Future, Error=S3Error> + Send { + let the_bucket = self.to_owned(); let list_entire_bucket = loop_fn((None, Vec::new()), move |(continuation_token, results): (Option, Vec)| { let mut inner_results = results; - self.list_page_async(prefix.clone(), delimiter.clone(), continuation_token).and_then(|(result, _status_code)| { + the_bucket.list_page_async(prefix.clone(), delimiter.clone(), continuation_token).and_then(|(result, _status_code)| { inner_results.push(result.clone()); match result.next_continuation_token { Some(token) => Ok(Loop::Continue((Some(token), inner_results))), From b320ef4601243da396bb909b073e0af240e1beb0 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 16 Sep 2019 21:16:11 +0200 Subject: [PATCH 6/8] Bump version, lint --- Cargo.toml | 2 +- src/signing.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 14e9179c1f..366267ad41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-s3" -version = "0.18.0-beta2" +version = "0.18.0-beta3" authors = ["Drazen Urch", "Nick Stevens"] description = "Tiny Rust library for working with Amazon S3." repository = "https://github.com/durch/rust-s3" diff --git a/src/signing.rs b/src/signing.rs index 10949409db..28ce40154f 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -20,7 +20,7 @@ pub fn uri_encode(string: &str, encode_slash: bool) -> String { let mut result = String::with_capacity(string.len() * 2); for c in string.chars() { match c { - 'a'...'z' | 'A'...'Z' | '0'...'9' | '_' | '-' | '~' | '.' => result.push(c), + 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '~' | '.' => result.push(c), '/' if encode_slash => result.push_str("%2F"), '/' if !encode_slash => result.push('/'), _ => { From 5ad203b056e056590ed0d2cbd446d665243d6144 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Wed, 2 Oct 2019 15:01:51 +0200 Subject: [PATCH 7/8] Fix error handling --- Cargo.toml | 3 +- src/bin/simple_crud.rs | 33 ++++--------- src/bucket.rs | 51 +++++++------------- src/credentials.rs | 41 ++++------------ src/error.rs | 49 +++++++++++++++++++ src/lib.rs | 3 +- src/region.rs | 21 ++------ src/request.rs | 106 +++++++++++------------------------------ 8 files changed, 119 insertions(+), 188 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.toml b/Cargo.toml index 366267ad41..72f6ef3143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-s3" -version = "0.18.0-beta3" +version = "0.18.0-beta4" authors = ["Drazen Urch", "Nick Stevens"] description = "Tiny Rust library for working with Amazon S3." repository = "https://github.com/durch/rust-s3" @@ -28,7 +28,6 @@ url = "^1.5.1" rust-ini = "^0.9" dirs = "^1.0" futures = "^0.1.28" -snafu = {version = "^0.5", features = ["futures-01"]} [features] no-verify-ssl = [] diff --git a/src/bin/simple_crud.rs b/src/bin/simple_crud.rs index d39808682c..ff0f3252e2 100644 --- a/src/bin/simple_crud.rs +++ b/src/bin/simple_crud.rs @@ -1,35 +1,20 @@ extern crate s3; -extern crate snafu; - -use snafu::{ResultExt, Snafu}; use std::str; use s3::bucket::Bucket; use s3::credentials::Credentials; +use s3::error::S3Error; const BUCKET: &str = "drazen-test-bucket-2"; const MESSAGE: &str = "I want to go to S3"; const REGION: &str = "us-east-1"; -#[derive(Debug, Snafu)] -pub enum Error { - InvalidRegion { source: s3::region::Error }, - BucketCreate { source: s3::bucket::Error }, - BucketPut { source: s3::bucket::Error }, - BucketGet { source: s3::bucket::Error }, - BucketPutTag { source: s3::bucket::Error }, - BucketGetTag { source: s3::bucket::Error }, - BucketLocation { source: s3::bucket::Error }, -} - -type S3Result = std::result::Result; - -pub fn main() -> S3Result<()> { - let region = REGION.parse().context(InvalidRegion)?; +pub fn main() -> Result<(), S3Error> { + let region = REGION.parse()?; // Create Bucket in REGION for BUCKET let credentials = Credentials::default(); - let bucket = Bucket::new(BUCKET, region, credentials).context(BucketCreate)?; + let bucket = Bucket::new(BUCKET, region, credentials)?; // List out contents of directory // let results = bucket.list("", None).unwrap(); @@ -48,22 +33,22 @@ pub fn main() -> S3Result<()> { // Put a "test_file" with the contents of MESSAGE at the root of the // bucket. - let (_, code) = bucket.put_object("test_file", MESSAGE.as_bytes(), "text/plain").context(BucketPut)?; + let (_, code) = bucket.put_object("test_file", MESSAGE.as_bytes(), "text/plain")?; assert_eq!(200, code); // Get the "test_file" contents and make sure that the returned message // matches what we sent. - let (data, code) = bucket.get_object("test_file").context(BucketGet)?; + let (data, code) = bucket.get_object("test_file")?; let string = str::from_utf8(&data).unwrap(); assert_eq!(200, code); assert_eq!(MESSAGE, string); // Get bucket location - println!("{:?}", bucket.location().context(BucketLocation)?); + println!("{:?}", bucket.location()?); - bucket.put_object_tagging("test_file", &[("test", "tag")]).context(BucketPutTag)?; + bucket.put_object_tagging("test_file", &[("test", "tag")])?; println!("Tags set"); - let (tags, _status) = bucket.get_object_tagging("test_file").context(BucketGetTag)?; + let (tags, _status) = bucket.get_object_tagging("test_file")?; println!("{:?}", tags); Ok(()) diff --git a/src/bucket.rs b/src/bucket.rs index 4a6a2b821e..b886edc703 100644 --- a/src/bucket.rs +++ b/src/bucket.rs @@ -7,28 +7,13 @@ use std::io::Write; use credentials::Credentials; use command::Command; use region::Region; -use request::{Request, Headers, Query, S3Error}; +use request::{Request, Headers, Query}; use serde_types::{ListBucketResult, BucketLocationResult}; use serde_types::Tagging; use futures::Future; -use futures::future::{loop_fn, Loop, ok, result, LoopFn}; -use snafu::{ResultExt, Snafu}; - -#[derive(Debug, Snafu)] -pub enum Error { - XmlError { - source: serde_xml::Error - }, - RequestError { - source: ::request::Error - }, - ParseError { - source: ::region::Error - }, - ResponseError -} +use futures::future::{loop_fn, Loop}; +use error::{S3Error, S3Result}; -type S3Result = std::result::Result; /// # Example /// ``` @@ -101,7 +86,7 @@ impl Bucket { pub fn get_object(&self, path: &str) -> S3Result<(Vec, u16)> { let command = Command::GetObject; let request = Request::new(self, path, command); - Ok(request.response_data().context(RequestError)?) + Ok(request.response_data()?) } /// Gets file from an S3 path. @@ -153,7 +138,7 @@ impl Bucket { pub fn get_object_stream(&self, path: &str, writer: &mut T) -> S3Result { let command = Command::GetObject; let request = Request::new(self, path, command); - Ok(request.response_data_to_writer(writer).context(RequestError)?) + Ok(request.response_data_to_writer(writer)?) } /// Stream file from S3 path to a local file, generic over T: Write. @@ -206,18 +191,18 @@ impl Bucket { /// ``` pub fn location(&self) -> S3Result<(Region, u16)> { let request = Request::new(self, "?location", Command::GetBucketLocation); - let result = request.response_data().context(RequestError)?; + let result = request.response_data()?; let region_string = String::from_utf8_lossy(&result.0); let region = match serde_xml::deserialize(region_string.as_bytes()) { Ok(r) => { let location_result: BucketLocationResult = r; - location_result.region.parse().context(ParseError)? + location_result.region.parse()? } Err(e) => { if e.to_string() == "missing field `$value`" { - "us-east-1".parse().context(ParseError)? + "us-east-1".parse()? } else { - panic!("lala") + panic!("How did we get here?") } } }; @@ -243,7 +228,7 @@ impl Bucket { pub fn delete_object(&self, path: &str) -> S3Result<(Vec, u16)> { let command = Command::DeleteObject; let request = Request::new(self, path, command); - request.response_data().context(RequestError) + request.response_data() } /// Put into an S3 bucket. @@ -273,7 +258,7 @@ impl Bucket { content_type, }; let request = Request::new(self, path, command); - Ok(request.response_data().context(RequestError)?) + Ok(request.response_data()?) } fn _tags_xml(&self, tags: &[(&str, &str)]) -> String { @@ -317,7 +302,7 @@ impl Bucket { tags: &content.to_string() }; let request = Request::new(self, path, command); - Ok(request.response_data().context(RequestError)?) + Ok(request.response_data()?) } @@ -344,7 +329,7 @@ impl Bucket { pub fn delete_object_tagging(&self, path: &str) -> S3Result<(Vec, u16)> { let command = Command::DeleteObjectTagging; let request = Request::new(self, path, command); - Ok(request.response_data().context(RequestError)?) + Ok(request.response_data()?) } /// Retrieve an S3 object list of tags. @@ -374,12 +359,12 @@ impl Bucket { pub fn get_object_tagging(&self, path: &str) -> S3Result<(Option, u16)> { let command = Command::GetObjectTagging {}; let request = Request::new(self, path, command); - let result = request.response_data().context(RequestError)?; + let result = request.response_data()?; let tagging = if result.1 == 200 { let result_string = String::from_utf8_lossy(&result.0); println!("{}", result_string); - Some(serde_xml::deserialize(result_string.as_bytes()).context(XmlError)?) + Some(serde_xml::deserialize(result_string.as_bytes())?) } else { None }; @@ -398,9 +383,9 @@ impl Bucket { continuation_token, }; let request = Request::new(self, "/", command); - let result = request.response_data().context(RequestError)?; + let result = request.response_data()?; let result_string = String::from_utf8_lossy(&result.0); - let deserialized: ListBucketResult = serde_xml::deserialize(result_string.as_bytes()).context(XmlError)?; + let deserialized: ListBucketResult = serde_xml::deserialize(result_string.as_bytes())?; Ok((deserialized, result.1)) } @@ -501,7 +486,7 @@ impl Bucket { } }) }); - list_entire_bucket.and_then(|(_token, results): (Option<&str>, Vec)| Ok(results)).map_err(|_| S3Error {}) + list_entire_bucket.and_then(|(_token, results): (Option<&str>, Vec)| Ok(results)).map_err(|_| S3Error { src: None }) } diff --git a/src/credentials.rs b/src/credentials.rs index c8f1ffe614..16f118079b 100644 --- a/src/credentials.rs +++ b/src/credentials.rs @@ -1,30 +1,7 @@ use std::env; use dirs; use ini::Ini; -use snafu::{ResultExt, Snafu}; - -#[derive(Debug, Snafu)] -pub enum Error { - VarError { - source: std::env::VarError - }, - HomeDir, - IniError { - source: ini::ini::Error - }, - #[snafu(display("Section [{}] not found in {}", section, profile))] - AwsMissingSection { - section: String, - profile: String - }, - #[snafu(display("Missing {} in {}", part, profile))] - AwsConfigError { - part: String, - profile: String - } -} - -type S3Result = std::result::Result; +use error::{err, S3Result}; /// AWS access credentials: access key, secret key, and optional token. /// @@ -125,8 +102,8 @@ impl Credentials { } fn from_env() -> S3Result { - let access_key = env::var("AWS_ACCESS_KEY_ID").context(VarError)?; - let secret_key = env::var("AWS_SECRET_ACCESS_KEY").context(VarError)?; + let access_key = env::var("AWS_ACCESS_KEY_ID")?; + let secret_key = env::var("AWS_SECRET_ACCESS_KEY")?; let token = match env::var("AWS_SESSION_TOKEN") { Ok(x) => Some(x), Err(_) => None @@ -137,29 +114,29 @@ impl Credentials { fn from_profile(section: Option) -> S3Result { let home_dir = match dirs::home_dir() { Some(path) => Ok(path), - None => Err(Error::HomeDir), + None => Err(err("Invalid home dir")), }; let profile = format!("{}/.aws/credentials", home_dir?.display()); - let conf = Ini::load_from_file(&profile).context(IniError)?; + let conf = Ini::load_from_file(&profile)?; let section = match section { Some(s) => s, None => String::from("default") }; let data1 = match conf.section(Some(section.clone())) { Some(d) => Ok(d), - None => Err(Error::AwsMissingSection {section: section.clone(), profile: profile.clone()}) + None => Err(err("Missing aws section")) }; let data2 = match conf.section(Some(section.clone())) { Some(d) => Ok(d), - None => Err(Error::AwsMissingSection {section, profile: profile.clone()}) + None => Err(err("Missing aws section")) }; let access_key = match data1?.get("aws_access_key_id") { Some(x) => Ok(x.to_owned()), - None => Err(Error::AwsConfigError {part: "aws_access_key_id".to_string(), profile: profile.clone()}) + None => Err(err("Missing aws section")) }; let secret_key = match data2?.get("aws_secret_access_key") { Some(x) => Ok(x.to_owned()), - None => Err(Error::AwsConfigError {part: "aws_secret_access_key".to_string(), profile}) + None => Err(err("Missing aws section")) }; Ok(Credentials { access_key: access_key?.to_owned(), secret_key: secret_key?.to_owned(), token: None, _private: () }) } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000000..f4cae74c5b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,49 @@ +use std; +use serde_xml; +use reqwest; +use core::fmt; +use std::error::Error; + +macro_rules! impl_from { + ($t: ty) => { + impl From<$t> for S3Error { + fn from(e: $t) -> S3Error { + S3Error { src: Some(String::from(format!("{}",e))) } + } + } + } +} + +impl_from!(serde_xml::Error); +impl_from!(reqwest::Error); +impl_from!(reqwest::header::InvalidHeaderName); +impl_from!(reqwest::header::InvalidHeaderValue); +impl_from!(std::env::VarError); +impl_from!(ini::ini::Error); + +#[derive(Debug)] +pub struct S3Error { + pub src: Option +} + +pub fn err(e: &str) -> S3Error { + S3Error {src: Some(String::from(e))} +} + +impl fmt::Display for S3Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.src.as_ref() { + Some(err) => write!(f, "{}", err), + None => write!(f, "An unknown error has occured!") + } + + } +} + +impl Error for S3Error { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } +} + +pub type S3Result = std::result::Result; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6e1708f1a6..53fcde28c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,6 @@ extern crate url; extern crate ini; extern crate dirs; extern crate futures; -extern crate snafu; extern crate core; pub mod bucket; @@ -23,6 +22,8 @@ pub mod request; pub mod serde_types; pub mod signing; pub mod deserializer; +#[macro_use] +pub mod error; const LONG_DATE: &str = "%Y%m%dT%H%M%SZ"; const EMPTY_PAYLOAD_SHA: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; diff --git a/src/region.rs b/src/region.rs index f3f0d3b8f0..d9b32d733b 100644 --- a/src/region.rs +++ b/src/region.rs @@ -1,19 +1,6 @@ use std::fmt; use std::str::{self, FromStr}; -use snafu::Snafu; - -#[derive(Debug, Snafu)] -pub enum Error { - XmlError, - RequestError { - source: ::request::Error - }, - ParseError { - source: std::string::ParseError - } -} - -type S3Result = std::result::Result; +use error::{S3Result, S3Error}; /// AWS S3 [region identifier](https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region), /// passing in custom values is also possible, in that case it is up to you to pass a valid endpoint, @@ -115,7 +102,7 @@ impl fmt::Display for Region { } impl FromStr for Region { - type Err = Error; + type Err = S3Error; fn from_str(s: &str) -> S3Result { use self::Region::*; @@ -179,7 +166,7 @@ impl Region { pub fn scheme(&self) -> String { match *self { - Region::Custom{ region: _, ref endpoint } => { + Region::Custom{ ref endpoint, ..} => { match endpoint.find("://") { Some(pos) => endpoint[..pos].to_string(), None => "https".to_string() @@ -191,7 +178,7 @@ impl Region { pub fn host(&self) -> String { match *self { - Region::Custom{ region: _, ref endpoint } => { + Region::Custom{ ref endpoint, ..} => { match endpoint.find("://") { Some(pos) => endpoint[pos + 3..].to_string(), None => endpoint.to_string() diff --git a/src/request.rs b/src/request.rs index 6afeb9ab00..244559316a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,8 +1,6 @@ extern crate base64; extern crate md5; -use snafu::{ResultExt, Snafu}; - use std::collections::HashMap; use std::io::{Read, Write}; @@ -18,61 +16,13 @@ use url::Url; use futures::prelude::*; -use serde_types::AwsError; +//use serde_types::AwsError; use signing; use EMPTY_PAYLOAD_SHA; use LONG_DATE; use reqwest::async::Response; -use core::fmt; -use std::error; - -#[derive(Debug, Default)] -pub struct S3Error {} - -impl fmt::Display for S3Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "S3Error!") - } -} - -impl error::Error for S3Error { - fn description(&self) -> &str { - "Description for S3Error" - } - - fn cause(&self) -> Option<&error::Error> { - None - } -} - -#[derive(Debug, Snafu)] -pub enum Error { - InvalidHeaderName { - source: reqwest::header::InvalidHeaderName - }, - InvalidHeaderValue { - source: reqwest::header::InvalidHeaderValue - }, - ReqwestFuture, - ResponseError, - ParseError { - source: std::string::ParseError - } - -} - -type S3Result = std::result::Result; - -#[derive(Debug, Snafu)] -enum InternalError { - BucketError { - source: ::bucket::Error - }, - RegionError { - source: ::region::Error - } -} +use error::{S3Error, S3Result, err}; /// Collection of HTTP headers sent to S3 service, in key/value format. pub type Headers = HashMap; @@ -234,33 +184,33 @@ impl<'a> Request<'a> { .bucket .extra_headers .iter() - .map(|(k, v)| Ok((k.parse::().context(InvalidHeaderName)?, v.parse::().context(InvalidHeaderValue)?))) - .collect::>()?; + .map(|(k, v)| Ok((k.parse::()?, v.parse::()?))) + .collect::>()?; match self.command { - Command::GetBucketLocation => headers.insert(header::HOST, self.bucket.self_host().parse().context(InvalidHeaderValue)?), - _ => headers.insert(header::HOST, self.bucket.host().parse().context(InvalidHeaderValue)?) + Command::GetBucketLocation => headers.insert(header::HOST, self.bucket.self_host().parse()?), + _ => headers.insert(header::HOST, self.bucket.host().parse()?) }; headers.insert( header::CONTENT_LENGTH, - self.content_length().to_string().parse().context(InvalidHeaderValue)?, + self.content_length().to_string().parse()?, ); - headers.insert(header::CONTENT_TYPE, self.content_type().parse().context(InvalidHeaderValue)?); - headers.insert("X-Amz-Content-Sha256", sha256.parse().context(InvalidHeaderValue)?); - headers.insert("X-Amz-Date", self.long_date().parse().context(InvalidHeaderValue)?); + headers.insert(header::CONTENT_TYPE, self.content_type().parse()?); + headers.insert("X-Amz-Content-Sha256", sha256.parse()?); + headers.insert("X-Amz-Date", self.long_date().parse()?); if let Some(token) = self.bucket.credentials().token.as_ref() { - headers.insert("X-Amz-Security-Token", token.parse().context(InvalidHeaderValue)?); + headers.insert("X-Amz-Security-Token", token.parse()?); } if let Command::PutObjectTagging { tags } = self.command { let digest = md5::compute(tags); let hash = base64::encode(digest.as_ref()); - headers.insert("Content-MD5", hash.parse().context(InvalidHeaderValue)?); + headers.insert("Content-MD5", hash.parse()?); } // This must be last, as it signs the other headers let authorization = self.authorization(&headers); - headers.insert(header::AUTHORIZATION, authorization.parse().context(InvalidHeaderValue)?); + headers.insert(header::AUTHORIZATION, authorization.parse()?); // The format of RFC2822 is somewhat malleable, so including it in // signed headers can cause signature mismatches. We do include the @@ -268,7 +218,7 @@ impl<'a> Request<'a> { // range and can't be used again e.g. reply attacks. Adding this header // after the generation of the Authorization header leaves it out of // the signed headers. - headers.insert(header::DATE, self.datetime.to_rfc2822().parse().context(InvalidHeaderValue)?); + headers.insert(header::DATE, self.datetime.to_rfc2822().parse()?); Ok(headers) } @@ -276,14 +226,14 @@ impl<'a> Request<'a> { pub fn response_data(&self) -> S3Result<(Vec, u16)> { match self.response_data_future().wait() { Ok((response_data, status_code)) => Ok((response_data, status_code)), - Err(_) => Err(Error::ReqwestFuture) + Err(_) => Err(S3Error { src: Some("Error getting response".to_string())}) } } pub fn response_data_to_writer(&self, writer: &mut T) -> S3Result { match self.response_data_to_writer_future(writer).wait() { Ok(status_code) => Ok(status_code), - Err(_) => Err(Error::ReqwestFuture) + Err(_) => Err(err("ReqwestFuture")) } } @@ -314,14 +264,14 @@ impl<'a> Request<'a> { .headers(headers.to_owned()) .body(content.to_owned()); - request.send().map_err(|_| S3Error {}) + request.send().map_err(|_| S3Error {src: None}) } pub fn response_data_future(&self) -> impl Future, u16), Error=S3Error> { self.response_future() - .and_then(|mut response| Ok((response.text(), response.status().as_u16()))).map_err(|_| S3Error {}) + .and_then(|mut response| Ok((response.text(), response.status().as_u16()))).map_err(|_| S3Error {src: None}) .and_then(|(body_future, status_code)| { - body_future.and_then(move |body| Ok((body.as_bytes().to_vec(), status_code))).map_err(|_| S3Error {}) + body_future.and_then(move |body| Ok((body.as_bytes().to_vec(), status_code))).map_err(|_| S3Error {src: None}) }) } @@ -340,11 +290,9 @@ mod tests { use bucket::Bucket; use command::Command; use credentials::Credentials; - use request::{Request, InternalError}; - use request::S3Result; - use snafu::ResultExt; + use request::{Request}; use url::form_urlencoded::Parse; - use request::{ParseError, BucketError, RegionError}; + use error::S3Result; // Fake keys - otherwise using Credentials::default will use actual user // credentials if they exist. @@ -355,9 +303,9 @@ mod tests { } #[test] - fn url_uses_https_by_default() -> Result<(), InternalError> { - let region = "custom-region".parse().context(RegionError)?; - let bucket = Bucket::new("my-first-bucket", region, fake_credentials()).context(BucketError)?; + fn url_uses_https_by_default() -> S3Result<()> { + let region = "custom-region".parse()?; + let bucket = Bucket::new("my-first-bucket", region, fake_credentials())?; let path = "/my-first/path"; let request = Request::new(&bucket, path, Command::GetObject); @@ -371,9 +319,9 @@ mod tests { } #[test] - fn url_uses_scheme_from_custom_region_if_defined() -> Result<(), InternalError> { - let region = "http://custom-region".parse().context(RegionError)?; - let bucket = Bucket::new("my-second-bucket", region, fake_credentials()).context(BucketError)?; + fn url_uses_scheme_from_custom_region_if_defined() -> S3Result<()> { + let region = "http://custom-region".parse()?; + let bucket = Bucket::new("my-second-bucket", region, fake_credentials())?; let path = "/my-second/path"; let request = Request::new(&bucket, path, Command::GetObject); From fb37a88eda3de1348801ae4fced78af545988ed2 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Thu, 17 Oct 2019 11:15:57 +0200 Subject: [PATCH 8/8] Update dependancies --- Cargo.toml | 22 +++++++++++----------- README.md | 13 +++++++------ src/bucket.rs | 8 ++++---- src/error.rs | 1 + src/request.rs | 28 +++++++++++++--------------- src/signing.rs | 29 +++++++++++++++-------------- 6 files changed, 51 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 72f6ef3143..05633304d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rust-s3" -version = "0.18.0-beta4" +version = "0.18.0" authors = ["Drazen Urch", "Nick Stevens"] -description = "Tiny Rust library for working with Amazon S3." +description = "Tiny Rust library for working with Amazon S3 and compatible APIs" repository = "https://github.com/durch/rust-s3" readme = "README.md" keywords = ["Amazon", "AWS", "S3"] @@ -16,17 +16,17 @@ path = "src/lib.rs" [dependencies] base64 = "0.10.1" chrono = "^0.4.0" -hex = "^0.2.0" -hmac = "^0.4.2" +hex = "^0.4.0" +hmac = "^0.7.1" reqwest = "^0.9.1" -serde_derive = "^1.0.11" -serde = "^1.0.11" -serde-xml-rs = "^0.2.1" -sha2 = "^0.6.0" +serde_derive = "^1.0.101" +serde = "^1.0.101" +serde-xml-rs = "^0.3.1" +sha2 = "^0.8.0" md5 = "^0.6.1" -url = "^1.5.1" -rust-ini = "^0.9" -dirs = "^1.0" +url = "^2.1.0" +rust-ini = "^0.13" +dirs = "^2.0.2" futures = "^0.1.28" [features] diff --git a/README.md b/README.md index 05959b5d4e..6a59422798 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,19 @@ [![Join the chat at https://gitter.im/durch/rust-s3](https://badges.gitter.im/durch/rust-s3.svg)](https://gitter.im/durch/rust-s3?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## rust-s3 [[docs](https://durch.github.io/rust-s3/)] -Tiny Rust library for working with Amazon S3 or arbitrary S3 compatible APIs +Tiny Rust library for working with Amazon S3 or arbitrary S3 compatible APIs, fully compatible with *async* usage ### Intro -Very modest interface towards Amazon S3. -Supports `put`, `get`, `list`, and `delete`. +Very modest interface towards Amazon S3, as well as S3 compatible APIs. +Supports `put`, `get`, `list`, and `delete`, operations on `tags` and `location`. + +Supports streaming S3 contents generic over `T: Write` as of `0.15.0`. ### What is cool The main cool feature is that `put` commands return a presigned link to the file you uploaded. This means you can upload to s3, and give the link to select people without having to worry about publicly accessible files on S3. -Also supports streaming S3 contents generic over `T: Write` as of `0.15.0`. ### Configuration @@ -30,13 +31,13 @@ it is configured for one week which is the maximum Amazon allows ATM. ``` [dependencies] -rust-s3 = "0.15.0" +rust-s3 = "0.18.0" ``` #### Disable SSL verification for endpoints ``` [dependencies] -rust-s3 = {version = "0.15.0", features = ["no-verify-ssl"]} +rust-s3 = {version = "0.18.0", features = ["no-verify-ssl"]} ``` #### Example diff --git a/src/bucket.rs b/src/bucket.rs index b886edc703..e6e835df03 100644 --- a/src/bucket.rs +++ b/src/bucket.rs @@ -193,7 +193,7 @@ impl Bucket { let request = Request::new(self, "?location", Command::GetBucketLocation); let result = request.response_data()?; let region_string = String::from_utf8_lossy(&result.0); - let region = match serde_xml::deserialize(region_string.as_bytes()) { + let region = match serde_xml::from_reader(region_string.as_bytes()) { Ok(r) => { let location_result: BucketLocationResult = r; location_result.region.parse()? @@ -364,7 +364,7 @@ impl Bucket { let tagging = if result.1 == 200 { let result_string = String::from_utf8_lossy(&result.0); println!("{}", result_string); - Some(serde_xml::deserialize(result_string.as_bytes())?) + Some(serde_xml::from_reader(result_string.as_bytes())?) } else { None }; @@ -385,7 +385,7 @@ impl Bucket { let request = Request::new(self, "/", command); let result = request.response_data()?; let result_string = String::from_utf8_lossy(&result.0); - let deserialized: ListBucketResult = serde_xml::deserialize(result_string.as_bytes())?; + let deserialized: ListBucketResult = serde_xml::from_reader(result_string.as_bytes())?; Ok((deserialized, result.1)) } @@ -402,7 +402,7 @@ impl Bucket { let request = Request::new(self, "/", command); let result = request.response_data_future(); result.and_then(|(response, status_code)| - match serde_xml::deserialize(response.as_slice()) { + match serde_xml::from_reader(response.as_slice()) { Ok(list_bucket_result) => Ok((list_bucket_result, status_code)), Err(e) => { panic!("Could not deserialize list bucket result: {}", e); diff --git a/src/error.rs b/src/error.rs index f4cae74c5b..9f23df2432 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,7 @@ impl_from!(reqwest::header::InvalidHeaderName); impl_from!(reqwest::header::InvalidHeaderValue); impl_from!(std::env::VarError); impl_from!(ini::ini::Error); +impl_from!(hmac::crypto_mac::InvalidKeyLength); #[derive(Debug)] pub struct S3Error { diff --git a/src/request.rs b/src/request.rs index 244559316a..48c2dfd15b 100644 --- a/src/request.rs +++ b/src/request.rs @@ -7,11 +7,10 @@ use std::io::{Read, Write}; use bucket::Bucket; use chrono::{DateTime, Utc}; use command::Command; -use hmac::{Hmac, Mac}; +use hmac::Mac; use reqwest::async as async; use reqwest::header::{self, HeaderMap, HeaderName, HeaderValue}; use sha2::{Digest, Sha256}; -use hex::ToHex; use url::Url; use futures::prelude::*; @@ -121,12 +120,12 @@ impl<'a> Request<'a> { Command::PutObject { content, .. } => { let mut sha = Sha256::default(); sha.input(content); - sha.result().as_slice().to_hex() + hex::encode(sha.result().as_slice()) } Command::PutObjectTagging { tags } => { let mut sha = Sha256::default(); sha.input(tags.as_bytes()); - sha.result().as_slice().to_hex() + hex::encode(sha.result().as_slice()) } _ => EMPTY_PAYLOAD_SHA.into(), } @@ -149,29 +148,29 @@ impl<'a> Request<'a> { signing::string_to_sign(&self.datetime, &self.bucket.region(), request) } - fn signing_key(&self) -> Vec { - signing::signing_key( + fn signing_key(&self) -> S3Result> { + Ok(signing::signing_key( &self.datetime, &self.bucket.secret_key(), &self.bucket.region(), "s3", - ) + )?) } - fn authorization(&self, headers: &HeaderMap) -> String { + fn authorization(&self, headers: &HeaderMap) -> S3Result { let canonical_request = self.canonical_request(headers); let string_to_sign = self.string_to_sign(&canonical_request); - let mut hmac = Hmac::::new(&self.signing_key()); + let mut hmac = signing::HmacSha256::new_varkey(&self.signing_key()?)?; hmac.input(string_to_sign.as_bytes()); - let signature = hmac.result().code().to_hex(); + let signature = hex::encode(hmac.result().code()); let signed_header = signing::signed_header_string(headers); - signing::authorization_header( + Ok(signing::authorization_header( &self.bucket.access_key(), &self.datetime, &self.bucket.region(), &signed_header, &signature, - ) + )) } fn headers(&self) -> S3Result { @@ -209,7 +208,7 @@ impl<'a> Request<'a> { } // This must be last, as it signs the other headers - let authorization = self.authorization(&headers); + let authorization = self.authorization(&headers)?; headers.insert(header::AUTHORIZATION, authorization.parse()?); // The format of RFC2822 is somewhat malleable, so including it in @@ -260,7 +259,7 @@ impl<'a> Request<'a> { }; let request = client - .request(self.command.http_verb(), self.url()) + .request(self.command.http_verb(), self.url().as_str()) .headers(headers.to_owned()) .body(content.to_owned()); @@ -291,7 +290,6 @@ mod tests { use command::Command; use credentials::Credentials; use request::{Request}; - use url::form_urlencoded::Parse; use error::S3Result; // Fake keys - otherwise using Credentials::default will use actual user diff --git a/src/signing.rs b/src/signing.rs index 28ce40154f..676750c5e5 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -5,16 +5,18 @@ use std::str; use chrono::{DateTime, Utc}; -use hex::ToHex; use hmac::{Hmac, Mac}; use url::Url; use region::Region; use reqwest::header::HeaderMap; use sha2::{Digest, Sha256}; +use error::S3Result; const SHORT_DATE: &str = "%Y%m%d"; const LONG_DATETIME: &str = "%Y%m%dT%H%M%SZ"; +pub type HmacSha256 = Hmac; + /// Encode a URI following the specific requirements of the AWS service. pub fn uri_encode(string: &str, encode_slash: bool) -> String { let mut result = String::with_capacity(string.len() * 2); @@ -102,7 +104,7 @@ pub fn string_to_sign(datetime: &DateTime, region: &Region, canonical_req: format!("AWS4-HMAC-SHA256\n{timestamp}\n{scope}\n{hash}", timestamp = datetime.format(LONG_DATETIME), scope = scope_string(datetime, region), - hash = hasher.result().as_slice().to_hex()) + hash = hex::encode(hasher.result().as_slice())) } /// Generate the AWS signing key, derived from the secret key, date, region, @@ -111,17 +113,17 @@ pub fn signing_key(datetime: &DateTime, secret_key: &str, region: &Region, service: &str) - -> Vec { + -> S3Result> { let secret = String::from("AWS4") + secret_key; - let mut date_hmac = Hmac::::new(secret.as_bytes()); + let mut date_hmac = HmacSha256::new_varkey(secret.as_bytes())?; date_hmac.input(datetime.format(SHORT_DATE).to_string().as_bytes()); - let mut region_hmac = Hmac::::new(date_hmac.result().code()); + let mut region_hmac = HmacSha256::new_varkey(&date_hmac.result().code())?; region_hmac.input(region.to_string().as_bytes()); - let mut service_hmac = Hmac::::new(region_hmac.result().code()); + let mut service_hmac = HmacSha256::new_varkey(®ion_hmac.result().code())?; service_hmac.input(service.as_bytes()); - let mut signing_hmac = Hmac::::new(service_hmac.result().code()); + let mut signing_hmac = HmacSha256::new_varkey(&service_hmac.result().code())?; signing_hmac.input(b"aws4_request"); - signing_hmac.result().code().into() + Ok(signing_hmac.result().code().to_vec()) } /// Generate the AWS authorization header. @@ -144,7 +146,6 @@ mod tests { use std::str; use chrono::{TimeZone, Utc}; - use hex::ToHex; use reqwest::header::{HeaderMap, HeaderValue}; use url::Url; @@ -200,8 +201,8 @@ mod tests { let key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"; let expected = "c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9"; let datetime = Utc.ymd(2015, 8, 30).and_hms(0, 0, 0); - let signature = signing_key(&datetime, key, &"us-east-1".parse().unwrap(), "iam"); - assert_eq!(expected, signature.to_hex()); + let signature = signing_key(&datetime, key, &"us-east-1".parse().unwrap(), "iam").unwrap(); + assert_eq!(expected, hex::encode(signature)); } const EXPECTED_SHA: &'static str = "e3b0c44298fc1c149afbf4c8996fb924\ @@ -251,9 +252,9 @@ mod tests { let expected = "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41"; let secret = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; let signing_key = signing_key(&datetime, secret, &"us-east-1".parse().unwrap(), "s3"); - let mut hmac = Hmac::::new(&signing_key); + let mut hmac = Hmac::::new_varkey(&signing_key.unwrap()).unwrap(); hmac.input(string_to_sign.as_bytes()); - assert_eq!(expected, hmac.result().code().to_hex()); + assert_eq!(expected, hex::encode(hmac.result().code())); } #[test] @@ -269,7 +270,7 @@ mod tests { true "###; - let deserialized: ListBucketResult = serde_xml::deserialize(result_string.as_bytes()).expect("Parse error!"); + let deserialized: ListBucketResult = serde_xml::from_reader(result_string.as_bytes()).expect("Parse error!"); assert!(deserialized.is_truncated); } }