add xml validation and basic deserialization test
Some checks failed
continuous-integration/drone/pr Build is failing

This commit is contained in:
Trinity Pointard 2021-12-14 19:27:45 +01:00
parent 3999fd4a02
commit 6fef0f4cad

View file

@ -48,7 +48,8 @@ pub async fn handle_put_website(
.await? .await?
.ok_or(Error::NotFound)?; .ok_or(Error::NotFound)?;
let _conf: WebsiteConfiguration = from_reader(&body as &[u8])?; let conf: WebsiteConfiguration = from_reader(&body as &[u8])?;
conf.validate()?;
if let BucketState::Present(state) = bucket.state.get_mut() { if let BucketState::Present(state) = bucket.state.get_mut() {
state.website.update(true); state.website.update(true);
@ -78,7 +79,7 @@ pub struct WebsiteConfiguration {
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct RoutingRule { pub struct RoutingRule {
#[serde(rename = "RoutingRule")] #[serde(rename = "RoutingRule")]
pub routing_rule: RoutingRuleInner, pub inner: RoutingRuleInner,
} }
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
@ -104,7 +105,7 @@ pub struct Suffix {
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Target { pub struct Target {
#[serde(rename = "HostName")] #[serde(rename = "HostName")]
pub hostname: Option<Value>, pub hostname: Value,
#[serde(rename = "Protocol")] #[serde(rename = "Protocol")]
pub protocol: Option<Value>, pub protocol: Option<Value>,
} }
@ -131,6 +132,108 @@ pub struct Redirect {
pub replace_full: Option<Value>, pub replace_full: Option<Value>,
} }
impl WebsiteConfiguration {
pub fn validate(&self) -> Result<(), Error> {
if self.redirect_all_requests_to.is_some() {
if self.error_document.is_some()
|| self.index_document.is_some()
|| self.routing_rules.is_some()
{
return Err(Error::BadRequest(
"Bad XML: can't have RedirectAllRequestsTo and other fields".to_owned(),
));
}
}
if let Some(ref ed) = self.error_document {
ed.validate()?;
}
if let Some(ref id) = self.index_document {
id.validate()?;
}
if let Some(ref rart) = self.redirect_all_requests_to {
rart.validate()?;
}
if let Some(ref rrs) = self.routing_rules {
for rr in rrs {
rr.inner.validate()?;
}
}
Ok(())
}
}
impl Key {
pub fn validate(&self) -> Result<(), Error> {
if self.key.0.is_empty() {
Err(Error::BadRequest(
"Bad XML: error document specified but empty".to_owned(),
))
} else {
Ok(())
}
}
}
impl Suffix {
pub fn validate(&self) -> Result<(), Error> {
if self.suffix.0.is_empty() | self.suffix.0.contains('/') {
Err(Error::BadRequest(
"Bad XML: index document is empty or contains /".to_owned(),
))
} else {
Ok(())
}
}
}
impl Target {
pub fn validate(&self) -> Result<(), Error> {
if let Some(ref protocol) = self.protocol {
if protocol.0 != "http" && protocol.0 != "https" {
return Err(Error::BadRequest("Bad XML: invalid protocol".to_owned()));
}
}
Ok(())
}
}
impl RoutingRuleInner {
pub fn validate(&self) -> Result<(), Error> {
let has_prefix = self
.condition
.as_ref()
.map(|c| c.prefix.as_ref())
.flatten()
.is_some();
self.redirect.validate(has_prefix)
}
}
impl Redirect {
pub fn validate(&self, has_prefix: bool) -> Result<(), Error> {
if self.replace_prefix.is_some() {
if self.replace_full.is_some() {
return Err(Error::BadRequest(
"Bad XML: both ReplaceKeyPrefixWith and ReplaceKeyWith are set".to_owned(),
));
}
if !has_prefix {
return Err(Error::BadRequest(
"Bad XML: ReplaceKeyPrefixWith is set, but KeyPrefixEquals isn't".to_owned(),
));
}
}
if let Some(ref protocol) = self.protocol {
if protocol.0 != "http" && protocol.0 != "https" {
return Err(Error::BadRequest("Bad XML: invalid protocol".to_owned()));
}
}
// TODO there are probably more invalide cases, but which ones?
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -142,32 +245,64 @@ mod tests {
let message = r#"<?xml version="1.0" encoding="UTF-8"?> let message = r#"<?xml version="1.0" encoding="UTF-8"?>
<WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<ErrorDocument> <ErrorDocument>
<Key>string</Key> <Key>my-error-doc</Key>
</ErrorDocument> </ErrorDocument>
<IndexDocument> <IndexDocument>
<Suffix>string</Suffix> <Suffix>my-index</Suffix>
</IndexDocument> </IndexDocument>
<RedirectAllRequestsTo> <RedirectAllRequestsTo>
<HostName>string</HostName> <HostName>garage.tld</HostName>
<Protocol>string</Protocol> <Protocol>https</Protocol>
</RedirectAllRequestsTo> </RedirectAllRequestsTo>
<RoutingRules> <RoutingRules>
<RoutingRule> <RoutingRule>
<Condition> <Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals> <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
<KeyPrefixEquals>string</KeyPrefixEquals> <KeyPrefixEquals>prefix1</KeyPrefixEquals>
</Condition> </Condition>
<Redirect> <Redirect>
<HostName>string</HostName> <HostName>gara.ge</HostName>
<Protocol>string</Protocol> <Protocol>http</Protocol>
<HttpRedirectCode>303</HttpRedirectCode> <HttpRedirectCode>303</HttpRedirectCode>
<ReplaceKeyPrefixWith>string</ReplaceKeyPrefixWith> <ReplaceKeyPrefixWith>prefix2</ReplaceKeyPrefixWith>
<ReplaceKeyWith>string</ReplaceKeyWith> <ReplaceKeyWith>fullkey</ReplaceKeyWith>
</Redirect> </Redirect>
</RoutingRule> </RoutingRule>
</RoutingRules> </RoutingRules>
</WebsiteConfiguration>"#; </WebsiteConfiguration>"#;
let _conf: WebsiteConfiguration = from_str(message).unwrap(); let conf: WebsiteConfiguration = from_str(message).unwrap();
let ref_value = WebsiteConfiguration {
xmlns: (),
error_document: Some(Key {
key: Value("my-error-doc".to_owned()),
}),
index_document: Some(Suffix {
suffix: Value("my-index".to_owned()),
}),
redirect_all_requests_to: Some(Target {
hostname: Value("garage.tld".to_owned()),
protocol: Some(Value("https".to_owned())),
}),
routing_rules: Some(vec![RoutingRule {
inner: RoutingRuleInner {
condition: Some(Condition {
http_error_code: Some(IntValue(404)),
prefix: Some(Value("prefix1".to_owned())),
}),
redirect: Redirect {
hostname: Some(Value("gara.ge".to_owned())),
protocol: Some(Value("http".to_owned())),
http_redirect_code: Some(IntValue(303)),
replace_prefix: Some(Value("prefix2".to_owned())),
replace_full: Some(Value("fullkey".to_owned())),
},
},
}]),
};
assert_eq! {
ref_value,
conf
}
// TODO verify result is ok // TODO verify result is ok
// TODO cycle back and verify if ok // TODO cycle back and verify if ok
} }