add xml validation and basic deserialization test
Some checks failed
continuous-integration/drone/pr Build is failing
Some checks failed
continuous-integration/drone/pr Build is failing
This commit is contained in:
parent
3999fd4a02
commit
6fef0f4cad
1 changed files with 148 additions and 13 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue