From 60f3200479ca66b2e9e270ace63b6c96b19292d9 Mon Sep 17 00:00:00 2001 From: abersheeran Date: Tue, 31 Oct 2023 13:04:31 +0800 Subject: [PATCH] Parse xml --- Cargo.toml | 1 + src/dav.rs | 151 ++++++++++++++++++++++++++++---------------------- src/r2.rs | 71 ++++++++++++++++++++---- src/values.rs | 21 ++++++- src/xml.rs | 102 +++++++++++++++++++++++++++++----- 5 files changed, 251 insertions(+), 95 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 275b5c4..27e2834 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["cdylib"] async-trait = "0.1.74" base64 = "0.21.5" lazy_static = "1.4.0" +quick-xml = "0.31.0" worker = "0.0.18" [profile.release] diff --git a/src/dav.rs b/src/dav.rs index 918ac5d..74a4c5c 100644 --- a/src/dav.rs +++ b/src/dav.rs @@ -2,7 +2,7 @@ use worker::ByteStream; use crate::r2::R2; use crate::values::{Depth, Overwrite, Range}; -use crate::xml::XMLBuilder; +use crate::xml::XMLNode; use std::collections::HashMap; use std::option::Option; @@ -69,9 +69,15 @@ impl Dav { depth: Depth, req_body: String, ) -> Result { - // if req_body.len() > 0 { - // return Err((415, None, None)); - // } + let mut xml; + if req_body.len() > 0 { + match XMLNode::parse_xml(&req_body) { + Ok(v) => xml = v, + Err(_) => return Err((415, None, None)), + }; + } else { + return Err((415, None, None)); + } let mut headers = HashMap::new(); headers.insert( @@ -81,7 +87,7 @@ impl Dav { match depth { Depth::One => { - let mut multistatus = XMLBuilder::new( + let mut multistatus = XMLNode::new( "D:multistatus".to_string(), Some(vec![("xmlns:D".to_string(), "DAV:".to_string())]), None, @@ -89,45 +95,44 @@ impl Dav { match self.fs.list(path.clone()).await { Ok(items) => { for (href, properties) in items { - let mut response = - XMLBuilder::new("D:response".to_string(), None, None); + let response = multistatus.elem("D:response".to_string(), None, None); response.elem("D:href".to_string(), None, Some(href)); - let mut propstat = - XMLBuilder::new("D:propstat".to_string(), None, None); - let mut prop = XMLBuilder::new("D:prop".to_string(), None, None); - prop.elem("D:creationdate".to_string(), None, properties.creation_date); - prop.elem("D:displayname".to_string(), None, properties.display_name); - prop.elem( - "D:getcontentlanguage".to_string(), - None, - properties.get_content_language, - ); - prop.elem( - "D:getcontentlength".to_string(), - None, - properties - .get_content_length - .map_or(None, |v| Some(v.to_string())), - ); - prop.elem( - "D:getcontenttype".to_string(), - None, - properties.get_content_type, - ); - prop.elem("D:getetag".to_string(), None, properties.get_etag); - prop.elem( - "D:getlastmodified".to_string(), - None, - properties.get_last_modified, - ); - propstat.add(prop); + let propstat = response.elem("D:propstat".to_string(), None, None); propstat.elem( "D:status".to_string(), None, Some("HTTP/1.1 200 OK".to_string()), ); - response.add(propstat); - multistatus.add(response); + let prop = propstat.elem("D:prop".to_string(), None, None); + properties + .creation_date + .map(|v| prop.elem("D:creationdate".to_string(), None, Some(v))); + properties + .display_name + .map(|v| prop.elem("D:displayname".to_string(), None, Some(v))); + properties.get_content_language.map(|v| { + prop.elem("D:getcontentlanguage".to_string(), None, Some(v)) + }); + properties.get_content_length.map(|v| { + prop.elem( + "D:getcontentlength".to_string(), + None, + Some(v.to_string()), + ) + }); + properties + .get_content_type + .map(|v| prop.elem("D:getcontenttype".to_string(), None, Some(v))); + properties + .get_etag + .map(|v| prop.elem("D:getetag".to_string(), None, Some(v))); + properties.get_last_modified.map(|v| { + prop.elem( + "D:getlastmodified".to_string(), + None, + Some(v.to_string()), + ) + }); } Ok((207, headers, multistatus.build())) @@ -136,13 +141,13 @@ impl Dav { } } Depth::Zero => { - let mut multistatus = XMLBuilder::new( + let mut multistatus = XMLNode::new( "D:multistatus".to_string(), Some(vec![("xmlns:D".to_string(), "DAV:".to_string())]), None, ); match self.fs.get(path.clone()).await { - Ok((href, properties)) => { + Ok((href, properties, _, custom_metadata)) => { let response = multistatus.elem("D:response".to_string(), None, None); response.elem("D:href".to_string(), None, Some(href)); let propstat = response.elem("D:propstat".to_string(), None, None); @@ -152,31 +157,31 @@ impl Dav { Some("HTTP/1.1 200 OK".to_string()), ); let prop = propstat.elem("D:prop".to_string(), None, None); - prop.elem("D:creationdate".to_string(), None, properties.creation_date); - prop.elem("D:displayname".to_string(), None, properties.display_name); - prop.elem( - "D:getcontentlanguage".to_string(), - None, - properties.get_content_language, - ); - prop.elem( - "D:getcontentlength".to_string(), - None, - properties - .get_content_length - .map_or(None, |v| Some(v.to_string())), - ); - prop.elem( - "D:getcontenttype".to_string(), - None, - properties.get_content_type, - ); - prop.elem("D:getetag".to_string(), None, properties.get_etag); - prop.elem( - "D:getlastmodified".to_string(), - None, - properties.get_last_modified, - ); + properties + .creation_date + .map(|v| prop.elem("D:creationdate".to_string(), None, Some(v))); + properties + .display_name + .map(|v| prop.elem("D:displayname".to_string(), None, Some(v))); + properties + .get_content_language + .map(|v| prop.elem("D:getcontentlanguage".to_string(), None, Some(v))); + properties.get_content_length.map(|v| { + prop.elem("D:getcontentlength".to_string(), None, Some(v.to_string())) + }); + properties + .get_content_type + .map(|v| prop.elem("D:getcontenttype".to_string(), None, Some(v))); + properties + .get_etag + .map(|v| prop.elem("D:getetag".to_string(), None, Some(v))); + properties.get_last_modified.map(|v| { + prop.elem("D:getlastmodified".to_string(), None, Some(v.to_string())) + }); + + for (key, value) in custom_metadata { + prop.elem(key, None, Some(value)); + } Ok((207, (headers), (multistatus.build()))) } @@ -212,7 +217,7 @@ impl Dav { "Content-Type".to_string(), "application/xml; charset=utf-8".to_string(), ); - let mut multistatus = XMLBuilder::new( + let mut multistatus = XMLNode::new( "D:multistatus".to_string(), Some(vec![("xmlns:D".to_string(), "DAV:".to_string())]), None, @@ -249,7 +254,7 @@ impl Dav { range: Range, ) -> Result { match self.fs.download(path, range.clone()).await { - Ok((properties, stream)) => { + Ok((properties, response_headers, stream)) => { let mut headers: HashMap = HashMap::new(); headers.insert("Accept-Ranges".to_string(), "bytes".to_string()); headers.insert( @@ -270,6 +275,18 @@ impl Dav { properties .get_last_modified .map(|v| headers.insert("Last-Modified".to_string(), v)); + response_headers + .cache_control + .map(|v| headers.insert("Cache-Control".to_string(), v)); + response_headers + .cache_expiry + .map(|v| headers.insert("Expires".to_string(), v.to_string())); + response_headers + .content_disposition + .map(|v| headers.insert("Content-Disposition".to_string(), v)); + response_headers + .content_encoding + .map(|v| headers.insert("Content-Encoding".to_string(), v)); match (range.start, range.end) { (Some(start), Some(end)) => { headers.insert( diff --git a/src/r2.rs b/src/r2.rs index e5187e1..3f5aa1f 100644 --- a/src/r2.rs +++ b/src/r2.rs @@ -1,4 +1,6 @@ -use crate::values::{DavProperties, Range}; +use std::collections::HashMap; + +use crate::values::{DavProperties, HttpResponseHeaders, Range}; use worker::{console_debug, Bucket, ByteStream, FixedLengthStream, Headers, Range as R2Range}; pub struct R2 { @@ -10,10 +12,26 @@ impl R2 { R2 { bucket } } - pub async fn get(&self, path: String) -> Result<(String, DavProperties), String> { + pub async fn get( + &self, + path: String, + ) -> Result< + ( + String, + DavProperties, + HttpResponseHeaders, + HashMap, + ), + String, + > { match self.bucket.get(path).execute().await { Ok(f) => f.map_or(Err("Resource not found".to_string()), |file| { - Ok((file.key(), DavProperties::from(&file))) + Ok(( + file.key(), + DavProperties::from(&file), + HttpResponseHeaders::from(file.http_metadata()), + file.custom_metadata().unwrap_or(HashMap::new()), + )) }), Err(error) => Err(error.to_string()), } @@ -33,14 +51,37 @@ impl R2 { } } - pub async fn patch_metadata(&self, path: String, metadata: Headers) -> Result<(), String> { - match self.bucket.get(path).execute().await { - Ok(f) => f.map_or(Err("Resource not found".to_string()), |file| { - match file.write_http_metadata(metadata) { - Ok(_) => Ok(()), - Err(error) => Err(error.to_string()), + pub async fn patch_metadata( + &self, + path: String, + metadata: HashMap, + ) -> Result, String> { + match self.bucket.get(path.clone()).execute().await { + Ok(f) => match f { + Some(file) => { + let stream = match file.body() { + Some(body) => match body.stream() { + Ok(s) => s, + Err(e) => return Err(e.to_string()), + }, + None => return Err("Failed to get file body stream".to_string()), + }; + match self + .bucket + .put(path, FixedLengthStream::wrap(stream, file.size().into())) + .custom_metadata(metadata) + .execute() + .await + { + Ok(file) => match file.custom_metadata() { + Ok(metadata) => Ok(metadata), + Err(e) => Err(e.to_string()), + }, + Err(error) => Err(error.to_string()), + } } - }), + None => Err("Resource not found".to_string()), + }, Err(error) => Err(error.to_string()), } } @@ -49,7 +90,7 @@ impl R2 { &self, path: String, range: Range, - ) -> Result<(DavProperties, ByteStream), String> { + ) -> Result<(DavProperties, HttpResponseHeaders, ByteStream), String> { let r2range: Option = match (range.start, range.end) { (Some(start), Some(end)) => Some(R2Range::OffsetWithLength { offset: start, @@ -78,7 +119,13 @@ impl R2 { .map_or(Err("Failed to get file body stream".to_string()), |b| { b.stream().map_or( Err("Failed to get file body stream".to_string()), - |stream| Ok((DavProperties::from(&file), stream)), + |stream| { + Ok(( + DavProperties::from(&file), + HttpResponseHeaders::from(file.http_metadata()), + stream, + )) + }, ) }) }), diff --git a/src/values.rs b/src/values.rs index 2843a10..a671bb4 100644 --- a/src/values.rs +++ b/src/values.rs @@ -1,4 +1,4 @@ -use worker::{console_debug, Object}; +use worker::{console_debug, Date, HttpMetadata, Object}; #[derive(Default, Debug, Clone, PartialEq, Hash, Eq)] pub enum Depth { @@ -106,3 +106,22 @@ impl From<&Object> for DavProperties { } } } + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct HttpResponseHeaders { + pub content_disposition: Option, + pub content_encoding: Option, + pub cache_control: Option, + pub cache_expiry: Option, +} + +impl From for HttpResponseHeaders { + fn from(value: HttpMetadata) -> Self { + HttpResponseHeaders { + content_disposition: value.content_disposition, + content_encoding: value.content_encoding, + cache_control: value.cache_control, + cache_expiry: value.cache_expiry, + } + } +} diff --git a/src/xml.rs b/src/xml.rs index f7c69c9..a8ca01a 100644 --- a/src/xml.rs +++ b/src/xml.rs @@ -1,19 +1,22 @@ +use quick_xml::events::Event; +use quick_xml::reader::Reader; use std::collections::HashMap; -pub struct XMLBuilder { - name: String, - value: Option, - attributes: Option>, - elements: Vec, +#[derive(Default, Debug, Clone)] +pub struct XMLNode { + pub name: String, + pub value: Option, + pub attributes: Option>, + pub elements: Vec, } -impl XMLBuilder { +impl XMLNode { pub fn new( name: String, attributes: Option>, value: Option, - ) -> XMLBuilder { - XMLBuilder { + ) -> XMLNode { + XMLNode { name, value, attributes: attributes.map(|v| v.into_iter().collect()), @@ -26,13 +29,13 @@ impl XMLBuilder { name: String, attributes: Option>, value: Option, - ) -> &mut XMLBuilder { - let el = XMLBuilder::new(name, attributes, value); + ) -> &mut XMLNode { + let el = XMLNode::new(name, attributes, value); self.elements.push(el); self.elements.last_mut().unwrap() } - pub fn add(&mut self, element: XMLBuilder) { + pub fn add(&mut self, element: XMLNode) { self.elements.push(element); } @@ -43,7 +46,7 @@ impl XMLBuilder { xml.join("") } - fn write_element(&self, element: &XMLBuilder) -> String { + fn write_element(&self, element: &XMLNode) -> String { let mut xml = Vec::new(); // attributes let mut attrs = Vec::new(); @@ -69,22 +72,91 @@ impl XMLBuilder { xml.push(format!("", element.name)); xml.join("") } + + pub fn parse_xml(xml: &str) -> Result { + let mut reader = Reader::from_str(xml); + reader.trim_text(true); + let mut buf = Vec::new(); + let mut elements: Vec = Vec::new(); + let mut stack: Vec<(String, HashMap, String)> = Vec::new(); + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(ref e)) => { + stack.push(( + std::str::from_utf8(e.name().as_ref()).unwrap().to_string(), + e.attributes() + .map(|a| { + let a = a.unwrap(); + ( + std::str::from_utf8(a.key.as_ref()).unwrap().to_string(), + std::str::from_utf8(a.value.as_ref()).unwrap().to_string(), + ) + }) + .collect(), + "".to_string(), + )); + } + Ok(Event::End(_)) => { + stack.pop().map(|(name, attributes, value)| { + let mut element = + XMLNode::new(name, Some(attributes.into_iter().collect()), Some(value)); + match elements.pop() { + None => { + let _ = &elements.push(element.clone()); + } + Some(c) => { + element.add(c); + let _ = &elements.push(element); + } + }; + }); + } + Ok(Event::Text(e)) => { + stack.pop().map(|(name, attributes, _)| { + stack.push((name, attributes, e.unescape().unwrap().into_owned())); + }); + } + Ok(Event::Eof) => break, + Err(e) => { + return Err(format!( + "Error at position {}: {:?}", + reader.buffer_position(), + e + )) + } + _ => (), + } + buf.clear(); + } + if elements.len() == 1 { + Ok(elements.pop().unwrap()) + } else { + Err(format!("XMLNode parse error, {:?}", elements)) + } + } } #[cfg(test)] mod tests { - use crate::xml::XMLBuilder; + use crate::xml::XMLNode; #[test] fn xml_build() { - let mut xml = XMLBuilder::new("root".to_string(), None, None); + let mut xml = XMLNode::new("root".to_string(), None, None); xml.elem("child".to_string(), None, None) .elem("grandchild".to_string(), None, None) - .add(XMLBuilder::new( + .add(XMLNode::new( "greatgrandchild".to_string(), None, Some("value".to_string()), )); assert!(xml.build() == "value") } + + #[test] + fn xml_parse() { + let xml = "value"; + let xml = XMLNode::parse_xml(xml).unwrap(); + assert!(xml.build() == "value", "{}", xml.build()) + } }