Parse xml
This commit is contained in:
parent
9794f37842
commit
60f3200479
@ -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]
|
||||
|
173
src/dav.rs
173
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<DavResponse, DavErrResponse> {
|
||||
// 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,60 +95,6 @@ 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);
|
||||
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);
|
||||
propstat.elem(
|
||||
"D:status".to_string(),
|
||||
None,
|
||||
Some("HTTP/1.1 200 OK".to_string()),
|
||||
);
|
||||
response.add(propstat);
|
||||
multistatus.add(response);
|
||||
}
|
||||
|
||||
Ok((207, headers, multistatus.build()))
|
||||
}
|
||||
Err(message) => return Err((404, None, Some(message))),
|
||||
}
|
||||
}
|
||||
Depth::Zero => {
|
||||
let mut multistatus = XMLBuilder::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)) => {
|
||||
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 +104,84 @@ 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,
|
||||
);
|
||||
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_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);
|
||||
.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,
|
||||
properties.get_last_modified,
|
||||
Some(v.to_string()),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Ok((207, headers, multistatus.build()))
|
||||
}
|
||||
Err(message) => return Err((404, None, Some(message))),
|
||||
}
|
||||
}
|
||||
Depth::Zero => {
|
||||
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, _, 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);
|
||||
propstat.elem(
|
||||
"D:status".to_string(),
|
||||
None,
|
||||
Some("HTTP/1.1 200 OK".to_string()),
|
||||
);
|
||||
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()))
|
||||
});
|
||||
|
||||
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<DavStreamResponse, DavErrResponse> {
|
||||
match self.fs.download(path, range.clone()).await {
|
||||
Ok((properties, stream)) => {
|
||||
Ok((properties, response_headers, stream)) => {
|
||||
let mut headers: HashMap<String, String> = 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(
|
||||
|
69
src/r2.rs
69
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, String>,
|
||||
),
|
||||
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(()),
|
||||
pub async fn patch_metadata(
|
||||
&self,
|
||||
path: String,
|
||||
metadata: HashMap<String, String>,
|
||||
) -> Result<HashMap<String, String>, 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<R2Range> = 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,
|
||||
))
|
||||
},
|
||||
)
|
||||
})
|
||||
}),
|
||||
|
@ -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<String>,
|
||||
pub content_encoding: Option<String>,
|
||||
pub cache_control: Option<String>,
|
||||
pub cache_expiry: Option<Date>,
|
||||
}
|
||||
|
||||
impl From<HttpMetadata> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
102
src/xml.rs
102
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<String>,
|
||||
attributes: Option<HashMap<String, String>>,
|
||||
elements: Vec<XMLBuilder>,
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct XMLNode {
|
||||
pub name: String,
|
||||
pub value: Option<String>,
|
||||
pub attributes: Option<HashMap<String, String>>,
|
||||
pub elements: Vec<XMLNode>,
|
||||
}
|
||||
|
||||
impl XMLBuilder {
|
||||
impl XMLNode {
|
||||
pub fn new(
|
||||
name: String,
|
||||
attributes: Option<Vec<(String, String)>>,
|
||||
value: Option<String>,
|
||||
) -> XMLBuilder {
|
||||
XMLBuilder {
|
||||
) -> XMLNode {
|
||||
XMLNode {
|
||||
name,
|
||||
value,
|
||||
attributes: attributes.map(|v| v.into_iter().collect()),
|
||||
@ -26,13 +29,13 @@ impl XMLBuilder {
|
||||
name: String,
|
||||
attributes: Option<Vec<(String, String)>>,
|
||||
value: Option<String>,
|
||||
) -> &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<XMLNode, String> {
|
||||
let mut reader = Reader::from_str(xml);
|
||||
reader.trim_text(true);
|
||||
let mut buf = Vec::new();
|
||||
let mut elements: Vec<XMLNode> = Vec::new();
|
||||
let mut stack: Vec<(String, HashMap<String, String>, 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() == "<?xml version=\"1.0\" encoding=\"utf-8\"?><root><child><grandchild><greatgrandchild>value</greatgrandchild></grandchild></child></root>")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xml_parse() {
|
||||
let xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><root><child><grandchild><greatgrandchild>value</greatgrandchild></grandchild></child></root>";
|
||||
let xml = XMLNode::parse_xml(xml).unwrap();
|
||||
assert!(xml.build() == "<?xml version=\"1.0\" encoding=\"utf-8\"?><root><child><grandchild><greatgrandchild>value</greatgrandchild></grandchild></child></root>", "{}", xml.build())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user