Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions src/actions/list_objects_v2.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::borrow::Cow;
use std::time::Duration;

use percent_encoding::percent_decode;
use serde::Deserialize;
use time::OffsetDateTime;
use url::Url;
Expand Down Expand Up @@ -47,8 +48,8 @@ pub struct ListObjectsV2Response {
pub max_keys: Option<u16>,
#[serde(rename = "CommonPrefixes", default)]
pub common_prefixes: Vec<CommonPrefixes>,
// #[serde(rename = "EncodingType")]
// encoding_type: String,
#[serde(rename = "EncodingType")]
encoding_type: String,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wandering whether this should be an enum or not

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like an Option<String>?

Since there is an official S3 specification, as far as I know, I have no idea.
What I can say is that DO, minio, and was all return the encoding_type set to url. But maybe some other providers don't.

I would say we keep it like that until someone gives us an implementation that doesn't work like that personally 😅

// #[serde(rename = "KeyCount")]
// key_count: u16,
// #[serde(rename = "ContinuationToken")]
Expand Down Expand Up @@ -164,13 +165,21 @@ impl<'a> ListObjectsV2<'a> {

pub fn parse_response(s: &str) -> Result<ListObjectsV2Response, quick_xml::DeError> {
let mut parsed: ListObjectsV2Response = quick_xml::de::from_str(s)?;
let url_encoded = parsed.encoding_type == "url";

// S3 returns an Owner with an empty DisplayName and ID when fetch-owner is disabled
for content in parsed.contents.iter_mut() {
if let Some(owner) = &content.owner {
if owner.id.is_empty() && owner.display_name.is_empty() {
content.owner = None;
}
if url_encoded {
match percent_decode(content.key.as_bytes()).decode_utf8() {
Ok(Cow::Borrowed(_)) => (),
Ok(Cow::Owned(s)) => content.key = s,
Err(_) => (),
};
}
}
}

Expand Down Expand Up @@ -372,4 +381,40 @@ mod tests {
assert!(parsed.next_continuation_token.is_none());
assert!(parsed.start_after.is_none());
}

#[test]
fn parse_url_encoded_key() {
let input = r#"
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>test</Name>
<Prefix></Prefix>
<KeyCount>0</KeyCount>
<MaxKeys>4500</MaxKeys>
<Delimiter></Delimiter>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>100%25tamo.jpg</Key>
<LastModified>2020-12-01T20:43:11.794Z</LastModified>
<ETag>"bfd537a51d15208163231b0711e0b1f3"</ETag>
<Size>4274</Size>
<Owner>
<ID></ID>
<DisplayName></DisplayName>
</Owner>
<StorageClass>STANDARD</StorageClass>
</Contents>
<EncodingType>url</EncodingType>
</ListBucketResult>
"#;

let parsed = ListObjectsV2::parse_response(input).unwrap();
assert_eq!(parsed.contents.len(), 1);
assert_eq!(parsed.contents[0].key, "100%tamo.jpg");

assert_eq!(parsed.max_keys, Some(4500));
assert!(parsed.common_prefixes.is_empty());
assert!(parsed.next_continuation_token.is_none());
assert!(parsed.start_after.is_none());
}
}