Improved topological sorting

This commit is contained in:
mdecimus
2025-03-04 15:50:53 +01:00
parent 1c460c7f3b
commit 3554dea4cb
43 changed files with 1230 additions and 313 deletions

View File

@@ -8,7 +8,9 @@ resolver = "2"
dav-proto = { path = "/Users/me/code/dav-proto" }
common = { path = "../common" }
groupware = { path = "../groupware" }
directory = { path = "../directory" }
http_proto = { path = "../http-proto" }
trc = { path = "../trc" }
hashify = { version = "0.2" }
hyper = { version = "1.0.1", features = ["server", "http1", "http2"] }

View File

@@ -0,0 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/

View File

@@ -0,0 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::request::Acl};
use http_proto::HttpResponse;
pub(crate) trait FileAclRequestHandler: Sync + Send {
fn handle_file_acl_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: Acl,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FileAclRequestHandler for Server {
async fn handle_file_acl_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: Acl,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::request::SyncCollection};
use http_proto::HttpResponse;
pub(crate) trait FileChangesRequestHandler: Sync + Send {
fn handle_file_changes_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: SyncCollection,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FileChangesRequestHandler for Server {
async fn handle_file_changes_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: SyncCollection,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::RequestHeaders;
use http_proto::HttpResponse;
pub(crate) trait FileCopyMoveRequestHandler: Sync + Send {
fn handle_file_copy_move_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
is_move: bool,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FileCopyMoveRequestHandler for Server {
async fn handle_file_copy_move_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
is_move: bool,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::RequestHeaders;
use http_proto::HttpResponse;
pub(crate) trait FileDeleteRequestHandler: Sync + Send {
fn handle_file_delete_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FileDeleteRequestHandler for Server {
async fn handle_file_delete_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::RequestHeaders;
use http_proto::HttpResponse;
pub(crate) trait FileGetRequestHandler: Sync + Send {
fn handle_file_get_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
is_head: bool,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FileGetRequestHandler for Server {
async fn handle_file_get_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
is_head: bool,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::request::LockInfo};
use http_proto::HttpResponse;
pub(crate) trait FileLockRequestHandler: Sync + Send {
fn handle_file_lock_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: Option<LockInfo>,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FileLockRequestHandler for Server {
async fn handle_file_lock_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: Option<LockInfo>,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::request::MkCol};
use http_proto::HttpResponse;
pub(crate) trait FileMkColRequestHandler: Sync + Send {
fn handle_file_mkcol_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: Option<MkCol>,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FileMkColRequestHandler for Server {
async fn handle_file_mkcol_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: Option<MkCol>,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod acl;
pub mod changes;
pub mod copy_move;
pub mod delete;
pub mod get;
pub mod lock;
pub mod mkcol;
pub mod propfind;
pub mod proppatch;
pub mod update;
pub(crate) enum UpdateType {
Post(Vec<u8>),
Put(Vec<u8>),
Patch(Vec<u8>),
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::request::PropFind};
use http_proto::HttpResponse;
pub(crate) trait FilePropFindRequestHandler: Sync + Send {
fn handle_file_propfind_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: PropFind,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FilePropFindRequestHandler for Server {
async fn handle_file_propfind_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: PropFind,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::request::PropertyUpdate};
use http_proto::HttpResponse;
pub(crate) trait FilePropPatchRequestHandler: Sync + Send {
fn handle_file_proppatch_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: PropertyUpdate,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FilePropPatchRequestHandler for Server {
async fn handle_file_proppatch_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: PropertyUpdate,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, auth::AccessToken};
use dav_proto::{RequestHeaders, schema::request::SyncCollection};
use http_proto::HttpResponse;
use super::UpdateType;
pub(crate) trait FileUpdateRequestHandler: Sync + Send {
fn handle_file_update_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: UpdateType,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl FileUpdateRequestHandler for Server {
async fn handle_file_update_request(
&self,
access_token: &AccessToken,
headers: RequestHeaders<'_>,
request: UpdateType,
) -> crate::Result<HttpResponse> {
todo!()
}
}

View File

@@ -4,16 +4,24 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod calendar;
pub mod card;
pub mod file;
pub mod principal;
pub mod request;
use dav_proto::schema::request::Report;
use http_proto::HttpResponse;
use hyper::{Method, StatusCode};
pub(crate) type Result<T> = std::result::Result<T, DavError>;
#[derive(Debug, Clone, Copy)]
pub enum DavResource {
Card,
Cal,
File,
Principal,
}
#[derive(Debug, Clone, Copy)]
@@ -22,6 +30,7 @@ pub enum DavMethod {
PUT,
POST,
DELETE,
HEAD,
PATCH,
PROPFIND,
PROPPATCH,
@@ -32,6 +41,13 @@ pub enum DavMethod {
LOCK,
UNLOCK,
OPTIONS,
ACL,
}
pub(crate) enum DavError {
Parse(dav_proto::parser::Error),
Internal(trc::Error),
UnsupportedReport(Report),
}
impl DavResource {
@@ -39,7 +55,8 @@ impl DavResource {
hashify::tiny_map!(service.as_bytes(),
"card" => DavResource::Card,
"cal" => DavResource::Cal,
"file" => DavResource::File
"file" => DavResource::File,
"pri" => DavResource::Principal,
)
}
@@ -59,6 +76,7 @@ impl DavMethod {
Method::OPTIONS => Some(DavMethod::OPTIONS),
Method::POST => Some(DavMethod::POST),
Method::PATCH => Some(DavMethod::PATCH),
Method::HEAD => Some(DavMethod::HEAD),
_ => {
hashify::tiny_map!(method.as_str().as_bytes(),
"PROPFIND" => DavMethod::PROPFIND,
@@ -68,7 +86,8 @@ impl DavMethod {
"COPY" => DavMethod::COPY,
"MOVE" => DavMethod::MOVE,
"LOCK" => DavMethod::LOCK,
"UNLOCK" => DavMethod::UNLOCK
"UNLOCK" => DavMethod::UNLOCK,
"ACL" => DavMethod::ACL
)
}
}
@@ -84,9 +103,8 @@ impl DavMethod {
| DavMethod::PROPPATCH
| DavMethod::PROPFIND
| DavMethod::REPORT
| DavMethod::MKCOL
| DavMethod::COPY
| DavMethod::MOVE
| DavMethod::LOCK
| DavMethod::ACL
)
}
}

View File

@@ -0,0 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/

View File

@@ -7,9 +7,25 @@
use std::sync::Arc;
use common::{Server, auth::AccessToken};
use http_proto::{HttpRequest, HttpResponse, HttpSessionData};
use dav_proto::{
RequestHeaders,
parser::{DavParser, tokenizer::Tokenizer},
schema::request::{Acl, LockInfo, MkCol, PropFind, PropertyUpdate, Report},
};
use directory::Permission;
use http_proto::{HttpRequest, HttpResponse, HttpSessionData, request::fetch_body};
use hyper::{StatusCode, header};
use crate::{DavMethod, DavResource};
use crate::{
DavError, DavMethod, DavResource,
file::{
UpdateType, acl::FileAclRequestHandler, changes::FileChangesRequestHandler,
copy_move::FileCopyMoveRequestHandler, delete::FileDeleteRequestHandler,
get::FileGetRequestHandler, lock::FileLockRequestHandler, mkcol::FileMkColRequestHandler,
propfind::FilePropFindRequestHandler, proppatch::FilePropPatchRequestHandler,
update::FileUpdateRequestHandler,
},
};
pub trait DavRequestHandler: Sync + Send {
fn handle_dav_request(
@@ -19,20 +35,206 @@ pub trait DavRequestHandler: Sync + Send {
session: &HttpSessionData,
resource: DavResource,
method: DavMethod,
body: Vec<u8>,
) -> impl Future<Output = HttpResponse> + Send;
}
pub(crate) trait DavRequestDispatcher: Sync + Send {
fn dispatch_dav_request(
&self,
request: &HttpRequest,
access_token: Arc<AccessToken>,
resource: DavResource,
method: DavMethod,
body: Vec<u8>,
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
}
impl DavRequestDispatcher for Server {
async fn dispatch_dav_request(
&self,
request: &HttpRequest,
access_token: Arc<AccessToken>,
resource: DavResource,
method: DavMethod,
body: Vec<u8>,
) -> crate::Result<HttpResponse> {
// Parse headers
let mut headers = RequestHeaders::new(request.uri().path());
for (key, value) in request.headers() {
headers.parse(key.as_str(), value.to_str().unwrap_or_default());
}
// Dispatch
match resource {
DavResource::Card => {
todo!()
}
DavResource::Cal => {
todo!()
}
DavResource::Principal => {
todo!()
}
DavResource::File => match method {
DavMethod::PROPFIND => {
self.handle_file_propfind_request(
&access_token,
headers,
PropFind::parse(&mut Tokenizer::new(&body))?,
)
.await
}
DavMethod::PROPPATCH => {
self.handle_file_proppatch_request(
&access_token,
headers,
PropertyUpdate::parse(&mut Tokenizer::new(&body))?,
)
.await
}
DavMethod::MKCOL => {
self.handle_file_mkcol_request(
&access_token,
headers,
if !body.is_empty() {
Some(MkCol::parse(&mut Tokenizer::new(&body))?)
} else {
None
},
)
.await
}
DavMethod::GET => {
self.handle_file_get_request(&access_token, headers, false)
.await
}
DavMethod::HEAD => {
self.handle_file_get_request(&access_token, headers, true)
.await
}
DavMethod::DELETE => {
self.handle_file_delete_request(&access_token, headers)
.await
}
DavMethod::PUT => {
self.handle_file_update_request(&access_token, headers, UpdateType::Put(body))
.await
}
DavMethod::POST => {
self.handle_file_update_request(&access_token, headers, UpdateType::Post(body))
.await
}
DavMethod::PATCH => {
self.handle_file_update_request(&access_token, headers, UpdateType::Patch(body))
.await
}
DavMethod::COPY => {
self.handle_file_copy_move_request(&access_token, headers, false)
.await
}
DavMethod::MOVE => {
self.handle_file_copy_move_request(&access_token, headers, true)
.await
}
DavMethod::LOCK => {
self.handle_file_lock_request(
&access_token,
headers,
LockInfo::parse(&mut Tokenizer::new(&body))?.into(),
)
.await
}
DavMethod::UNLOCK => {
self.handle_file_lock_request(&access_token, headers, None)
.await
}
DavMethod::ACL => {
self.handle_file_acl_request(
&access_token,
headers,
Acl::parse(&mut Tokenizer::new(&body))?,
)
.await
}
DavMethod::REPORT => match Report::parse(&mut Tokenizer::new(&body))? {
Report::SyncCollection(sync_collection) => {
self.handle_file_changes_request(&access_token, headers, sync_collection)
.await
}
report => Err(DavError::UnsupportedReport(report)),
},
DavMethod::OPTIONS => unreachable!(),
},
}
}
}
impl DavRequestHandler for Server {
async fn handle_dav_request(
&self,
request: HttpRequest,
mut request: HttpRequest,
access_token: Arc<AccessToken>,
session: &HttpSessionData,
resource: DavResource,
method: DavMethod,
body: Vec<u8>,
) -> HttpResponse {
todo!()
let body = if method.has_body()
|| request
.headers()
.get(header::CONTENT_LENGTH)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<u64>().ok())
.is_some_and(|len| len > 0)
{
if let Some(body) = fetch_body(
&mut request,
if !access_token.has_permission(Permission::UnlimitedUploads) {
self.core.dav.max_request_size
} else {
0
},
session.session_id,
)
.await
{
body
} else {
trc::event!(
Limit(trc::LimitEvent::SizeRequest),
SpanId = session.session_id,
Contents = "Request body too large",
);
return HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE);
}
} else {
Vec::new()
};
match self
.dispatch_dav_request(&request, access_token, resource, method, body)
.await
{
Ok(response) => response,
Err(DavError::Internal(err)) => {
trc::error!(err.span_id(session.session_id));
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
}
Err(DavError::UnsupportedReport(report)) => HttpResponse::new(StatusCode::BAD_REQUEST),
Err(DavError::Parse(err)) => HttpResponse::new(StatusCode::BAD_REQUEST),
}
}
}
impl From<dav_proto::parser::Error> for DavError {
fn from(err: dav_proto::parser::Error) -> Self {
DavError::Parse(err)
}
}
impl From<trc::Error> for DavError {
fn from(err: trc::Error) -> Self {
DavError::Internal(err)
}
}