Support CORS

This commit is contained in:
abersheeran 2023-10-30 00:49:44 +08:00
parent 83bdcecd83
commit 9794f37842
5 changed files with 287 additions and 233 deletions

View File

@ -14,7 +14,7 @@ crate-type = ["cdylib"]
async-trait = "0.1.74" async-trait = "0.1.74"
base64 = "0.21.5" base64 = "0.21.5"
lazy_static = "1.4.0" lazy_static = "1.4.0"
worker = "0.0.15" worker = "0.0.18"
[profile.release] [profile.release]
lto = true lto = true

View File

@ -10,9 +10,9 @@ pub struct Dav {
fs: R2, fs: R2,
} }
type DavResponse = (u16, HashMap<String, String>, String); pub type DavResponse = (u16, HashMap<String, String>, String);
type DavErrResponse = (u16, Option<HashMap<String, String>>, Option<String>); pub type DavErrResponse = (u16, Option<HashMap<String, String>>, Option<String>);
type DavStreamResponse = (u16, HashMap<String, String>, ByteStream); pub type DavStreamResponse = (u16, HashMap<String, String>, ByteStream);
pub enum DavResponseType { pub enum DavResponseType {
DavResponse(Result<DavResponse, DavErrResponse>), DavResponse(Result<DavResponse, DavErrResponse>),
@ -32,8 +32,16 @@ impl From<Result<DavStreamResponse, DavErrResponse>> for DavResponseType {
} }
static DAV_CLASS: &str = "1"; static DAV_CLASS: &str = "1";
static SUPPORT_METHODS: [&str; 8] = [ static SUPPORT_METHODS: [&str; 9] = [
"OPTIONS", "PROPFIND", "MKCOL", "GET", "HEAD", "PUT", "COPY", "MOVE", "OPTIONS",
"PROPFIND",
"PROPPATCH",
"MKCOL",
"GET",
"HEAD",
"PUT",
"COPY",
"MOVE",
]; ];
impl Dav { impl Dav {
@ -61,9 +69,9 @@ impl Dav {
depth: Depth, depth: Depth,
req_body: String, req_body: String,
) -> Result<DavResponse, DavErrResponse> { ) -> Result<DavResponse, DavErrResponse> {
if req_body.len() > 0 { // if req_body.len() > 0 {
return Err((415, None, None)); // return Err((415, None, None));
} // }
let mut headers = HashMap::new(); let mut headers = HashMap::new();
headers.insert( headers.insert(
@ -78,7 +86,7 @@ impl Dav {
Some(vec![("xmlns:D".to_string(), "DAV:".to_string())]), Some(vec![("xmlns:D".to_string(), "DAV:".to_string())]),
None, None,
); );
match self.fs.list(path).await { match self.fs.list(path.clone()).await {
Ok(items) => { Ok(items) => {
for (href, properties) in items { for (href, properties) in items {
let mut response = let mut response =
@ -124,7 +132,7 @@ impl Dav {
Ok((207, headers, multistatus.build())) Ok((207, headers, multistatus.build()))
} }
Err(_) => return Err((404, None, None)), Err(message) => return Err((404, None, Some(message))),
} }
} }
Depth::Zero => { Depth::Zero => {
@ -133,12 +141,17 @@ impl Dav {
Some(vec![("xmlns:D".to_string(), "DAV:".to_string())]), Some(vec![("xmlns:D".to_string(), "DAV:".to_string())]),
None, None,
); );
match self.fs.get(path).await { match self.fs.get(path.clone()).await {
Ok((href, properties)) => { Ok((href, properties)) => {
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)); response.elem("D:href".to_string(), None, Some(href));
let mut propstat = XMLBuilder::new("D:propstat".to_string(), None, None); let propstat = response.elem("D:propstat".to_string(), None, None);
let mut prop = XMLBuilder::new("D:prop".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);
prop.elem("D:creationdate".to_string(), None, properties.creation_date); prop.elem("D:creationdate".to_string(), None, properties.creation_date);
prop.elem("D:displayname".to_string(), None, properties.display_name); prop.elem("D:displayname".to_string(), None, properties.display_name);
prop.elem( prop.elem(
@ -164,28 +177,63 @@ impl Dav {
None, None,
properties.get_last_modified, properties.get_last_modified,
); );
propstat.add(prop);
Ok((207, (headers), (multistatus.build())))
}
Err(_) => {
if !path.ends_with("/") {
return Err((404, None, None));
}
let response = multistatus.elem("D:response".to_string(), None, None);
response.elem("D:href".to_string(), None, Some(path));
let propstat = response.elem("D:propstat".to_string(), None, None);
propstat.elem( propstat.elem(
"D:status".to_string(), "D:status".to_string(),
None, None,
Some("HTTP/1.1 200 OK".to_string()), Some("HTTP/1.1 200 OK".to_string()),
); );
response.add(propstat); propstat.elem("D:prop".to_string(), None, None);
multistatus.add(response);
Ok((207, (headers), (multistatus.build()))) Ok((207, (headers), (multistatus.build())))
} }
Err(_) => return Err((404, None, None)),
} }
} }
Depth::Infinity => return Err((400, None, None)), Depth::Infinity => return Err((400, None, None)),
} }
} }
pub async fn handle_mkcol( pub async fn handle_proppatch(
&self, &self,
path: String, path: String,
req_body: String, req_body: String,
) -> Result<DavResponse, DavErrResponse> {
let mut headers = HashMap::new();
headers.insert(
"Content-Type".to_string(),
"application/xml; charset=utf-8".to_string(),
);
let mut multistatus = XMLBuilder::new(
"D:multistatus".to_string(),
Some(vec![("xmlns:D".to_string(), "DAV:".to_string())]),
None,
);
let response = multistatus.elem("D:response".to_string(), None, None);
response.elem("D:href".to_string(), None, Some(path));
let propstat = response.elem("D:propstat".to_string(), None, None);
let prop = propstat.elem("D:prop".to_string(), None, None);
// TODO
propstat.elem(
"D:status".to_string(),
None,
Some("HTTP/1.1 200 OK".to_string()),
);
Ok((207, HashMap::new(), multistatus.build()))
}
pub async fn handle_mkcol(
&self,
_: String,
req_body: String,
) -> Result<DavResponse, DavErrResponse> { ) -> Result<DavResponse, DavErrResponse> {
if req_body.len() > 0 { if req_body.len() > 0 {
return Err((415, None, None)); return Err((415, None, None));
@ -233,7 +281,7 @@ impl Dav {
_ => Ok((200, (headers), stream)), _ => Ok((200, (headers), stream)),
} }
} }
Err(_) => return Err((404, None, None)), Err(message) => return Err((404, None, Some(message))),
} }
} }
@ -261,7 +309,9 @@ impl Dav {
.join(", "); .join(", ");
return Ok((200, (headers), (html))); return Ok((200, (headers), (html)));
} }
Err(_) => return Err((404, None, None)), Err(message) => {
return Err((404, None, Some(message)));
}
} }
} }

View File

@ -1,6 +1,6 @@
use crate::values::Depth; use crate::values::Depth;
use base64; use base64;
use dav::DavResponseType; use dav::{DavErrResponse, DavResponse, DavResponseType, DavStreamResponse};
use r2::R2; use r2::R2;
use values::Overwrite; use values::Overwrite;
use worker::*; use worker::*;
@ -11,7 +11,7 @@ mod values;
mod xml; mod xml;
#[event(fetch)] #[event(fetch)]
async fn main(req: Request, env: Env, _: Context) -> Result<Response> { async fn main(mut req: Request, env: Env, _: Context) -> Result<Response> {
let username = env.var("USERNAME").unwrap().to_string(); let username = env.var("USERNAME").unwrap().to_string();
let password = env.var("PASSWORD").unwrap().to_string(); let password = env.var("PASSWORD").unwrap().to_string();
let protocol = env.var("PROTOCOL").unwrap().to_string(); let protocol = env.var("PROTOCOL").unwrap().to_string();
@ -29,7 +29,103 @@ async fn main(req: Request, env: Env, _: Context) -> Result<Response> {
"r2" => R2::new(env.bucket(bucket_name.as_str()).unwrap()), "r2" => R2::new(env.bucket(bucket_name.as_str()).unwrap()),
_ => panic!("PROTOCOL {} not supported", protocol), _ => panic!("PROTOCOL {} not supported", protocol),
}); });
worker(req, dav).await
let mut response = match match req.inner().method().as_str() {
"PROPFIND" => {
let request_body = req.text().await?;
console_debug!("request_body {:?}", request_body);
dav.handle_propfind(req.path(), parse_depth(&req), request_body)
.await
.into()
}
"PROPPATCH" => {
let request_body = req.text().await?;
console_debug!("request_body {:?}", request_body);
dav.handle_proppatch(req.path(), request_body).await.into()
}
"OPTIONS" => dav.handle_options().await.into(),
"MKCOL" => dav.handle_mkcol(req.path(), req.text().await?).await.into(),
"GET" => {
if req.path().ends_with("/") {
dav.handle_get_dir(req.path()).await.into()
} else {
dav.handle_get_obj(req.path(), parse_range(&req))
.await
.into()
}
}
"HEAD" => {
if req.path().ends_with("/") {
dav.handle_head_dir(req.path()).await.into()
} else {
dav.handle_head_obj(req.path(), parse_range(&req))
.await
.into()
}
}
"DELETE" => dav.handle_delete(req.path()).await.into(),
"PUT" => dav
.handle_put(
req.path(),
req.stream().unwrap(),
req.headers()
.get("content-length")
.unwrap()
.map_or(0, |v| v.parse::<u64>().unwrap()),
)
.await
.into(),
"COPY" => dav
.handle_copy(
req.path(),
parse_destination(&req),
parse_depth(&req),
parse_overwrite(&req),
)
.await
.into(),
"MOVE" => dav
.handle_move(
req.path(),
parse_destination(&req),
parse_depth(&req),
parse_overwrite(&req),
)
.await
.into(),
_ => dav.handle_unsupport_method().await.into(),
} {
DavResponseType::DavResponse(r) => r.map_or_else(from_dav_err_response, from_dav_response),
DavResponseType::DavStreamResponse(r) => {
r.map_or_else(from_dav_err_response, from_dav_stream_response)
}
};
let cors = Cors::new()
.with_origins(
req.headers()
.get("origin")
.unwrap()
.map_or(vec![], |v| vec![v.to_string()]),
)
.with_methods(Method::all())
.with_allowed_headers([
"authorization",
"content-type",
"depth",
"overwrite",
"destination",
"range",
])
.with_exposed_headers([
"content-length",
"content-type",
"etag",
"last-modified",
"range",
]);
response = response.map(|response| response.with_cors(&cors).unwrap());
response
} }
fn basic_authorization( fn basic_authorization(
@ -47,208 +143,102 @@ fn basic_authorization(
})) }))
}; };
match authorization_header { if let Some(text) = authorization_header {
Some(text) => { let a: Vec<&str> = text.split(" ").collect();
let a: Vec<&str> = text.split(" ").collect(); if a.len() != 2 || a[0] != "Basic" {
if a.len() != 2 || a[0] != "Basic" { return basic_authorization_error_response();
return basic_authorization_error_response();
}
if let Ok(v) = base64::decode(a[1]) {
let v = match String::from_utf8(v) {
Ok(v) => v,
Err(_) => return basic_authorization_error_response(),
};
let v: Vec<&str> = v.split(":").collect();
if v.len() != 2 {
return basic_authorization_error_response();
}
if v[0] != username || v[1] != password {
return basic_authorization_error_response();
}
return None;
} else {
return basic_authorization_error_response();
}
} }
None => { if let Ok(v) = base64::decode(a[1]) {
let v = match String::from_utf8(v) {
Ok(v) => v,
Err(_) => return basic_authorization_error_response(),
};
let v: Vec<&str> = v.split(":").collect();
if v.len() != 2 {
return basic_authorization_error_response();
}
if v[0] != username || v[1] != password {
return basic_authorization_error_response();
}
return None;
} else {
return basic_authorization_error_response(); return basic_authorization_error_response();
} }
} }
return basic_authorization_error_response();
} }
async fn worker(mut req: Request, dav: dav::Dav) -> Result<Response> { fn parse_depth(req: &Request) -> Depth {
let dav_response: DavResponseType = match req.inner().method().as_str() { req.headers()
"PROPFIND" => { .get("depth")
let depth: Depth = req .unwrap()
.headers() .map_or("infinity".to_string(), |v| v)
.get("depth") .into()
.unwrap() }
.map_or("infinity".to_string(), |v| v)
.into(); fn parse_range(req: &Request) -> values::Range {
let resource_path = req.path(); req.headers().get("range").unwrap().map_or(
dav.handle_propfind(resource_path, depth, req.text().await?) values::Range {
.await start: None,
.into() end: None,
} },
"OPTIONS" => dav.handle_options().await.into(), |v| values::Range::from(v.to_string().split("bytes=").next().unwrap().to_string()),
"MKCOL" => { )
let resource_path = req.path(); }
dav.handle_mkcol(resource_path, req.text().await?)
.await fn parse_destination(req: &Request) -> String {
.into() req.headers()
} .get("destination")
"GET" => { .unwrap()
let resource_path = req.path(); .map_or("".to_string(), |v| {
let range = req.headers().get("range").unwrap().map_or( v.split("http://")
values::Range { .nth(1)
start: None, .unwrap()
end: None, .split("/")
}, .skip(1)
|v| values::Range::from(v.to_string().split("bytes=").next().unwrap().to_string()), .collect::<Vec<&str>>()
); .join("/")
if resource_path.ends_with("/") { })
dav.handle_get_dir(resource_path).await.into() }
} else {
dav.handle_get_obj(resource_path, range).await.into() fn parse_overwrite(req: &Request) -> Overwrite {
} req.headers()
} .get("overwrite")
"HEAD" => { .unwrap()
let resource_path = req.path(); .map_or("T".to_string(), |v| v.to_string())
let range = req.headers().get("range").unwrap().map_or( .into()
values::Range { }
start: None,
end: None, fn from_dav_err_response(response: DavErrResponse) -> Result<Response> {
}, let (status_code, headers, body) = response;
|v| values::Range::from(v.to_string().split("bytes=").next().unwrap().to_string()), console_debug!("{} {:?} {:?}", status_code, headers, body);
); Response::error(body.unwrap_or("".to_string()), status_code).map(|response| {
if resource_path.ends_with("/") { match headers {
dav.handle_head_dir(resource_path).await.into() Some(headers) => response.with_headers(Headers::from_iter(headers)),
} else { None => response,
dav.handle_head_obj(resource_path, range).await.into() }
} .with_status(status_code)
} })
"DELETE" => { }
let resource_path = req.path();
dav.handle_delete(resource_path).await.into() fn from_dav_response(response: DavResponse) -> Result<Response> {
} let (status_code, headers, body) = response;
"PUT" => { console_debug!("{} {:?} {:?}", status_code, headers, body);
let resource_path = req.path(); Response::from_bytes(body.into_bytes()).map(|response| {
let content_length = req response
.headers() .with_headers(Headers::from_iter(headers))
.get("content-length") .with_status(status_code)
.unwrap() })
.map_or(0, |v| v.parse::<u64>().unwrap()); }
println!("content-length: {}", content_length);
dav.handle_put(resource_path, req.stream().unwrap(), content_length) fn from_dav_stream_response(response: DavStreamResponse) -> Result<Response> {
.await let (status_code, headers, body) = response;
.into() console_debug!("{} {:?} {:?}", status_code, headers, body);
} Response::from_stream(body).map(|response| {
"COPY" => { response
let resource_path = req.path(); .with_headers(Headers::from_iter(headers))
let destination = .with_status(status_code)
req.headers() })
.get("destination")
.unwrap()
.map_or("".to_string(), |v| {
v.split("http://")
.nth(1)
.unwrap()
.split("/")
.skip(1)
.collect::<Vec<&str>>()
.join("/")
});
let depth: Depth = req
.headers()
.get("depth")
.unwrap()
.map_or("infinity".to_string(), |v| v)
.into();
let overwrite: Overwrite = req
.headers()
.get("overwrite")
.unwrap()
.map_or("T".to_string(), |v| v.to_string())
.into();
dav.handle_copy(resource_path, destination, depth, overwrite)
.await
.into()
}
"MOVE" => {
let resource_path = req.path();
let destination =
req.headers()
.get("destination")
.unwrap()
.map_or("".to_string(), |v| {
v.split("http://")
.nth(1)
.unwrap()
.split("/")
.skip(1)
.collect::<Vec<&str>>()
.join("/")
});
let depth: Depth = req
.headers()
.get("depth")
.unwrap()
.map_or("infinity".to_string(), |v| v)
.into();
let overwrite: Overwrite = req
.headers()
.get("overwrite")
.unwrap()
.map_or("T".to_string(), |v| v.to_string())
.into();
dav.handle_move(resource_path, destination, depth, overwrite)
.await
.into()
}
_ => dav.handle_unsupport_method().await.into(),
};
match dav_response {
DavResponseType::DavResponse(r) => r.map_or_else(
|e| {
let (status_code, headers, body) = e;
Response::error(body.unwrap_or("".to_string()), status_code).map(|response| {
match headers {
Some(headers) => response.with_headers(Headers::from_iter(headers)),
None => response,
}
.with_status(status_code)
})
},
|r| {
let (status_code, headers, body) = r;
Response::from_body(ResponseBody::Body(body.into_bytes())).map(|response| {
response
.with_headers(Headers::from_iter(headers))
.with_status(status_code)
})
},
),
DavResponseType::DavStreamResponse(r) => r.map_or_else(
|e| {
let (status_code, headers, body) = e;
Response::error(body.unwrap_or("".to_string()), status_code).map(|response| {
match headers {
Some(headers) => response.with_headers(Headers::from_iter(headers)),
None => response,
}
.with_status(status_code)
})
},
|r| {
let (status_code, headers, body) = r;
Response::from_stream(body).map(|response| {
response
.with_headers(Headers::from_iter(headers))
.with_status(status_code)
})
},
),
}
} }

View File

@ -1,5 +1,5 @@
use crate::values::{DavProperties, Range}; use crate::values::{DavProperties, Range};
use worker::{Bucket, ByteStream, FixedLengthStream, Range as R2Range}; use worker::{console_debug, Bucket, ByteStream, FixedLengthStream, Headers, Range as R2Range};
pub struct R2 { pub struct R2 {
bucket: Bucket, bucket: Bucket,
@ -11,10 +11,9 @@ impl R2 {
} }
pub async fn get(&self, path: String) -> Result<(String, DavProperties), String> { pub async fn get(&self, path: String) -> Result<(String, DavProperties), String> {
let result = self.bucket.get(path).execute().await; match self.bucket.get(path).execute().await {
match result {
Ok(f) => f.map_or(Err("Resource not found".to_string()), |file| { Ok(f) => f.map_or(Err("Resource not found".to_string()), |file| {
Ok((file.key(), DavProperties::from_r2(&file))) Ok((file.key(), DavProperties::from(&file)))
}), }),
Err(error) => Err(error.to_string()), Err(error) => Err(error.to_string()),
} }
@ -25,7 +24,8 @@ impl R2 {
Ok(files) => { Ok(files) => {
let mut result = Vec::new(); let mut result = Vec::new();
for file in files.objects() { for file in files.objects() {
result.push((file.key(), DavProperties::from_r2(&file))) console_debug!("Access {}", file.key());
result.push((file.key(), DavProperties::from(&file)))
} }
Ok(result) Ok(result)
} }
@ -33,6 +33,18 @@ 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()),
}
}),
Err(error) => Err(error.to_string()),
}
}
pub async fn download( pub async fn download(
&self, &self,
path: String, path: String,
@ -66,7 +78,7 @@ impl R2 {
.map_or(Err("Failed to get file body stream".to_string()), |b| { .map_or(Err("Failed to get file body stream".to_string()), |b| {
b.stream().map_or( b.stream().map_or(
Err("Failed to get file body stream".to_string()), Err("Failed to get file body stream".to_string()),
|stream| Ok((DavProperties::from_r2(&file), stream)), |stream| Ok((DavProperties::from(&file), stream)),
) )
}) })
}), }),
@ -93,7 +105,7 @@ impl R2 {
.execute() .execute()
.await .await
{ {
Ok(file) => Ok(DavProperties::from_r2(&file)), Ok(file) => Ok(DavProperties::from(&file)),
Err(error) => Err(error.to_string()), Err(error) => Err(error.to_string()),
} }
} }

View File

@ -1,4 +1,4 @@
use worker::Object; use worker::{console_debug, Object};
#[derive(Default, Debug, Clone, PartialEq, Hash, Eq)] #[derive(Default, Debug, Clone, PartialEq, Hash, Eq)]
pub enum Depth { pub enum Depth {
@ -90,9 +90,11 @@ pub struct DavProperties {
pub get_last_modified: Option<String>, pub get_last_modified: Option<String>,
} }
impl DavProperties { impl From<&Object> for DavProperties {
pub fn from_r2(file: &Object) -> DavProperties { fn from(file: &Object) -> DavProperties {
console_debug!("Calling from Object for DavProperties");
let http_metedata = file.http_metadata(); let http_metedata = file.http_metadata();
console_debug!("http_metedata {:?}", http_metedata);
DavProperties { DavProperties {
creation_date: Some(file.uploaded().to_string()), creation_date: Some(file.uploaded().to_string()),
display_name: http_metedata.content_disposition, display_name: http_metedata.content_disposition,