mirror of
https://github.com/minio/minio-rs.git
synced 2025-12-06 23:36:52 +08:00
Parse and add tags to list objects output type (#77)
This commit is contained in:
parent
c672e7528b
commit
3f160cb6c0
@ -29,6 +29,7 @@ lazy_static = "1.4.0"
|
|||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
multimap = "0.10.0"
|
multimap = "0.10.0"
|
||||||
os_info = "3.7.0"
|
os_info = "3.7.0"
|
||||||
|
percent-encoding = "2.3.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.9.4"
|
regex = "1.9.4"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
|||||||
@ -110,6 +110,7 @@ pub enum Error {
|
|||||||
PostPolicyError(String),
|
PostPolicyError(String),
|
||||||
InvalidObjectLockConfig(String),
|
InvalidObjectLockConfig(String),
|
||||||
NoClientProvided,
|
NoClientProvided,
|
||||||
|
TagDecodingError(String, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Error {}
|
impl std::error::Error for Error {}
|
||||||
@ -214,6 +215,7 @@ impl fmt::Display for Error {
|
|||||||
Error::PostPolicyError(m) => write!(f, "{}", m),
|
Error::PostPolicyError(m) => write!(f, "{}", m),
|
||||||
Error::InvalidObjectLockConfig(m) => write!(f, "{}", m),
|
Error::InvalidObjectLockConfig(m) => write!(f, "{}", m),
|
||||||
Error::NoClientProvided => write!(f, "no client provided"),
|
Error::NoClientProvided => write!(f, "no client provided"),
|
||||||
|
Error::TagDecodingError(input, error_message) => write!(f, "tag decoding failed: {} on input '{}'", error_message, input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ use crate::s3::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
types::{FromS3Response, ListEntry, S3Request},
|
types::{FromS3Response, ListEntry, S3Request},
|
||||||
utils::{
|
utils::{
|
||||||
from_iso8601utc, urldecode,
|
from_iso8601utc, parse_tags, urldecode,
|
||||||
xml::{Element, MergeXmlElements},
|
xml::{Element, MergeXmlElements},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -125,6 +125,11 @@ fn parse_list_objects_contents(
|
|||||||
})
|
})
|
||||||
.collect::<HashMap<String, String>>()
|
.collect::<HashMap<String, String>>()
|
||||||
});
|
});
|
||||||
|
let user_tags = content
|
||||||
|
.get_child_text("UserTags")
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| parse_tags(x))
|
||||||
|
.transpose()?;
|
||||||
let is_delete_marker = content.name() == "DeleteMarker";
|
let is_delete_marker = content.name() == "DeleteMarker";
|
||||||
|
|
||||||
contents.push(ListEntry {
|
contents.push(ListEntry {
|
||||||
@ -138,6 +143,7 @@ fn parse_list_objects_contents(
|
|||||||
is_latest,
|
is_latest,
|
||||||
version_id,
|
version_id,
|
||||||
user_metadata,
|
user_metadata,
|
||||||
|
user_tags,
|
||||||
is_prefix: false,
|
is_prefix: false,
|
||||||
is_delete_marker,
|
is_delete_marker,
|
||||||
encoding_type: etype,
|
encoding_type: etype,
|
||||||
@ -168,6 +174,7 @@ fn parse_list_objects_common_prefixes(
|
|||||||
is_latest: false,
|
is_latest: false,
|
||||||
version_id: None,
|
version_id: None,
|
||||||
user_metadata: None,
|
user_metadata: None,
|
||||||
|
user_tags: None,
|
||||||
is_prefix: true,
|
is_prefix: true,
|
||||||
is_delete_marker: false,
|
is_delete_marker: false,
|
||||||
encoding_type: encoding_type.as_ref().cloned(),
|
encoding_type: encoding_type.as_ref().cloned(),
|
||||||
|
|||||||
@ -159,6 +159,7 @@ pub struct ListEntry {
|
|||||||
pub is_latest: bool, // except ListObjects V1/V2
|
pub is_latest: bool, // except ListObjects V1/V2
|
||||||
pub version_id: Option<String>, // except ListObjects V1/V2
|
pub version_id: Option<String>, // except ListObjects V1/V2
|
||||||
pub user_metadata: Option<HashMap<String, String>>,
|
pub user_metadata: Option<HashMap<String, String>>,
|
||||||
|
pub user_tags: Option<HashMap<String, String>>,
|
||||||
pub is_prefix: bool,
|
pub is_prefix: bool,
|
||||||
pub is_delete_marker: bool,
|
pub is_delete_marker: bool,
|
||||||
pub encoding_type: Option<String>,
|
pub encoding_type: Option<String>,
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
//! Various utility and helper functions
|
//! Various utility and helper functions
|
||||||
|
|
||||||
use crate::s3::error::Error;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
use base64::engine::general_purpose::STANDARD as BASE64;
|
use base64::engine::general_purpose::STANDARD as BASE64;
|
||||||
use base64::engine::Engine as _;
|
use base64::engine::Engine as _;
|
||||||
use byteorder::{BigEndian, ReadBytesExt};
|
use byteorder::{BigEndian, ReadBytesExt};
|
||||||
@ -24,13 +25,15 @@ use crc::{Crc, CRC_32_ISO_HDLC};
|
|||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use md5::compute as md5compute;
|
use md5::compute as md5compute;
|
||||||
use multimap::MultiMap;
|
use multimap::MultiMap;
|
||||||
|
use percent_encoding::{percent_decode_str, utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::collections::BTreeMap;
|
|
||||||
pub use urlencoding::decode as urldecode;
|
pub use urlencoding::decode as urldecode;
|
||||||
pub use urlencoding::encode as urlencode;
|
pub use urlencoding::encode as urlencode;
|
||||||
use xmltree::Element;
|
use xmltree::Element;
|
||||||
|
|
||||||
|
use crate::s3::error::Error;
|
||||||
|
|
||||||
/// Date and time with UTC timezone
|
/// Date and time with UTC timezone
|
||||||
pub type UtcTime = DateTime<Utc>;
|
pub type UtcTime = DateTime<Utc>;
|
||||||
|
|
||||||
@ -392,11 +395,86 @@ pub fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize {
|
|||||||
c
|
c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Characters to escape in query strings. Based on RFC 3986 and the golang
|
||||||
|
// net/url implementation used in the MinIO server.
|
||||||
|
//
|
||||||
|
// https://tools.ietf.org/html/rfc3986
|
||||||
|
//
|
||||||
|
// 1. All non-ascii characters are escaped always.
|
||||||
|
// 2. All reserved characters are escaped.
|
||||||
|
// 3. Any other characters are not escaped.
|
||||||
|
//
|
||||||
|
// Unreserved characters in addition to alphanumeric characters are: '-', '_',
|
||||||
|
// '.', '~' (§2.3 Unreserved characters (mark))
|
||||||
|
//
|
||||||
|
// Reserved characters for query strings: '$', '&', '+', ',', '/', ':', ';',
|
||||||
|
// '=', '?', '@' (§3.4)
|
||||||
|
//
|
||||||
|
// NON_ALPHANUMERIC already escapes everything non-alphanumeric (it includes all
|
||||||
|
// the reserved characters). So we only remove the unreserved characters from
|
||||||
|
// this set.
|
||||||
|
const QUERY_ESCAPE: &AsciiSet = &NON_ALPHANUMERIC
|
||||||
|
.remove(b'-')
|
||||||
|
.remove(b'_')
|
||||||
|
.remove(b'.')
|
||||||
|
.remove(b'~');
|
||||||
|
|
||||||
|
fn unescape(s: &str) -> Result<String, Error> {
|
||||||
|
percent_decode_str(s)
|
||||||
|
.decode_utf8()
|
||||||
|
.map_err(|e| Error::TagDecodingError(s.to_string(), e.to_string()))
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape(s: &str) -> String {
|
||||||
|
utf8_percent_encode(s, QUERY_ESCAPE).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use this while adding API to set tags.
|
||||||
|
//
|
||||||
|
// Handles escaping same as MinIO server - needed for ensuring compatibility.
|
||||||
|
pub fn encode_tags(h: &HashMap<String, String>) -> String {
|
||||||
|
let mut tags = Vec::new();
|
||||||
|
for (k, v) in h {
|
||||||
|
tags.push(format!("{}={}", escape(k), escape(v)));
|
||||||
|
}
|
||||||
|
tags.join("&")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_tags(s: &str) -> Result<HashMap<String, String>, Error> {
|
||||||
|
let mut tags = HashMap::new();
|
||||||
|
for tag in s.split('&') {
|
||||||
|
let mut kv = tag.split('=');
|
||||||
|
let k = match kv.next() {
|
||||||
|
Some(v) => unescape(v)?,
|
||||||
|
None => {
|
||||||
|
return Err(Error::TagDecodingError(
|
||||||
|
s.to_string(),
|
||||||
|
"tag key was empty".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let v = match kv.next() {
|
||||||
|
Some(v) => unescape(v)?,
|
||||||
|
None => "".to_owned(),
|
||||||
|
};
|
||||||
|
if kv.next().is_some() {
|
||||||
|
return Err(Error::TagDecodingError(
|
||||||
|
s.to_string(),
|
||||||
|
"tag had too many values for a key".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
tags.insert(k, v);
|
||||||
|
}
|
||||||
|
Ok(tags)
|
||||||
|
}
|
||||||
|
|
||||||
pub mod xml {
|
pub mod xml {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::s3::error::Error;
|
use crate::s3::error::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct XmlElementIndex {
|
struct XmlElementIndex {
|
||||||
children: HashMap<String, Vec<usize>>,
|
children: HashMap<String, Vec<usize>>,
|
||||||
}
|
}
|
||||||
@ -432,6 +510,7 @@ pub mod xml {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Element<'a> {
|
pub struct Element<'a> {
|
||||||
inner: &'a xmltree::Element,
|
inner: &'a xmltree::Element,
|
||||||
child_element_index: XmlElementIndex,
|
child_element_index: XmlElementIndex,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user