Skip to content

Commit

Permalink
Merge branch 'ergonomic-futures'
Browse files Browse the repository at this point in the history
  • Loading branch information
durch committed Oct 17, 2019
2 parents c794980 + fb37a88 commit 4539963
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 50 deletions.
22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand All @@ -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]
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/bucket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?
Expand Down Expand Up @@ -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
};
Expand All @@ -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))
}

Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
28 changes: 13 additions & 15 deletions src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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(),
}
Expand All @@ -149,29 +148,29 @@ impl<'a> Request<'a> {
signing::string_to_sign(&self.datetime, &self.bucket.region(), request)
}

fn signing_key(&self) -> Vec<u8> {
signing::signing_key(
fn signing_key(&self) -> S3Result<Vec<u8>> {
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<String> {
let canonical_request = self.canonical_request(headers);
let string_to_sign = self.string_to_sign(&canonical_request);
let mut hmac = Hmac::<Sha256>::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<HeaderMap> {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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
Expand Down
29 changes: 15 additions & 14 deletions src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Sha256>;

/// 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);
Expand Down Expand Up @@ -102,7 +104,7 @@ pub fn string_to_sign(datetime: &DateTime<Utc>, 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,
Expand All @@ -111,17 +113,17 @@ pub fn signing_key(datetime: &DateTime<Utc>,
secret_key: &str,
region: &Region,
service: &str)
-> Vec<u8> {
-> S3Result<Vec<u8>> {
let secret = String::from("AWS4") + secret_key;
let mut date_hmac = Hmac::<Sha256>::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::<Sha256>::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::<Sha256>::new(region_hmac.result().code());
let mut service_hmac = HmacSha256::new_varkey(&region_hmac.result().code())?;
service_hmac.input(service.as_bytes());
let mut signing_hmac = Hmac::<Sha256>::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.
Expand All @@ -144,7 +146,6 @@ mod tests {
use std::str;

use chrono::{TimeZone, Utc};
use hex::ToHex;
use reqwest::header::{HeaderMap, HeaderValue};
use url::Url;

Expand Down Expand Up @@ -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\
Expand Down Expand Up @@ -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::<Sha256>::new(&signing_key);
let mut hmac = Hmac::<Sha256>::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]
Expand All @@ -269,7 +270,7 @@ mod tests {
<IsTruncated>true</IsTruncated>
</ListBucketResult>
"###;
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);
}
}

0 comments on commit 4539963

Please sign in to comment.