webdav propfind integration tests

This commit is contained in:
Quentin 2024-05-22 19:36:27 +02:00
parent 742beeeafb
commit 649a7b8b1b
Signed by: quentin
GPG key ID: E9602264D639FF68
7 changed files with 370 additions and 19 deletions

228
Cargo.lock generated
View file

@ -152,6 +152,7 @@ dependencies = [
name = "aerogramme"
version = "0.3.0"
dependencies = [
"aero-dav",
"aero-proto",
"aero-user",
"anyhow",
@ -160,6 +161,8 @@ dependencies = [
"futures",
"log",
"nix",
"quick-xml",
"reqwest",
"rpassword",
"tokio",
"tracing",
@ -952,6 +955,12 @@ version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64-simd"
version = "0.8.0"
@ -1515,6 +1524,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -1933,6 +1957,22 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper 1.2.0",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.3"
@ -2103,6 +2143,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "iso8601"
version = "0.6.1"
@ -2297,6 +2343,12 @@ version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -2324,6 +2376,24 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nix"
version = "0.27.1"
@ -2435,12 +2505,50 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "openssl"
version = "0.10.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
dependencies = [
"bitflags 2.4.2",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "os_str_bytes"
version = "6.6.1"
@ -2724,6 +2832,49 @@ version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "reqwest"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.4.2",
"http 1.1.0",
"http-body 1.0.0",
"http-body-util",
"hyper 1.2.0",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite 0.2.13",
"rustls-pemfile 2.1.1",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]]
name = "rfc6979"
version = "0.3.1"
@ -3075,6 +3226,18 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha1"
version = "0.10.6"
@ -3299,6 +3462,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "synstructure"
version = "0.12.6"
@ -3311,12 +3480,45 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand 2.0.1",
"rustix 0.38.31",
"windows-sys 0.52.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
@ -3435,6 +3637,16 @@ dependencies = [
"syn 2.0.48",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.23.4"
@ -3678,6 +3890,12 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
@ -3979,6 +4197,16 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winreg"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "wyz"
version = "0.2.0"

View file

@ -66,6 +66,7 @@ http-body-util = "0.1.1"
hyper = "1.2"
hyper-rustls = { version = "0.26", features = ["http2"] }
hyper-util = { version = "0.1", features = ["full"] }
reqwest = { version = "0.12", features = [ "blocking" ]} # for testing purposes only
# serialization, compression & parsing
serde = "1.0.137"

View file

@ -638,10 +638,10 @@ mod tests {
use imap_codec::ResponseCodec;
use std::fs;
use aero_user::cryptoblob;
use aero_collections::mail::mailbox::MailMeta;
use aero_collections::mail::query::QueryResult;
use aero_collections::unique_ident;
use aero_user::cryptoblob;
use crate::imap::index::MailIndex;
use crate::imap::mime_view;

View file

@ -21,6 +21,11 @@ tracing.workspace = true
tracing-subscriber.workspace = true
rpassword.workspace = true
[dev-dependencies]
reqwest.workspace = true
aero-dav.workspace = true
quick-xml.workspace = true
[[test]]
name = "behavior"
path = "tests/behavior.rs"

View file

@ -5,21 +5,25 @@ use crate::common::constants::*;
use crate::common::fragments::*;
fn main() {
rfc3501_imap4rev1_base();
// IMAP
/*rfc3501_imap4rev1_base();
rfc6851_imapext_move();
rfc4551_imapext_condstore();
rfc2177_imapext_idle();
rfc5161_imapext_enable(); // 1
rfc3691_imapext_unselect(); // 2
rfc7888_imapext_literal(); // 3
rfc4315_imapext_uidplus(); // 4
rfc5819_imapext_liststatus(); // 5
rfc5161_imapext_enable();
rfc3691_imapext_unselect();
rfc7888_imapext_literal();
rfc4315_imapext_uidplus();
rfc5819_imapext_liststatus();*/
// WebDAV
rfc4918_webdav_core();
println!("✅ SUCCESS 🌟🚀🥳🙏🥹");
}
fn rfc3501_imap4rev1_base() {
println!("🧪 rfc3501_imap4rev1_base");
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
connect(imap_socket).context("server says hello")?;
capability(imap_socket, Extension::None).context("check server capabilities")?;
login(imap_socket, Account::Alice).context("login test")?;
@ -69,7 +73,7 @@ fn rfc3501_imap4rev1_base() {
fn rfc3691_imapext_unselect() {
println!("🧪 rfc3691_imapext_unselect");
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
connect(imap_socket).context("server says hello")?;
lmtp_handshake(lmtp_socket).context("handshake lmtp done")?;
@ -118,7 +122,7 @@ fn rfc3691_imapext_unselect() {
fn rfc5161_imapext_enable() {
println!("🧪 rfc5161_imapext_enable");
common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket, _dav_socket| {
connect(imap_socket).context("server says hello")?;
login(imap_socket, Account::Alice).context("login test")?;
enable(imap_socket, Enable::Utf8Accept, Some(Enable::Utf8Accept))?;
@ -132,7 +136,7 @@ fn rfc5161_imapext_enable() {
fn rfc6851_imapext_move() {
println!("🧪 rfc6851_imapext_move");
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
connect(imap_socket).context("server says hello")?;
capability(imap_socket, Extension::Move).context("check server capabilities")?;
@ -174,7 +178,7 @@ fn rfc6851_imapext_move() {
fn rfc7888_imapext_literal() {
println!("🧪 rfc7888_imapext_literal");
common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, _lmtp_socket, _dav_socket| {
connect(imap_socket).context("server says hello")?;
capability(imap_socket, Extension::LiteralPlus).context("check server capabilities")?;
@ -187,7 +191,7 @@ fn rfc7888_imapext_literal() {
fn rfc4551_imapext_condstore() {
println!("🧪 rfc4551_imapext_condstore");
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
// Setup the test
connect(imap_socket).context("server says hello")?;
@ -245,7 +249,7 @@ fn rfc4551_imapext_condstore() {
fn rfc2177_imapext_idle() {
println!("🧪 rfc2177_imapext_idle");
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
// Test setup, check capability
connect(imap_socket).context("server says hello")?;
capability(imap_socket, Extension::Idle).context("check server capabilities")?;
@ -266,7 +270,7 @@ fn rfc2177_imapext_idle() {
fn rfc4315_imapext_uidplus() {
println!("🧪 rfc4315_imapext_uidplus");
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
// Test setup, check capability, insert 2 emails
connect(imap_socket).context("server says hello")?;
capability(imap_socket, Extension::UidPlus).context("check server capabilities")?;
@ -320,7 +324,7 @@ fn rfc4315_imapext_uidplus() {
/// ```
fn rfc5819_imapext_liststatus() {
println!("🧪 rfc5819_imapext_liststatus");
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket| {
common::aerogramme_provider_daemon_dev(|imap_socket, lmtp_socket, _dav_socket| {
// Test setup, check capability, add 2 emails, read 1
connect(imap_socket).context("server says hello")?;
capability(imap_socket, Extension::ListStatus).context("check server capabilities")?;
@ -355,3 +359,94 @@ fn rfc5819_imapext_liststatus() {
})
.expect("test fully run");
}
use aero_dav::caltypes as cal;
use aero_dav::realization::All;
use aero_dav::types as dav;
use crate::common::dav_deserialize;
fn rfc4918_webdav_core() {
println!("🧪 rfc4918_webdav_core");
common::aerogramme_provider_daemon_dev(|_imap, _lmtp, http| {
// --- PROPFIND ---
// empty request body (assume "allprop")
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").send()?.text()?;
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
let root_propstats = multistatus.responses.iter()
.find_map(|v| match &v.status_or_propstat {
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x),
_ => None,
})
.expect("propstats for root must exist");
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
let display_name = root_success.prop.0.iter()
.find_map(|v| match v { dav::AnyProperty::Value(dav::Property::DisplayName(x)) => Some(x), _ => None } )
.expect("root has a display name");
let content_type = root_success.prop.0.iter()
.find_map(|v| match v { dav::AnyProperty::Value(dav::Property::GetContentType(x)) => Some(x), _ => None } )
.expect("root has a content type");
let resource_type = root_success.prop.0.iter()
.find_map(|v| match v { dav::AnyProperty::Value(dav::Property::ResourceType(x)) => Some(x), _ => None } )
.expect("root has a resource type");
assert_eq!(display_name, "DAV Root");
assert_eq!(content_type, "httpd/unix-directory");
assert_eq!(resource_type, &[ dav::ResourceType::Collection ]);
// propname
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><propname/></propfind>"#;
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").body(propfind_req).send()?.text()?;
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
let root_propstats = multistatus.responses.iter()
.find_map(|v| match &v.status_or_propstat {
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x),
_ => None,
})
.expect("propstats for root must exist");
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::DisplayName))).is_some());
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::ResourceType))).is_some());
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::GetContentType))).is_some());
// list of properties
let propfind_req = r#"<?xml version="1.0" encoding="utf-8" ?><propfind xmlns="DAV:"><prop><displayname/><getcontentlength/></prop></propfind>"#;
let body = http.request(reqwest::Method::from_bytes(b"PROPFIND")?, "http://localhost:8087").body(propfind_req).send()?.text()?;
let multistatus = dav_deserialize::<dav::Multistatus<All>>(&body);
let root_propstats = multistatus.responses.iter()
.find_map(|v| match &v.status_or_propstat {
dav::StatusOrPropstat::PropStat(dav::Href(p), x) if p.as_str() == "/" => Some(x),
_ => None,
})
.expect("propstats for root must exist");
let root_success = root_propstats.iter().find(|p| p.status.0.as_u16() == 200).expect("some propstats for root must be 200");
let root_not_found = root_propstats.iter().find(|p| p.status.0.as_u16() == 404).expect("some propstats for root must be not found");
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::DisplayName(x)) if x == "DAV Root")).is_some());
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::ResourceType(_)))).is_none());
assert!(root_success.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Value(dav::Property::GetContentType(_)))).is_none());
assert!(root_not_found.prop.0.iter().find(|p| matches!(p, dav::AnyProperty::Request(dav::PropertyRequest::GetContentLength))).is_some());
// depth 1
// check tree (calendar, Personal)
// --- PUT ---
// --- GET ---
// --- DELETE ---
Ok(())
})
.expect("test fully run");
}
// @TODO ACL
// @TODO CALDAV
// @TODO SYNC

View file

@ -8,10 +8,13 @@ use std::net::{Shutdown, TcpStream};
use std::process::Command;
use std::thread;
use reqwest::blocking::Client;
use reqwest::header;
use constants::SMALL_DELAY;
pub fn aerogramme_provider_daemon_dev(
mut fx: impl FnMut(&mut TcpStream, &mut TcpStream) -> Result<()>,
mut fx: impl FnMut(&mut TcpStream, &mut TcpStream, &mut Client) -> Result<()>,
) -> Result<()> {
// Check port is not used (= free) before starting the test
let mut max_retry = 20;
@ -53,8 +56,15 @@ pub fn aerogramme_provider_daemon_dev(
let mut lmtp_socket =
TcpStream::connect("[::1]:1025").context("lmtp socket must be connected")?;
println!("-- ready to test imap features --");
let result = fx(&mut imap_socket, &mut lmtp_socket);
let mut headers = header::HeaderMap::new();
headers.insert(
header::AUTHORIZATION,
header::HeaderValue::from_static("Basic YWxpY2U6aHVudGVyMg=="),
);
let mut http_client = Client::builder().default_headers(headers).build()?;
println!("-- ready to test features --");
let result = fx(&mut imap_socket, &mut lmtp_socket, &mut http_client);
println!("-- test teardown --");
imap_socket
@ -97,3 +107,13 @@ pub fn read_first_u32(inp: &str) -> Result<u32> {
.collect::<String>()
.parse::<u32>()?)
}
use aero_dav::xml::{Node, Reader};
pub fn dav_deserialize<T: Node<T>>(src: &str) -> T {
futures::executor::block_on(async {
let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes()))
.await
.expect("build reader");
rdr.find().await.expect("parse XML")
})
}

View file

@ -185,6 +185,8 @@
# Shell
shell = gpkgs.mkShell {
buildInputs = [
gpkgs.openssl
gpkgs.pkg-config
cargo2nix.packages.x86_64-linux.default
fenix.packages.x86_64-linux.complete.toolchain
#fenix.packages.x86_64-linux.rust-analyzer