From 48c2945172b88c35c187d298a35bf26716af4e91 Mon Sep 17 00:00:00 2001 From: "Jannis M. Hoffmann" Date: Thu, 21 Nov 2024 21:14:40 +0100 Subject: Switch to varlink as IPC protocol This is a lot! Whole new design on top of a statefult varlink interface. You can now handle multiple request response cycles over a single connection. The error responses are lot more refined than just status codes with optional messages and finally part of the protocol. TODO: A lot of error handling needs to be improved. --- .gitignore | 2 +- Cargo.lock | 436 ++++------ Cargo.toml | 12 +- build.rs | 11 - jwebmail.proto | 165 ---- src/arguments.rs | 25 - src/cmd.rs | 114 ++- src/cmd/add_folder.rs | 53 +- src/cmd/count.rs | 27 - src/cmd/folders.rs | 35 +- src/cmd/init.rs | 35 + src/cmd/list.rs | 158 ++-- src/cmd/move.rs | 19 + src/cmd/move_mail.rs | 18 - src/cmd/raw.rs | 185 ++-- src/cmd/read.rs | 24 - src/cmd/remove.rs | 29 +- src/cmd/search.rs | 17 + src/cmd/show.rs | 26 + src/cmd/stats.rs | 25 + src/de.jmhoffmann.jwebmail.mail-storage.varlink | 86 ++ src/de_jmhoffmann_jwebmail_mail-storage.rs | 1035 +++++++++++++++++++++++ src/error.rs | 47 - src/main.rs | 128 +-- src/pb3/mod.rs | 3 - src/rfc822.rs | 365 ++++---- 26 files changed, 1988 insertions(+), 1092 deletions(-) delete mode 100644 build.rs delete mode 100644 jwebmail.proto delete mode 100644 src/arguments.rs delete mode 100644 src/cmd/count.rs create mode 100644 src/cmd/init.rs create mode 100644 src/cmd/move.rs delete mode 100644 src/cmd/move_mail.rs delete mode 100644 src/cmd/read.rs create mode 100644 src/cmd/search.rs create mode 100644 src/cmd/show.rs create mode 100644 src/cmd/stats.rs create mode 100644 src/de.jmhoffmann.jwebmail.mail-storage.varlink create mode 100644 src/de_jmhoffmann_jwebmail_mail-storage.rs delete mode 100644 src/error.rs delete mode 100644 src/pb3/mod.rs diff --git a/.gitignore b/.gitignore index b57a302..d06aa5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ target/ -src/pb3/jwebmail.rs +src/de_jmhoffmann_jwebmail_mail-storage.rs diff --git a/Cargo.lock b/Cargo.lock index 4592685..aa92063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -27,60 +18,14 @@ dependencies = [ ] [[package]] -name = "anstream" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" - -[[package]] -name = "anstyle-parse" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.1" +name = "ansi_term" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", + "winapi", ] -[[package]] -name = "anyhow" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - [[package]] name = "autocfg" version = "1.3.0" @@ -114,12 +59,24 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chainerror" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce1bb7fb0c258a6600d699950da347a7a9dad66c3ce815769b5f11cf8fce78e" + [[package]] name = "charset" version = "0.1.5" @@ -144,52 +101,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "clap" -version = "4.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" - -[[package]] -name = "colorchoice" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -211,27 +122,15 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - [[package]] name = "encoding_rs" version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "errno" version = "0.3.9" @@ -259,24 +158,12 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.14.5" +name = "getopts" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "windows-sys 0.52.0", + "unicode-width", ] [[package]] @@ -302,22 +189,6 @@ dependencies = [ "cc", ] -[[package]] -name = "indexmap" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "itoa" version = "1.0.11" @@ -337,16 +208,18 @@ dependencies = [ name = "jwebmail-extract" version = "0.8.0" dependencies = [ - "anyhow", + "base64", "chrono", - "clap", "libc", "log", "maildir", "mailparse", - "protobuf", - "protobuf-codegen", + "serde", + "serde_derive", + "serde_json", "simplelog", + "varlink", + "varlink_generator", ] [[package]] @@ -394,6 +267,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -425,69 +307,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "powerfmt" -version = "0.2.0" +name = "peg" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" dependencies = [ - "unicode-ident", + "peg-macros", + "peg-runtime", ] [[package]] -name = "protobuf" -version = "3.5.1" +name = "peg-macros" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bcc343da15609eaecd65f8aa76df8dc4209d325131d8219358c0aaaebab0bf6" +checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" dependencies = [ - "once_cell", - "protobuf-support", - "thiserror", + "peg-runtime", + "proc-macro2", + "quote", ] [[package]] -name = "protobuf-codegen" -version = "3.5.1" +name = "peg-runtime" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d0cde5642ea4df842b13eb9f59ea6fafa26dcb43e3e1ee49120e9757556189" -dependencies = [ - "anyhow", - "once_cell", - "protobuf", - "protobuf-parse", - "regex", - "tempfile", - "thiserror", -] +checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" [[package]] -name = "protobuf-parse" -version = "3.5.1" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b0e9b447d099ae2c4993c0cbb03c7a9d6c937b17f2d56cfc0b1550e6fcfdb76" -dependencies = [ - "anyhow", - "indexmap", - "log", - "protobuf", - "protobuf-support", - "tempfile", - "thiserror", - "which", -] +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "protobuf-support" -version = "3.5.1" +name = "proc-macro2" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0766e3675a627c327e4b3964582594b0e8741305d628a98a5de75a1d15f99b9" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ - "thiserror", + "unicode-ident", ] [[package]] @@ -505,35 +363,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" -[[package]] -name = "regex" -version = "1.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - [[package]] name = "rustix" version = "0.38.36" @@ -547,24 +376,42 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "serde" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", ] [[package]] @@ -585,16 +432,21 @@ dependencies = [ ] [[package]] -name = "strsim" -version = "0.11.1" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "syn" -version = "2.0.77" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -607,7 +459,7 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fastrand", "once_cell", "rustix", @@ -623,26 +475,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thiserror" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "time" version = "0.3.36" @@ -676,6 +508,17 @@ dependencies = [ "time-core", ] +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -683,10 +526,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "utf8parse" -version = "0.2.2" +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unix_socket" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" +dependencies = [ + "cfg-if 0.1.10", + "libc", +] + +[[package]] +name = "varlink" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409e275987d74665c23610c0959c133360cafd761c1a6ddb1ca6d0685c8cef5d" +dependencies = [ + "libc", + "serde", + "serde_derive", + "serde_json", + "tempfile", + "uds_windows", + "unix_socket", + "winapi", +] + +[[package]] +name = "varlink_generator" +version = "10.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8ff746c5b65d4bfb3a50f630b85cfb6a9d59f18720126e3ebd6bc98527fa51" +dependencies = [ + "chainerror", + "getopts", + "proc-macro2", + "quote", + "syn 1.0.109", + "varlink_parser", +] + +[[package]] +name = "varlink_parser" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb9f3c1e8ccb33cdb6c84a4477ef3f3884ce6f4b70514ef1fbf7686eae921e" +dependencies = [ + "ansi_term", + "chainerror", + "peg", +] [[package]] name = "wasm-bindgen" @@ -694,7 +588,7 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "wasm-bindgen-macro", ] @@ -710,7 +604,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -732,7 +626,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -743,18 +637,6 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 91de65b..9a4795d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,15 +16,17 @@ codegen-units = 1 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] -anyhow = "1.0" -protobuf-codegen = "3.4" +varlink_generator = "10.1.0" [dependencies] chrono = "0.4" -clap = { version = "4.5", features = ["derive"] } libc = "0.2" log = "0.4" +simplelog = "0.12" maildir = "0.6" mailparse = "0.14" -protobuf = "3.4" -simplelog = "0.12" +varlink = "11.0.1" +serde = "1.0.215" +serde_json = "1.0.133" +serde_derive = "1.0.215" +base64 = "0.22.1" diff --git a/build.rs b/build.rs deleted file mode 100644 index e8ef3be..0000000 --- a/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -use anyhow::Result; -use protobuf_codegen::Codegen; - -fn main() -> Result<()> { - Codegen::new() - .protoc() - .include(".") - .input("jwebmail.proto") - .out_dir("src/pb3") - .run() -} diff --git a/jwebmail.proto b/jwebmail.proto deleted file mode 100644 index e4cba3b..0000000 --- a/jwebmail.proto +++ /dev/null @@ -1,165 +0,0 @@ -syntax = "proto3"; - -package jwebmail; - -message MIMEHeader { - - enum ContentDisposition { - CONTENT_DISPOSITION_NONE = 0; - CONTENT_DISPOSITION_INLINE = 1; - CONTENT_DISPOSITION_ATTACHMENT = 2; - } - - string maintype = 1; - string subtype = 2; - ContentDisposition contentdispo = 3; - optional string file_name = 4; -} - -message MailHeader { - - message MailAddr { - optional string name = 1; - string address = 2; - } - - string send_date = 1; - repeated MailAddr written_from = 2; - optional MailAddr sender = 3; - repeated MailAddr reply_to = 4; - repeated MailAddr send_to = 5; - repeated MailAddr cc = 6; - repeated MailAddr bcc = 7; - string subject = 8; - repeated string comments = 9; - repeated string keywords = 10; - MIMEHeader mime = 11; -} - -message ListMailHeader { - uint64 byte_size = 1; - bool unread = 2; - string rec_date = 3; - string mid = 4; - MailHeader header = 5; -} - -message MailBody { - message Multipart { - optional string preamble = 1; - repeated MIMEPart parts = 2; - optional string epilogue = 3; - } - - oneof Body { - string discrete = 1; - Multipart multipart = 2; - Mail mail = 3; - } -} - -message Mail { - MailHeader head = 1; - MailBody body = 2; -} - -message MIMEPart { - MIMEHeader mime_header = 1; - MailBody body = 2; -} - -// Request-Response pairs - -message ListReq { - string folder = 1; - int32 start = 2; - int32 end = 3; - string sort = 4; -} - -message ListResp { - repeated ListMailHeader mail_heads = 1; -} - -message StatsReq { - string folder = 1; -} - -message StatsResp { - uint32 mail_count = 1; - uint32 unread_count = 2; - uint64 byte_size = 3; -} - -message ShowReq { - string folder = 1; - string mid = 2; -} - -message ShowResp { - Mail mail = 1; -} - -message RawReq { - string folder = 1; - string mid = 2; - optional string path = 3; -} - -message RawResp { - MIMEHeader header = 1; - bytes body = 2; -} - -message SearchReq { - string folder = 1; - string pattern = 2; -} - -message SearchResp { - repeated ListMailHeader found = 1; -} - -message FoldersReq { -} - -message FoldersResp { - repeated string folders = 1; -} - -message MoveReq { - string mid = 1; - string from_f = 2; - string to_f = 3; -} - -message MoveResp { -} - -message RemoveReq { - string folder = 1; - string mid = 2; -} - -message RemoveResp { -} - -message AddFolderReq { - string name = 1; -} - -message AddFolderResp { - int32 status = 1; -} - -service MailService { - rpc List(ListReq) returns (ListResp); - rpc Stats(StatsReq) returns (StatsResp); - rpc Show(ShowReq) returns (ShowResp); - rpc Raw(RawReq) returns (RawResp); - rpc Search(SearchReq) returns (SearchResp); - rpc Folders(FoldersReq) returns (FoldersResp); - rpc Move(MoveReq) returns (MoveResp); - rpc Remove(RemoveReq) returns (RemoveResp); - rpc AddFolder(AddFolderReq) returns (AddFolderResp); -} diff --git a/src/arguments.rs b/src/arguments.rs deleted file mode 100644 index a944abe..0000000 --- a/src/arguments.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::path::PathBuf; - -use clap::{Parser, ValueEnum}; - -#[derive(Clone, ValueEnum)] -pub enum Mode { - List, - Search, - Count, - Read, - Raw, - Folders, - Move, - Remove, - AddFolder, -} - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -pub struct Arguments { - pub maildir_path: PathBuf, - pub sys_user: String, - pub mail_user: String, - pub mode: Mode, -} diff --git a/src/cmd.rs b/src/cmd.rs index 201f498..576c9d4 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -1,24 +1,30 @@ use std::path::PathBuf; +use std::sync::RwLock; +use crate::de_jmhoffmann_jwebmail_mailstorage::*; use maildir::Maildir; mod add_folder; -mod count; mod folders; +mod init; mod list; -mod move_mail; +mod r#move; mod raw; -mod read; mod remove; +mod show; +mod stats; +mod search; -pub use add_folder::add_folder; -pub use count::count; -pub use folders::folders; -pub use list::list; -pub use move_mail::move_mail; -pub use raw::raw; -pub use read::read; -pub use remove::remove; +use add_folder::add_folder; +use folders::folders; +use init::init; +use list::list; +use r#move::r#move; +use raw::raw; +use remove::remove; +use show::show; +use stats::stats; +use search::search; pub fn open_submaildir(mut path: PathBuf, sub: &str) -> Maildir { if !sub.is_empty() { @@ -26,3 +32,89 @@ pub fn open_submaildir(mut path: PathBuf, sub: &str) -> Maildir { } Maildir::from(path) } + +pub struct MailStorage { + maildir_path: RwLock>, +} + +impl Default for MailStorage { + fn default() -> Self { + MailStorage { maildir_path: RwLock::new(None) } + } +} + +impl VarlinkInterface for MailStorage { + fn init( + &self, + call: &mut dyn Call_Init, + unix_user: String, + mailbox_path: String, + ) -> varlink::Result<()> { + init(self, call, unix_user, mailbox_path) + } + + fn add_folder(&self, call: &mut dyn Call_AddFolder, name: String) -> varlink::Result<()> { + add_folder(self, call, name) + } + + fn stats(&self, call: &mut dyn Call_Stats, folder: String) -> varlink::Result<()> { + stats(self, call, folder) + } + + fn folders(&self, call: &mut dyn Call_Folders) -> varlink::Result<()> { + folders(self, call) + } + + fn list( + &self, + call: &mut dyn Call_List, + folder: String, + start: i64, + end: i64, + sort: Sort, + ) -> varlink::Result<()> { + list(self, call, folder, start, end, sort) + } + + fn r#move( + &self, + call: &mut dyn Call_Move, + mid: String, + from_folder: String, + to_folder: String, + ) -> varlink::Result<()> { + r#move(self, call, mid, from_folder, to_folder) + } + + fn raw( + &self, + call: &mut dyn Call_Raw, + folder: String, + mid: String, + path: Option, + ) -> varlink::Result<()> { + raw(self, call, folder, mid, path) + } + + fn remove( + &self, + call: &mut dyn Call_Remove, + folder: String, + mid: String, + ) -> varlink::Result<()> { + remove(self, call, folder, mid) + } + + fn search( + &self, + call: &mut dyn Call_Search, + folder: String, + pattern: String, + ) -> varlink::Result<()> { + search(self, call, folder, pattern) + } + + fn show(&self, call: &mut dyn Call_Show, folder: String, mid: String) -> varlink::Result<()> { + show(self, call, folder, mid) + } +} diff --git a/src/cmd/add_folder.rs b/src/cmd/add_folder.rs index 82df1f9..07ad455 100644 --- a/src/cmd/add_folder.rs +++ b/src/cmd/add_folder.rs @@ -1,37 +1,40 @@ use std::fs::create_dir; -use std::path::PathBuf; -use protobuf::Message as _; +use varlink; -use crate::error::Result; -use crate::pb3::jwebmail::{AddFolderReq, AddFolderResp}; +use crate::cmd::MailStorage; +use crate::de_jmhoffmann_jwebmail_mailstorage::{AddFolder_Reply_status, Call_AddFolder}; -pub fn add_folder(mut p: PathBuf, req: &[u8]) -> Result> { - let r = AddFolderReq::parse_from_bytes(req)?; - let mut resp = AddFolderResp::new(); +pub fn add_folder( + ms: &MailStorage, + call: &mut dyn Call_AddFolder, + name: String, +) -> varlink::Result<()> { + if let Some(mut p) = ms.maildir_path.read().unwrap().clone() { + let mut folder = ".".to_owned(); + folder.push_str(&name); - let mut folder = ".".to_owned(); - folder.push_str(&r.name); - p.push(folder); + p.push(folder); - if p.is_dir() { - resp.status = 1; - return resp.write_to_bytes().map_err(|e| e.into()); - } + if p.is_dir() { + return call.reply(AddFolder_Reply_status::skiped); + } - create_dir(&p)?; + create_dir(&p).map_err(varlink::map_context!())?; - p.push("tmp"); - create_dir(&p)?; + p.push("tmp"); + create_dir(&p).map_err(varlink::map_context!())?; - p.pop(); - p.push("new"); - create_dir(&p)?; + p.pop(); + p.push("new"); + create_dir(&p).map_err(varlink::map_context!())?; - p.pop(); - p.push("cur"); - create_dir(p)?; + p.pop(); + p.push("cur"); + create_dir(p).map_err(varlink::map_context!())?; - resp.status = 0; - resp.write_to_bytes().map_err(|e| e.into()) + call.reply(AddFolder_Reply_status::created) + } else { + call.reply_not_initialized() + } } diff --git a/src/cmd/count.rs b/src/cmd/count.rs deleted file mode 100644 index 5de542b..0000000 --- a/src/cmd/count.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::path::PathBuf; - -use protobuf::Message; - -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{StatsReq, StatsResp}; - -pub fn count(path: PathBuf, req: &[u8]) -> Result> { - let r = StatsReq::parse_from_bytes(req)?; - - let md = open_submaildir(path, &r.folder); - - let mut resp = StatsResp::new(); - resp.mail_count = md.count_cur() as u32; - resp.unread_count = md - .list_cur() - .filter(|x| x.as_ref().map_or(false, |z| !z.is_seen())) - .count() as u32; - resp.byte_size = md - .path() - .join("cur") - .read_dir()? - .map(|x| x.map_or(0, |z| z.metadata().map_or(0, |y| y.len()))) - .sum(); - resp.write_to_bytes().map_err(|e| e.into()) -} diff --git a/src/cmd/folders.rs b/src/cmd/folders.rs index 417203c..8bf9a30 100644 --- a/src/cmd/folders.rs +++ b/src/cmd/folders.rs @@ -1,9 +1,7 @@ -use std::path::{Path, PathBuf}; +use std::path::Path; -use protobuf::Message as _; - -use crate::error::Result; -use crate::pb3::jwebmail::{FoldersReq, FoldersResp}; +use crate::cmd::MailStorage; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Folders; fn is_mailsubdir(p: &Path) -> bool { if !p.is_dir() { @@ -32,19 +30,20 @@ fn is_mailsubdir(p: &Path) -> bool { true } -pub fn folders(path: PathBuf, req: &[u8]) -> Result> { - let _ = FoldersReq::parse_from_bytes(req)?; - - let mut subdirs: Vec<_> = path - .read_dir()? - .filter_map(|d| d.ok()) - .filter(|d| is_mailsubdir(&d.path())) - .filter_map(|d| Some(d.path().file_name()?.to_string_lossy()[1..].to_owned())) - .collect(); +pub fn folders(ms: &MailStorage, call: &mut dyn Call_Folders) -> varlink::Result<()> { + if let Some(path) = &*ms.maildir_path.read().unwrap() { + let mut subdirs: Vec<_> = path + .read_dir() + .map_err(varlink::map_context!())? + .filter_map(|d| d.ok()) + .filter(|d| is_mailsubdir(&d.path())) + .filter_map(|d| Some(d.path().file_name()?.to_string_lossy()[1..].to_owned())) + .collect(); - subdirs.sort(); + subdirs.sort(); - let mut res = FoldersResp::new(); - res.folders = subdirs; - res.write_to_bytes().map_err(|e| e.into()) + call.reply(subdirs) + } else { + call.reply_not_initialized() + } } diff --git a/src/cmd/init.rs b/src/cmd/init.rs new file mode 100644 index 0000000..167d028 --- /dev/null +++ b/src/cmd/init.rs @@ -0,0 +1,35 @@ +use std::ffi::CString; + +use crate::cmd::MailStorage; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Init; + +pub fn init( + ms: &MailStorage, + call: &mut dyn Call_Init, + unix_user: String, + mailbox_path: String, +) -> varlink::Result<()> { + unsafe { + *libc::__errno_location() = 0; + } + if let Ok(c_sys_user) = CString::new(unix_user.clone()) { + let user_info: *const libc::passwd = unsafe { libc::getpwnam(c_sys_user.as_ptr()) }; + let err = unsafe { *libc::__errno_location() }; + if err != 0 { + return call.reply_invalid_user(unix_user); + } + if user_info.is_null() { + return call.reply_invalid_user(unix_user); + } + let rc = unsafe { libc::setuid((*user_info).pw_uid) }; + if rc != 0 { + return call.reply_invalid_user(unix_user); + } + + *ms.maildir_path.write().unwrap() = Some(mailbox_path.into()); + + call.reply() + } else { + call.reply_invalid_user(unix_user) + } +} diff --git a/src/cmd/list.rs b/src/cmd/list.rs index 3c2d42a..8aeaf97 100644 --- a/src/cmd/list.rs +++ b/src/cmd/list.rs @@ -1,12 +1,11 @@ use std::cmp::Reverse; -use std::path::PathBuf; use log::warn; -use protobuf::Message as _; -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{ListMailHeader, ListReq, ListResp, MailHeader}; +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::{ + Call_List, ListMailHeader, MailHeader, Sort, Sort_direction, Sort_parameter, +}; use crate::rfc822::me_to_lmh; fn from_or_sender(mh: &MailHeader) -> &str { @@ -37,95 +36,110 @@ fn mid_to_rec_time(mid: &str) -> f64 { fn sort_by_and_take( mut entries: Vec, - sortby: &str, + sortby: Sort, s: usize, e: usize, ) -> Vec { - match sortby { - "date" => { - entries.sort_by(|a, b| { - mid_to_rec_time(a.id()) - .partial_cmp(&mid_to_rec_time(b.id())) - .unwrap() - }); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - "!date" => { - entries.sort_by(|b, a| { - mid_to_rec_time(a.id()) - .partial_cmp(&mid_to_rec_time(b.id())) - .unwrap() - }); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - "size" => { - entries.sort_by_cached_key(|a| a.path().metadata().map_or(0, |m| m.len())); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - "!size" => { - entries.sort_by_cached_key(|a| Reverse(a.path().metadata().map_or(0, |m| m.len()))); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - "subject" | "!subject" => { + match sortby.parameter { + Sort_parameter::date => match sortby.direction { + Sort_direction::asc => { + entries.sort_by(|a, b| { + mid_to_rec_time(a.id()) + .partial_cmp(&mid_to_rec_time(b.id())) + .unwrap() + }); + entries + .drain(s..e) + .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) + .collect() + } + Sort_direction::desc => { + entries.sort_by(|b, a| { + mid_to_rec_time(a.id()) + .partial_cmp(&mid_to_rec_time(b.id())) + .unwrap() + }); + entries + .drain(s..e) + .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) + .collect() + } + }, + Sort_parameter::size => match sortby.direction { + Sort_direction::asc => { + entries.sort_by_cached_key(|a| a.path().metadata().map_or(0, |m| m.len())); + entries + .drain(s..e) + .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) + .collect() + } + Sort_direction::desc => { + entries.sort_by_cached_key(|a| Reverse(a.path().metadata().map_or(0, |m| m.len()))); + entries + .drain(s..e) + .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) + .collect() + } + }, + Sort_parameter::subject => { let mut x: Vec = entries .drain(..) .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) .collect(); - if sortby.as_bytes().first().copied() == Some(b'!') { - x.sort_by(|a, b| a.header.subject.cmp(&b.header.subject)) - } else { - x.sort_by(|b, a| a.header.subject.cmp(&b.header.subject)) + match sortby.direction { + Sort_direction::asc => x.sort_by(|a, b| a.header.subject.cmp(&b.header.subject)), + Sort_direction::desc => x.sort_by(|b, a| a.header.subject.cmp(&b.header.subject)), } x.drain(s..e).collect() } - "sender" | "!sender" => { + Sort_parameter::sender => { let mut x: Vec = entries .drain(..) .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) .collect(); - if sortby.as_bytes().first().copied() != Some(b'!') { - x.sort_by(|a, b| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) - } else { - x.sort_by(|b, a| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) + match sortby.direction { + Sort_direction::asc => { + x.sort_by(|a, b| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) + } + Sort_direction::desc => { + x.sort_by(|b, a| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) + } } x.drain(s..e).collect() } - _ => todo!(), } } -pub fn list(path: PathBuf, req: &[u8]) -> Result> { - let r = ListReq::parse_from_bytes(req)?; - let md = open_submaildir(path, &r.folder); +pub fn list( + ms: &MailStorage, + call: &mut dyn Call_List, + folder: String, + start: i64, + end: i64, + sort: Sort, +) -> varlink::Result<()> { + if let Some(path) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(path, &folder); - for r in md.list_new() { - match r { - Err(e) => warn!("{}", e), - Ok(me) => { - if let Err(e) = md.move_new_to_cur(me.id()) { - warn!("{}", e); + for r in md.list_new() { + match r { + Err(e) => warn!("{}", e), + Ok(me) => { + if let Err(e) = md.move_new_to_cur(me.id()) { + warn!("{}", e); + } } - } - }; - } + }; + } + + let a: Vec<_> = md.list_cur().filter_map(std::result::Result::ok).collect(); + let start = std::cmp::min(a.len(), start as usize); + let end = std::cmp::min(a.len(), end as usize); - let a: Vec<_> = md.list_cur().filter_map(std::result::Result::ok).collect(); - let start = std::cmp::min(a.len(), r.start as usize); - let end = std::cmp::min(a.len(), r.end as usize); + let mail_heads = sort_by_and_take(a, sort, start, end); - let mut resp = ListResp::new(); - resp.mail_heads = sort_by_and_take(a, &r.sort, start, end); - resp.write_to_bytes().map_err(|e| e.into()) + call.reply(mail_heads) + } else { + call.reply_not_initialized() + } } diff --git a/src/cmd/move.rs b/src/cmd/move.rs new file mode 100644 index 0000000..c852d6c --- /dev/null +++ b/src/cmd/move.rs @@ -0,0 +1,19 @@ +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Move; + +pub fn r#move( + ms: &MailStorage, + call: &mut dyn Call_Move, + mid: String, + from_folder: String, + to_folder: String, +) -> varlink::Result<()> { + if let Some(p) = ms.maildir_path.read().unwrap().clone() { + let from = open_submaildir(p.clone(), &from_folder); + let to = open_submaildir(p, &to_folder); + from.move_to(&mid, &to).unwrap(); + call.reply() + } else { + call.reply_not_initialized() + } +} diff --git a/src/cmd/move_mail.rs b/src/cmd/move_mail.rs deleted file mode 100644 index 146e906..0000000 --- a/src/cmd/move_mail.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::path::PathBuf; - -use protobuf::Message as _; - -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{MoveReq, MoveResp}; - -pub fn move_mail(p: PathBuf, req: &[u8]) -> Result> { - let r = MoveReq::parse_from_bytes(req)?; - - let from = open_submaildir(p.clone(), &r.from_f); - let to = open_submaildir(p, &r.to_f); - from.move_to(&r.mid, &to)?; - - let resp = MoveResp::new(); - resp.write_to_bytes().map_err(|e| e.into()) -} diff --git a/src/cmd/raw.rs b/src/cmd/raw.rs index 9fe7620..a20eded 100644 --- a/src/cmd/raw.rs +++ b/src/cmd/raw.rs @@ -1,106 +1,109 @@ -use std::fs::read; -use std::io::ErrorKind as IOErrKind; -use std::path::PathBuf; +use base64::prelude::BASE64_STANDARD; +use base64::Engine; -use protobuf::Message as _; - -use crate::cmd::open_submaildir; -use crate::error::{Error, Result}; -use crate::pb3::jwebmail::mimeheader::ContentDisposition::*; -use crate::pb3::jwebmail::{MIMEHeader, RawReq, RawResp}; +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::MIMEHeader_content_dispo as CD; +use crate::de_jmhoffmann_jwebmail_mailstorage::{Call_Raw, MIMEHeader, MIMEHeader_mime_type}; use crate::rfc822::parse_mail_content; -pub fn raw(md_path: PathBuf, req: &[u8]) -> Result> { - let r = RawReq::parse_from_bytes(req)?; - - let md = open_submaildir(md_path, &r.folder); +pub fn raw( + ms: &MailStorage, + call: &mut dyn Call_Raw, + folder: String, + mid: String, + path: Option, +) -> varlink::Result<()> { + if let Some(p) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(p, &folder); - let mut mail = md.find(&r.mid).ok_or_else(|| { - std::io::Error::new(IOErrKind::NotFound, format!("mail {} not found", &r.mid)) - })?; + if let Some(mut mail) = md.find(&mid) { + match path.as_deref() { + Some("") | None => call.reply( + MIMEHeader { + mime_type: MIMEHeader_mime_type { + main_type: "message".to_owned(), + sub_type: "rfc822".to_owned(), + }, + file_name: Some(mail.id().to_owned()), + content_dispo: CD::none, + }, + BASE64_STANDARD + .encode(std::fs::read(mail.path()).map_err(varlink::map_context!())?), + ), + Some(mime_path) => { + if let Ok(path) = mime_path + .split('.') + .map(|x| x.parse()) + .collect::, std::num::ParseIntError>>() + { + let mut m = mail.parsed().unwrap(); - match r.path.as_deref() { - Some("") | None => { - let mut mh = MIMEHeader::new(); + if path[0] != 0 { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } - mh.maintype = "message".to_owned(); - mh.subtype = "rfc822".to_owned(); - mh.file_name = Some(mail.id().to_owned()); - mh.contentdispo = CONTENT_DISPOSITION_NONE.into(); + for i in &path[1..] { + match &m.ctype.mimetype { + x if x.starts_with("message/") => { + if *i != 0 { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } + let s: &'static _ = m.get_body_raw().unwrap().leak(); + m = mailparse::parse_mail(s).unwrap(); + } + x if x.starts_with("multipart/") => { + if *i >= m.subparts.len() { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } + m = m.subparts.swap_remove(*i); + } + _ => { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } + } + } - let mut resp = RawResp::new(); - resp.header = Some(mh).into(); - resp.body = read(mail.path())?; - resp.write_to_bytes().map_err(|e| e.into()) - } - Some(mime_path) => { - let path = mime_path - .split('.') - .map(|x| { - x.parse() - .map_err(|pe: std::num::ParseIntError| Error::PathError { - msg: pe.to_string(), - path: mime_path.to_owned(), - }) - }) - .collect::>>()?; - let mut m = mail.parsed()?; + if m.ctype.mimetype.starts_with("multipart/") { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } - if path[0] != 0 { - return Err(Error::PathError { - msg: "Message must be accessed by a 0".to_owned(), - path: mime_path.to_owned(), - }); - } + let mime_part = parse_mail_content(&m); + let content = if m.ctype.mimetype.starts_with("text/") { + m.get_body().unwrap().into_bytes() + } else { + m.get_body_raw().unwrap() + }; - for i in &path[1..] { - match &m.ctype.mimetype { - x if x.starts_with("message/") => { - if *i != 0 { - return Err(Error::PathError { - msg: "Message must be accessed by a 0".to_owned(), - path: mime_path.to_owned(), - }); - } - let s: &'static _ = m.get_body_raw()?.leak(); - m = mailparse::parse_mail(s)?; - } - x if x.starts_with("multipart/") => { - if *i >= m.subparts.len() { - return Err(Error::PathError { - msg: "Out of bounds access".to_owned(), - path: mime_path.to_owned(), - }); - } - m = m.subparts.swap_remove(*i); - } - _ => { - return Err(Error::PathError { - msg: "Unable to descent into leaf component".to_owned(), - path: mime_path.to_owned(), - }) + call.reply(mime_part, BASE64_STANDARD.encode(content)) + } else { + call.reply_invalid_path_in_mail(folder, mid, mime_path.to_owned()) } } } - - if m.ctype.mimetype.starts_with("multipart/") { - return Err(Error::PathError { - msg: "Can not show multipart component".to_owned(), - path: mime_path.to_owned(), - }); - } - - let mime_part = parse_mail_content(&m)?; - let content = if m.ctype.mimetype.starts_with("text/") { - m.get_body()?.into_bytes() - } else { - m.get_body_raw()? - }; - - let mut resp = RawResp::new(); - resp.header = Some(mime_part).into(); - resp.body = content; - resp.write_to_bytes().map_err(|e| e.into()) + } else { + call.reply_invalid_mid(folder, mid) } + } else { + call.reply_not_initialized() } } diff --git a/src/cmd/read.rs b/src/cmd/read.rs deleted file mode 100644 index 797f4d6..0000000 --- a/src/cmd/read.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::io::ErrorKind as IOErrKind; -use std::path::PathBuf; - -use protobuf::Message as _; - -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{ShowReq, ShowResp}; -use crate::rfc822::parsed_mail_to_mail; - -pub fn read(path: PathBuf, req: &[u8]) -> Result> { - let r = ShowReq::parse_from_bytes(req)?; - let md = open_submaildir(path, &r.folder); - - md.add_flags(&r.mid, "S")?; - - let mut mail = md.find(&r.mid).ok_or_else(|| { - std::io::Error::new(IOErrKind::NotFound, format!("mail {} not found", &r.mid)) - })?; - - let mut resp = ShowResp::new(); - resp.mail = Some(parsed_mail_to_mail(mail.parsed()?)?).into(); - resp.write_to_bytes().map_err(|e| e.into()) -} diff --git a/src/cmd/remove.rs b/src/cmd/remove.rs index 8d26e68..73328a5 100644 --- a/src/cmd/remove.rs +++ b/src/cmd/remove.rs @@ -1,17 +1,18 @@ -use std::path::PathBuf; +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Remove; -use protobuf::Message as _; +pub fn remove( + ms: &MailStorage, + call: &mut dyn Call_Remove, + folder: String, + mid: String, +) -> varlink::Result<()> { + if let Some(p) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(p, &folder); + md.add_flags(&mid, "T").map_err(varlink::map_context!())?; -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{RemoveReq, RemoveResp}; - -pub fn remove(p: PathBuf, req: &[u8]) -> Result> { - let r = RemoveReq::parse_from_bytes(req)?; - - let md = open_submaildir(p, &r.folder); - md.add_flags(&r.mid, "T")?; - - let resp = RemoveResp::new(); - resp.write_to_bytes().map_err(|e| e.into()) + call.reply() + } else { + call.reply_not_initialized() + } } diff --git a/src/cmd/search.rs b/src/cmd/search.rs new file mode 100644 index 0000000..7cf0fb0 --- /dev/null +++ b/src/cmd/search.rs @@ -0,0 +1,17 @@ +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Search; + +pub fn search( + ms: &MailStorage, + call: &mut dyn Call_Search, + folder: String, + _pattern: String, +) -> varlink::Result<()> { + if let Some(p) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(p, &folder); + + todo!(); + } else { + call.reply_not_initialized() + } +} diff --git a/src/cmd/show.rs b/src/cmd/show.rs new file mode 100644 index 0000000..442eb21 --- /dev/null +++ b/src/cmd/show.rs @@ -0,0 +1,26 @@ +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Show; +use crate::rfc822::parsed_mail_to_mail; + +pub fn show( + ms: &MailStorage, + call: &mut dyn Call_Show, + folder: String, + mid: String, +) -> varlink::Result<()> { + if let Some(path) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(path, &folder); + + md.add_flags(&mid, "S").map_err(varlink::map_context!())?; + + if let Some(mut mail) = md.find(&mid) { + let mail2 = parsed_mail_to_mail(mail.parsed().unwrap()).unwrap(); + + call.reply(mail2) + } else { + call.reply_invalid_mid(folder, mid) + } + } else { + call.reply_not_initialized() + } +} diff --git a/src/cmd/stats.rs b/src/cmd/stats.rs new file mode 100644 index 0000000..e8e8325 --- /dev/null +++ b/src/cmd/stats.rs @@ -0,0 +1,25 @@ +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Stats; + +pub fn stats(ms: &MailStorage, call: &mut dyn Call_Stats, folder: String) -> varlink::Result<()> { + if let Some(maildir_path) = ms.maildir_path.read().unwrap().clone() { + let maildir = open_submaildir(maildir_path, &folder); + + let mail_count = maildir.count_cur() as i64; + let unread_count = maildir + .list_cur() + .filter(|x| x.as_ref().map_or(false, |z| !z.is_seen())) + .count() as i64; + let byte_size: u64 = maildir + .path() + .join("cur") + .read_dir() + .map_err(varlink::map_context!())? + .map(|x| x.map_or(0, |z| z.metadata().map_or(0, |y| y.len()))) + .sum(); + + call.reply(mail_count, unread_count, byte_size as i64) + } else { + call.reply_not_initialized() + } +} diff --git a/src/de.jmhoffmann.jwebmail.mail-storage.varlink b/src/de.jmhoffmann.jwebmail.mail-storage.varlink new file mode 100644 index 0000000..f61f624 --- /dev/null +++ b/src/de.jmhoffmann.jwebmail.mail-storage.varlink @@ -0,0 +1,86 @@ +interface de.jmhoffmann.jwebmail.mail-storage + + +type MIMEHeader ( + mime_type: (main_type: string, sub_type: string), + content_dispo: (none, inline, attachment), + file_name: ?string +) + +type MailAddr ( + name: ?string, + address: string +) + +# send_date is of ISO 8601 date-time format +type MailHeader ( + send_date: string, + written_from: []MailAddr, + sender: ?MailAddr, + reply_to: []MailAddr, + send_to: []MailAddr, + cc: []MailAddr, + bcc: []MailAddr, + subject: string, + comments: []string, + keywords: []string, + mime: MIMEHeader +) + +type ListMailHeader ( + byte_size: int, + unread: bool, + rec_date: string, + mid: string, + header: MailHeader +) + +type Multipart ( + preamble: ?string, + parts: []MIMEPart, + epilogue: ?string +) + +# exactly one of these fileds must be present +type MailBody ( + discrete: ?string, + multipart: ?Multipart, + mail: ?Mail +) + +type Mail ( + head: MailHeader, + body: MailBody +) + +type MIMEPart ( + mime_header: MIMEHeader, + body: MailBody +) + +type Sort ( + direction: (asc, desc), + parameter: (date, size, sender) +) + + +method Init(unix_user: string, mailbox_path: string) -> () +method List(folder: string, start: int, end: int, sort: Sort) -> (mail_heads: []ListMailHeader) +method Stats(folder: string) -> (mail_count: int, unread_count: int, byte_size: int) +method Show(folder: string, mid: string) -> (mail: Mail) +# body is base64 encoded +method Raw(folder: string, mid: string, path: ?string) -> (header: MIMEHeader, body: string) +method Search(folder: string, pattern: string) -> (found: []ListMailHeader) +method Folders() -> (folders: []string) +method Move(mid: string, from_folder: string, to_folder: string) -> () +method Remove(folder: string, mid: string) -> () +method AddFolder(name: string) -> (status: (created, skiped)) + + +error NotInitialized() +error InvalidFolder(folder: string) +error InvalidMID(folder: string, mid: string) +error InvalidPathInMail(folder: string, mid: string, path: string) +error InvalidSearchPattern(pattern: string) +error InvalidUser(unix_user: string) +error InvalidMailbox(path: string, not_a_mailbox: bool, user_mismatch: bool) diff --git a/src/de_jmhoffmann_jwebmail_mail-storage.rs b/src/de_jmhoffmann_jwebmail_mail-storage.rs new file mode 100644 index 0000000..bf3b148 --- /dev/null +++ b/src/de_jmhoffmann_jwebmail_mail-storage.rs @@ -0,0 +1,1035 @@ +#![doc = "This file was automatically generated by the varlink rust generator"] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +use serde_derive::{Deserialize, Serialize}; +use std::io::BufRead; +use std::sync::{Arc, RwLock}; +use varlink::{self, CallTrait}; +#[allow(dead_code)] +#[derive(Clone, PartialEq, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum ErrorKind { + Varlink_Error, + VarlinkReply_Error, + InvalidFolder(Option), + InvalidMID(Option), + InvalidMailbox(Option), + InvalidPathInMail(Option), + InvalidSearchPattern(Option), + InvalidUser(Option), + NotInitialized(Option), +} +impl ::std::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + ErrorKind::Varlink_Error => write!(f, "Varlink Error"), + ErrorKind::VarlinkReply_Error => write!(f, "Varlink error reply"), + ErrorKind::InvalidFolder(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidFolder: {:#?}", + v + ), + ErrorKind::InvalidMID(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidMID: {:#?}", + v + ), + ErrorKind::InvalidMailbox(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidMailbox: {:#?}", + v + ), + ErrorKind::InvalidPathInMail(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidPathInMail: {:#?}", + v + ), + ErrorKind::InvalidSearchPattern(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidSearchPattern: {:#?}", + v + ), + ErrorKind::InvalidUser(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidUser: {:#?}", + v + ), + ErrorKind::NotInitialized(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.NotInitialized: {:#?}", + v + ), + } + } +} +pub struct Error( + pub ErrorKind, + pub Option>, + pub Option<&'static str>, +); +impl Error { + #[allow(dead_code)] + pub fn kind(&self) -> &ErrorKind { + &self.0 + } +} +impl From for Error { + fn from(e: ErrorKind) -> Self { + Error(e, None, None) + } +} +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.1 + .as_ref() + .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)) + } +} +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} +impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use std::error::Error as StdError; + if let Some(ref o) = self.2 { + std::fmt::Display::fmt(o, f)?; + } + std::fmt::Debug::fmt(&self.0, f)?; + if let Some(e) = self.source() { + std::fmt::Display::fmt("\nCaused by:\n", f)?; + std::fmt::Debug::fmt(&e, f)?; + } + Ok(()) + } +} +#[allow(dead_code)] +pub type Result = std::result::Result; +impl From for Error { + fn from(e: varlink::Error) -> Self { + match e.kind() { + varlink::ErrorKind::VarlinkErrorReply(r) => Error( + ErrorKind::from(r), + Some(Box::from(e)), + Some(concat!(file!(), ":", line!(), ": ")), + ), + _ => Error( + ErrorKind::Varlink_Error, + Some(Box::from(e)), + Some(concat!(file!(), ":", line!(), ": ")), + ), + } + } +} +#[allow(dead_code)] +impl Error { + pub fn source_varlink_kind(&self) -> Option<&varlink::ErrorKind> { + use std::error::Error as StdError; + let mut s: &dyn StdError = self; + while let Some(c) = s.source() { + let k = self + .source() + .and_then(|e| e.downcast_ref::()) + .map(|e| e.kind()); + if k.is_some() { + return k; + } + s = c; + } + None + } +} +impl From<&varlink::Reply> for ErrorKind { + #[allow(unused_variables)] + fn from(e: &varlink::Reply) -> Self { + match e { + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidFolder" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidFolder(v), + Err(_) => ErrorKind::InvalidFolder(None), + }, + _ => ErrorKind::InvalidFolder(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidMID" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidMID(v), + Err(_) => ErrorKind::InvalidMID(None), + }, + _ => ErrorKind::InvalidMID(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidMailbox" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidMailbox(v), + Err(_) => ErrorKind::InvalidMailbox(None), + }, + _ => ErrorKind::InvalidMailbox(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidPathInMail" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidPathInMail(v), + Err(_) => ErrorKind::InvalidPathInMail(None), + }, + _ => ErrorKind::InvalidPathInMail(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidSearchPattern" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidSearchPattern(v), + Err(_) => ErrorKind::InvalidSearchPattern(None), + }, + _ => ErrorKind::InvalidSearchPattern(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidUser" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidUser(v), + Err(_) => ErrorKind::InvalidUser(None), + }, + _ => ErrorKind::InvalidUser(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.NotInitialized" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::NotInitialized(v), + Err(_) => ErrorKind::NotInitialized(None), + }, + _ => ErrorKind::NotInitialized(None), + }, + _ => ErrorKind::VarlinkReply_Error, + } + } +} +pub trait VarlinkCallError: varlink::CallTrait { + fn reply_invalid_folder(&mut self, r#folder: String) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidFolder", + Some( + serde_json::to_value(InvalidFolder_Args { r#folder }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_mid(&mut self, r#folder: String, r#mid: String) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidMID", + Some( + serde_json::to_value(InvalidMID_Args { r#folder, r#mid }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_mailbox( + &mut self, + r#path: String, + r#not_a_mailbox: bool, + r#user_mismatch: bool, + ) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidMailbox", + Some( + serde_json::to_value(InvalidMailbox_Args { + r#path, + r#not_a_mailbox, + r#user_mismatch, + }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_path_in_mail( + &mut self, + r#folder: String, + r#mid: String, + r#path: String, + ) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidPathInMail", + Some( + serde_json::to_value(InvalidPathInMail_Args { + r#folder, + r#mid, + r#path, + }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_search_pattern(&mut self, r#pattern: String) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidSearchPattern", + Some( + serde_json::to_value(InvalidSearchPattern_Args { r#pattern }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_user(&mut self, r#unix_user: String) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidUser", + Some( + serde_json::to_value(InvalidUser_Args { r#unix_user }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_not_initialized(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.NotInitialized", + None, + )) + } +} +impl<'a> VarlinkCallError for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#ListMailHeader { + pub r#byte_size: i64, + pub r#unread: bool, + pub r#rec_date: String, + pub r#mid: String, + pub r#header: MailHeader, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MIMEHeader_mime_type { + pub r#main_type: String, + pub r#sub_type: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#MIMEHeader_content_dispo { + r#none, + r#inline, + r#attachment, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MIMEHeader { + pub r#mime_type: MIMEHeader_mime_type, + pub r#content_dispo: MIMEHeader_content_dispo, + pub r#file_name: Option, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MIMEPart { + pub r#mime_header: MIMEHeader, + pub r#body: MailBody, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Mail { + pub r#head: MailHeader, + pub r#body: MailBody, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MailAddr { + pub r#name: Option, + pub r#address: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MailBody { + pub r#discrete: Option, + pub r#multipart: Option, + pub r#mail: Option>, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MailHeader { + pub r#send_date: String, + pub r#written_from: Vec, + pub r#sender: Option, + pub r#reply_to: Vec, + pub r#send_to: Vec, + pub r#cc: Vec, + pub r#bcc: Vec, + pub r#subject: String, + pub r#comments: Vec, + pub r#keywords: Vec, + pub r#mime: MIMEHeader, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Multipart { + pub r#preamble: Option, + pub r#parts: Vec, + pub r#epilogue: Option, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#Sort_direction { + r#asc, + r#desc, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#Sort_parameter { + r#date, + r#size, + r#sender, + r#subject, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Sort { + pub r#direction: Sort_direction, + pub r#parameter: Sort_parameter, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidFolder_Args { + pub r#folder: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidMID_Args { + pub r#folder: String, + pub r#mid: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidMailbox_Args { + pub r#path: String, + pub r#not_a_mailbox: bool, + pub r#user_mismatch: bool, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidPathInMail_Args { + pub r#folder: String, + pub r#mid: String, + pub r#path: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidSearchPattern_Args { + pub r#pattern: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidUser_Args { + pub r#unix_user: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct NotInitialized_Args {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#AddFolder_Reply_status { + r#created, + r#skiped, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct AddFolder_Reply { + pub r#status: AddFolder_Reply_status, +} +impl varlink::VarlinkReply for AddFolder_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct AddFolder_Args { + pub r#name: String, +} +pub trait Call_AddFolder: VarlinkCallError { + fn reply(&mut self, r#status: AddFolder_Reply_status) -> varlink::Result<()> { + self.reply_struct(AddFolder_Reply { r#status }.into()) + } +} +impl<'a> Call_AddFolder for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Folders_Reply { + pub r#folders: Vec, +} +impl varlink::VarlinkReply for Folders_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Folders_Args {} +pub trait Call_Folders: VarlinkCallError { + fn reply(&mut self, r#folders: Vec) -> varlink::Result<()> { + self.reply_struct(Folders_Reply { r#folders }.into()) + } +} +impl<'a> Call_Folders for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Init_Reply {} +impl varlink::VarlinkReply for Init_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Init_Args { + pub r#unix_user: String, + pub r#mailbox_path: String, +} +pub trait Call_Init: VarlinkCallError { + fn reply(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::parameters(None)) + } +} +impl<'a> Call_Init for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct List_Reply { + pub r#mail_heads: Vec, +} +impl varlink::VarlinkReply for List_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct List_Args { + pub r#folder: String, + pub r#start: i64, + pub r#end: i64, + pub r#sort: Sort, +} +pub trait Call_List: VarlinkCallError { + fn reply(&mut self, r#mail_heads: Vec) -> varlink::Result<()> { + self.reply_struct(List_Reply { r#mail_heads }.into()) + } +} +impl<'a> Call_List for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Move_Reply {} +impl varlink::VarlinkReply for Move_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Move_Args { + pub r#mid: String, + pub r#from_folder: String, + pub r#to_folder: String, +} +pub trait Call_Move: VarlinkCallError { + fn reply(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::parameters(None)) + } +} +impl<'a> Call_Move for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Raw_Reply { + pub r#header: MIMEHeader, + pub r#body: String, +} +impl varlink::VarlinkReply for Raw_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Raw_Args { + pub r#folder: String, + pub r#mid: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub r#path: Option, +} +pub trait Call_Raw: VarlinkCallError { + fn reply(&mut self, r#header: MIMEHeader, r#body: String) -> varlink::Result<()> { + self.reply_struct(Raw_Reply { r#header, r#body }.into()) + } +} +impl<'a> Call_Raw for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Remove_Reply {} +impl varlink::VarlinkReply for Remove_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Remove_Args { + pub r#folder: String, + pub r#mid: String, +} +pub trait Call_Remove: VarlinkCallError { + fn reply(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::parameters(None)) + } +} +impl<'a> Call_Remove for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Search_Reply { + pub r#found: Vec, +} +impl varlink::VarlinkReply for Search_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Search_Args { + pub r#folder: String, + pub r#pattern: String, +} +pub trait Call_Search: VarlinkCallError { + fn reply(&mut self, r#found: Vec) -> varlink::Result<()> { + self.reply_struct(Search_Reply { r#found }.into()) + } +} +impl<'a> Call_Search for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Show_Reply { + pub r#mail: Mail, +} +impl varlink::VarlinkReply for Show_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Show_Args { + pub r#folder: String, + pub r#mid: String, +} +pub trait Call_Show: VarlinkCallError { + fn reply(&mut self, r#mail: Mail) -> varlink::Result<()> { + self.reply_struct(Show_Reply { r#mail }.into()) + } +} +impl<'a> Call_Show for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Stats_Reply { + pub r#mail_count: i64, + pub r#unread_count: i64, + pub r#byte_size: i64, +} +impl varlink::VarlinkReply for Stats_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Stats_Args { + pub r#folder: String, +} +pub trait Call_Stats: VarlinkCallError { + fn reply( + &mut self, + r#mail_count: i64, + r#unread_count: i64, + r#byte_size: i64, + ) -> varlink::Result<()> { + self.reply_struct( + Stats_Reply { + r#mail_count, + r#unread_count, + r#byte_size, + } + .into(), + ) + } +} +impl<'a> Call_Stats for varlink::Call<'a> {} +pub trait VarlinkInterface { + fn add_folder(&self, call: &mut dyn Call_AddFolder, r#name: String) -> varlink::Result<()>; + fn folders(&self, call: &mut dyn Call_Folders) -> varlink::Result<()>; + fn init( + &self, + call: &mut dyn Call_Init, + r#unix_user: String, + r#mailbox_path: String, + ) -> varlink::Result<()>; + fn list( + &self, + call: &mut dyn Call_List, + r#folder: String, + r#start: i64, + r#end: i64, + r#sort: Sort, + ) -> varlink::Result<()>; + fn r#move( + &self, + call: &mut dyn Call_Move, + r#mid: String, + r#from_folder: String, + r#to_folder: String, + ) -> varlink::Result<()>; + fn raw( + &self, + call: &mut dyn Call_Raw, + r#folder: String, + r#mid: String, + r#path: Option, + ) -> varlink::Result<()>; + fn remove( + &self, + call: &mut dyn Call_Remove, + r#folder: String, + r#mid: String, + ) -> varlink::Result<()>; + fn search( + &self, + call: &mut dyn Call_Search, + r#folder: String, + r#pattern: String, + ) -> varlink::Result<()>; + fn show( + &self, + call: &mut dyn Call_Show, + r#folder: String, + r#mid: String, + ) -> varlink::Result<()>; + fn stats(&self, call: &mut dyn Call_Stats, r#folder: String) -> varlink::Result<()>; + fn call_upgraded( + &self, + _call: &mut varlink::Call, + _bufreader: &mut dyn BufRead, + ) -> varlink::Result> { + Ok(Vec::new()) + } +} +pub trait VarlinkClientInterface { + fn add_folder( + &mut self, + r#name: String, + ) -> varlink::MethodCall; + fn folders(&mut self) -> varlink::MethodCall; + fn init( + &mut self, + r#unix_user: String, + r#mailbox_path: String, + ) -> varlink::MethodCall; + fn list( + &mut self, + r#folder: String, + r#start: i64, + r#end: i64, + r#sort: Sort, + ) -> varlink::MethodCall; + fn r#move( + &mut self, + r#mid: String, + r#from_folder: String, + r#to_folder: String, + ) -> varlink::MethodCall; + fn raw( + &mut self, + r#folder: String, + r#mid: String, + r#path: Option, + ) -> varlink::MethodCall; + fn remove( + &mut self, + r#folder: String, + r#mid: String, + ) -> varlink::MethodCall; + fn search( + &mut self, + r#folder: String, + r#pattern: String, + ) -> varlink::MethodCall; + fn show( + &mut self, + r#folder: String, + r#mid: String, + ) -> varlink::MethodCall; + fn stats(&mut self, r#folder: String) -> varlink::MethodCall; +} +#[allow(dead_code)] +pub struct VarlinkClient { + connection: Arc>, +} +impl VarlinkClient { + #[allow(dead_code)] + pub fn new(connection: Arc>) -> Self { + VarlinkClient { connection } + } +} +impl VarlinkClientInterface for VarlinkClient { + fn add_folder( + &mut self, + r#name: String, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.AddFolder", + AddFolder_Args { r#name }, + ) + } + fn folders(&mut self) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Folders", + Folders_Args {}, + ) + } + fn init( + &mut self, + r#unix_user: String, + r#mailbox_path: String, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Init", + Init_Args { + r#unix_user, + r#mailbox_path, + }, + ) + } + fn list( + &mut self, + r#folder: String, + r#start: i64, + r#end: i64, + r#sort: Sort, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.List", + List_Args { + r#folder, + r#start, + r#end, + r#sort, + }, + ) + } + fn r#move( + &mut self, + r#mid: String, + r#from_folder: String, + r#to_folder: String, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Move", + Move_Args { + r#mid, + r#from_folder, + r#to_folder, + }, + ) + } + fn raw( + &mut self, + r#folder: String, + r#mid: String, + r#path: Option, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Raw", + Raw_Args { + r#folder, + r#mid, + r#path, + }, + ) + } + fn remove( + &mut self, + r#folder: String, + r#mid: String, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Remove", + Remove_Args { r#folder, r#mid }, + ) + } + fn search( + &mut self, + r#folder: String, + r#pattern: String, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Search", + Search_Args { + r#folder, + r#pattern, + }, + ) + } + fn show( + &mut self, + r#folder: String, + r#mid: String, + ) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Show", + Show_Args { r#folder, r#mid }, + ) + } + fn stats(&mut self, r#folder: String) -> varlink::MethodCall { + varlink::MethodCall::::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Stats", + Stats_Args { r#folder }, + ) + } +} +#[allow(dead_code)] +pub struct VarlinkInterfaceProxy { + inner: Box, +} +#[allow(dead_code)] +pub fn new(inner: Box) -> VarlinkInterfaceProxy { + VarlinkInterfaceProxy { inner } +} +impl varlink::Interface for VarlinkInterfaceProxy { + fn get_description(&self) -> &'static str { + "interface de.jmhoffmann.jwebmail.mail-storage\n\n\ntype MIMEHeader (\n mime_type: (main_type: string, sub_type: string),\n content_dispo: (none, inline, attachment),\n file_name: ?string\n)\n\ntype MailAddr (\n name: ?string,\n address: string\n)\n\n# send_date is of ISO 8601 date-time format\ntype MailHeader (\n send_date: string,\n written_from: []MailAddr,\n sender: ?MailAddr,\n reply_to: []MailAddr,\n send_to: []MailAddr,\n cc: []MailAddr,\n bcc: []MailAddr,\n subject: string,\n comments: []string,\n keywords: []string,\n mime: MIMEHeader\n)\n\ntype ListMailHeader (\n byte_size: int,\n unread: bool,\n rec_date: string,\n mid: string,\n header: MailHeader\n)\n\ntype Multipart (\n preamble: ?string,\n parts: []MIMEPart,\n epilogue: ?string\n)\n\n# exactly one of these fileds must be present\ntype MailBody (\n discrete: ?string,\n multipart: ?Multipart,\n mail: ?Mail\n)\n\ntype Mail (\n head: MailHeader,\n body: MailBody\n)\n\ntype MIMEPart (\n mime_header: MIMEHeader,\n body: MailBody\n)\n\ntype Sort (\n direction: (asc, desc),\n parameter: (date, size, sender)\n)\n\n\nmethod Init(unix_user: string, mailbox_path: string) -> ()\nmethod List(folder: string, start: int, end: int, sort: Sort) -> (mail_heads: []ListMailHeader)\nmethod Stats(folder: string) -> (mail_count: int, unread_count: int, byte_size: int)\nmethod Show(folder: string, mid: string) -> (mail: Mail)\n# body is base64 encoded\nmethod Raw(folder: string, mid: string, path: ?string) -> (header: MIMEHeader, body: string)\nmethod Search(folder: string, pattern: string) -> (found: []ListMailHeader)\nmethod Folders() -> (folders: []string)\nmethod Move(mid: string, from_folder: string, to_folder: string) -> ()\nmethod Remove(folder: string, mid: string) -> ()\nmethod AddFolder(name: string) -> (status: (created, skiped))\n\n\nerror NotInitialized()\nerror InvalidFolder(folder: string)\nerror InvalidMID(folder: string, mid: string)\nerror InvalidPathInMail(folder: string, mid: string, path: string)\nerror InvalidSearchPattern(pattern: string)\nerror InvalidUser(unix_user: string)\nerror InvalidMailbox(path: string, not_a_mailbox: bool, user_mismatch: bool)\n" + } + fn get_name(&self) -> &'static str { + "de.jmhoffmann.jwebmail.mail-storage" + } + fn call_upgraded( + &self, + call: &mut varlink::Call, + bufreader: &mut dyn BufRead, + ) -> varlink::Result> { + self.inner.call_upgraded(call, bufreader) + } + fn call(&self, call: &mut varlink::Call) -> varlink::Result<()> { + let req = call.request.unwrap(); + match req.method.as_ref() { + "de.jmhoffmann.jwebmail.mail-storage.AddFolder" => { + if let Some(args) = req.parameters.clone() { + let args: AddFolder_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .add_folder(call as &mut dyn Call_AddFolder, args.r#name) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Folders" => { + self.inner.folders(call as &mut dyn Call_Folders) + } + "de.jmhoffmann.jwebmail.mail-storage.Init" => { + if let Some(args) = req.parameters.clone() { + let args: Init_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.init( + call as &mut dyn Call_Init, + args.r#unix_user, + args.r#mailbox_path, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.List" => { + if let Some(args) = req.parameters.clone() { + let args: List_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.list( + call as &mut dyn Call_List, + args.r#folder, + args.r#start, + args.r#end, + args.r#sort, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Move" => { + if let Some(args) = req.parameters.clone() { + let args: Move_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.r#move( + call as &mut dyn Call_Move, + args.r#mid, + args.r#from_folder, + args.r#to_folder, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Raw" => { + if let Some(args) = req.parameters.clone() { + let args: Raw_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.raw( + call as &mut dyn Call_Raw, + args.r#folder, + args.r#mid, + args.r#path, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Remove" => { + if let Some(args) = req.parameters.clone() { + let args: Remove_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .remove(call as &mut dyn Call_Remove, args.r#folder, args.r#mid) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Search" => { + if let Some(args) = req.parameters.clone() { + let args: Search_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .search(call as &mut dyn Call_Search, args.r#folder, args.r#pattern) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Show" => { + if let Some(args) = req.parameters.clone() { + let args: Show_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .show(call as &mut dyn Call_Show, args.r#folder, args.r#mid) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Stats" => { + if let Some(args) = req.parameters.clone() { + let args: Stats_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.stats(call as &mut dyn Call_Stats, args.r#folder) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + m => call.reply_method_not_found(String::from(m)), + } + } +} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 0c0167f..0000000 --- a/src/error.rs +++ /dev/null @@ -1,47 +0,0 @@ -use maildir::MailEntryError; -use mailparse::MailParseError; -use protobuf::Error as PBError; - -pub type Result = std::result::Result; - -#[derive(Debug)] -pub enum Error { - IoError(std::io::Error), - MailEntryError(MailEntryError), - SortOrder(String), - Setuid(String), - Protobuf(PBError), - PathError { msg: String, path: String }, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for Error {} - -impl From for Error { - fn from(io_err: std::io::Error) -> Self { - Error::IoError(io_err) - } -} - -impl From for Error { - fn from(me_err: MailEntryError) -> Self { - Error::MailEntryError(me_err) - } -} - -impl From for Error { - fn from(mp_err: MailParseError) -> Self { - Error::MailEntryError(MailEntryError::ParseError(mp_err)) - } -} - -impl From for Error { - fn from(pb_err: PBError) -> Self { - Error::Protobuf(pb_err) - } -} diff --git a/src/main.rs b/src/main.rs index 5598b30..a8e33b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,46 +1,19 @@ -use std::ffi::{CStr, CString}; -use std::io::{stdin, stdout, Read, Write}; +use std::io::{BufRead, BufReader}; +use std::net::Shutdown; +use std::os::fd::FromRawFd; +use std::os::unix::net::UnixStream; +use std::str::FromStr; -use clap::Parser as _; +use varlink::{ConnectionHandler, ErrorKind, Stream, VarlinkService}; -mod arguments; mod cmd; -mod error; -mod pb3; +#[path = "de_jmhoffmann_jwebmail_mail-storage.rs"] +mod de_jmhoffmann_jwebmail_mailstorage; mod rfc822; -use arguments::{Arguments, Mode}; -use error::{Error, Result}; +use cmd::MailStorage; -fn switch_to_user(sys_user: &str) -> Result<()> { - unsafe { - *libc::__errno_location() = 0; - } - let c_sys_user = - CString::new(sys_user).map_err(|e| Error::Setuid(format!("nul char in user, {}", e)))?; - let user_info: *const libc::passwd = unsafe { libc::getpwnam(c_sys_user.as_ptr()) }; - let err = unsafe { *libc::__errno_location() }; - if err != 0 { - return Err(Error::Setuid(format!( - "error calling getpwnam {:?}", - unsafe { libc::strerror(err) } - ))); - }; - if user_info.is_null() { - return Err(Error::Setuid(format!("user {:?} does not exist", sys_user))); - }; - let rc = unsafe { libc::setuid((*user_info).pw_uid) }; - if rc != 0 { - let err = unsafe { *libc::__errno_location() }; - return Err(Error::Setuid(format!( - "error calling setuid {:?}", - unsafe { CStr::from_ptr(libc::strerror(err)) } - ))); - } - Ok(()) -} - -fn main() -> Result<()> { +fn main() { simplelog::TermLogger::init( simplelog::LevelFilter::Info, simplelog::Config::default(), @@ -49,29 +22,66 @@ fn main() -> Result<()> { ) .unwrap(); - let args = Arguments::parse(); - - switch_to_user(&args.sys_user)?; + let mail_storage = MailStorage::default(); - let path = args.maildir_path.join(args.mail_user); + let myinterface = de_jmhoffmann_jwebmail_mailstorage::new(Box::new(mail_storage)); + let service = VarlinkService::new( + "de.jmhoffmann.jwebmail.mail-storage.varlink", + "jwebmails storage service", + "1.0.1", + "https://fehcom.de/cgit/jwebmail2/", + vec![Box::new(myinterface)], + ); - stdout().write_all(b"OPEN\n")?; - stdout().flush()?; - let mut req = Vec::with_capacity(2048); - stdin().read_to_end(&mut req)?; - - let res = match args.mode { - Mode::Read => cmd::read(path, &req), - Mode::Raw => cmd::raw(path, &req), - Mode::List => cmd::list(path, &req), - Mode::Folders => cmd::folders(path, &req), - Mode::Count => cmd::count(path, &req), - //Mode::Search => cmd::search(&path, &req), - Mode::Move => cmd::move_mail(path, &req), - Mode::Remove => cmd::remove(path, &req), - Mode::AddFolder => cmd::add_folder(path, &req), - _ => todo!(), - }?; + if let Ok(listen_fds) = std::env::var("LISTEN_FDS") { + let lfds = u8::from_str(&listen_fds).expect("env variable `LISTEN_FDS` must be an integer"); + if lfds < 1 { + log::error!("No file descriptor to receive commands from!"); + return; + } else if lfds == 1 { + let uds = unsafe { UnixStream::from_raw_fd(3) }; + accept_con(&service, uds); + } else { + let listen_fdnames = std::env::var("LISTEN_FDNAMES") + .expect("when multiple `LISTEN_FDS` are available `LISTEN_FDNAMES` must be set"); + listen_fdnames + .split(':') + .enumerate() + .filter(|(_, name)| *name == "varlink") + .for_each(|(i, _)| { + accept_con(&service, unsafe { UnixStream::from_raw_fd(3 + i as i32) }) + }); + } + } else { + log::error!("No file descriptor to receive commands from!"); + } +} - stdout().write_all(&res).map_err(|e| e.into()) +/// mostly a copy from varlink::listen +fn accept_con(service: &VarlinkService, mut uds: UnixStream) { + let (r, mut w) = uds.split().unwrap(); + let mut br = BufReader::new(r); + let mut iface: Option = None; + loop { + match service.handle(&mut br, &mut w, iface.clone()) { + Ok((_, i)) => { + iface = i; + match br.fill_buf() { + Err(_) => break, + Ok(buf) if buf.is_empty() => break, + _ => {} + } + } + Err(err) => { + match err.kind() { + ErrorKind::ConnectionClosed | ErrorKind::SerdeJsonDe(_) => {} + _ => { + eprintln!("Worker error: {:?}", err); + } + } + let _ = uds.shutdown(Shutdown::Both); + break; + } + } + } } diff --git a/src/pb3/mod.rs b/src/pb3/mod.rs deleted file mode 100644 index 55a7aae..0000000 --- a/src/pb3/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// @generated - -pub mod jwebmail; diff --git a/src/rfc822.rs b/src/rfc822.rs index 658b167..a8fa5c3 100644 --- a/src/rfc822.rs +++ b/src/rfc822.rs @@ -1,14 +1,15 @@ use chrono::DateTime; -use mailparse::{addrparse_header, body::Body, dateparse, DispositionType, ParsedMail}; - -use crate::error::{Error, Result}; -use crate::pb3::jwebmail::mimeheader::ContentDisposition::*; -use crate::pb3::jwebmail::{ - mail_body::Multipart, mail_header::MailAddr, ListMailHeader, MIMEHeader, MIMEPart, Mail, - MailBody, MailHeader, +use maildir::MailEntryError; +use mailparse::{ + addrparse_header, body::Body, dateparse, DispositionType, MailParseError, ParsedMail, }; -fn parse_mail_addrs(inp: &mailparse::MailHeader) -> Result> { +use crate::de_jmhoffmann_jwebmail_mailstorage::MIMEHeader_content_dispo as CD; +use crate::de_jmhoffmann_jwebmail_mailstorage::*; + +fn parse_mail_addrs( + inp: &mailparse::MailHeader, +) -> std::result::Result, MailParseError> { let mut mal = addrparse_header(inp)?; Ok(mal @@ -17,17 +18,16 @@ fn parse_mail_addrs(inp: &mailparse::MailHeader) -> Result> { mailparse::MailAddr::Group(mut g) => g .addrs .drain(..) - .map(|s| { - let mut r = MailAddr::new(); - r.name = Some(s.display_name.unwrap_or_default()); - r.address = s.addr; - r + .map(|s| MailAddr { + name: Some(s.display_name.unwrap_or_default()), + address: s.addr, }) .collect(), mailparse::MailAddr::Single(s) => { - let mut addr = MailAddr::new(); - addr.name = Some(s.display_name.unwrap_or_default()); - addr.address = s.addr; + let addr = MailAddr { + name: Some(s.display_name.unwrap_or_default()), + address: s.addr, + }; vec![addr] } }) @@ -44,43 +44,44 @@ fn get_received(me: &mut maildir::MailEntry) -> i64 { }) } -pub fn me_to_lmh(mut me: maildir::MailEntry) -> Result { - let mut lmh = ListMailHeader::new(); - lmh.byte_size = me.path().metadata()?.len(); - lmh.unread = !me.is_seen(); - lmh.rec_date = DateTime::from_timestamp(get_received(&mut me), 0) - .unwrap() - .to_rfc3339(); - lmh.mid = me.id().to_owned(); - lmh.header = Some(parse_mail_header(&me.parsed()?)?).into(); +pub fn me_to_lmh( + mut me: maildir::MailEntry, +) -> std::result::Result { + let lmh = ListMailHeader { + byte_size: me.path().metadata()?.len() as i64, + unread: !me.is_seen(), + rec_date: DateTime::from_timestamp(get_received(&mut me), 0) + .unwrap() + .to_rfc3339(), + mid: me.id().to_owned(), + header: parse_mail_header(&me.parsed()?)?, + }; Ok(lmh) } -pub fn parse_mail_content(v: &ParsedMail) -> Result { - let mut c = MIMEHeader::new(); - - { - let mut val = v.ctype.mimetype.clone(); - if let Some(i) = val.find(';') { - val.truncate(i); - } - let j = val.find('/').unwrap(); - c.subtype = val.split_off(j + 1); - val.pop(); - c.maintype = val; +pub fn parse_mail_content(v: &ParsedMail) -> MIMEHeader { + let mut val = v.ctype.mimetype.clone(); + if let Some(i) = val.find(';') { + val.truncate(i); } + let j = val.find('/').unwrap(); + let subtype = val.split_off(j + 1); + val.pop(); + let maintype = val; + + let mut file_name = None; - match v.get_content_disposition().disposition { - DispositionType::Inline => c.contentdispo = CONTENT_DISPOSITION_INLINE.into(), + let cd = match v.get_content_disposition().disposition { + DispositionType::Inline => CD::inline, DispositionType::Attachment => { - c.contentdispo = CONTENT_DISPOSITION_ATTACHMENT.into(); if let Some(fname) = v.get_content_disposition().params.remove("filename") { - c.file_name = Some(fname); + file_name = Some(fname); } + CD::attachment } - _ => {} - } + _ => CD::none, + }; for h in &v.headers { let mut key = h.get_key(); @@ -89,29 +90,44 @@ pub fn parse_mail_content(v: &ParsedMail) -> Result { key.make_ascii_lowercase(); if key == "filename" { - c.file_name = Some(val); + file_name = Some(val); } } - Ok(c) + MIMEHeader { + mime_type: MIMEHeader_mime_type { + main_type: maintype, + sub_type: subtype, + }, + content_dispo: cd, + file_name, + } } -fn parse_mail_header(pm: &ParsedMail) -> Result { +fn parse_mail_header(pm: &ParsedMail) -> std::result::Result { let v = &pm.headers; - let mut mh = MailHeader::new(); - let mut mimeh = MIMEHeader::new(); - - { - let mut val = pm.ctype.mimetype.clone(); - if let Some(i) = val.find(';') { - val.truncate(i); - } - let j = val.find('/').unwrap(); - mimeh.subtype = val.split_off(j + 1); - val.pop(); - mimeh.maintype = val; + let mut val = pm.ctype.mimetype.clone(); + if let Some(i) = val.find(';') { + val.truncate(i); } + let j = val.find('/').unwrap(); + let sub_type = val.split_off(j + 1); + val.pop(); + let main_type = val; + + let mut file_name = None; + let mut content_dispo = CD::none; + let mut cc = vec![]; + let mut bcc = vec![]; + let mut comments = vec![]; + let mut keywords = vec![]; + let mut reply_to = vec![]; + let mut written_from = vec![]; + let mut send_to = vec![]; + let mut subject = String::new(); + let mut sender = None; + let mut send_date = String::new(); let mut key = String::new(); @@ -123,102 +139,130 @@ fn parse_mail_header(pm: &ParsedMail) -> Result { match key.as_str() { "date" => { - mh.send_date = DateTime::from_timestamp(dateparse(&val)?, 0) + send_date = DateTime::from_timestamp(dateparse(&val)?, 0) .unwrap() .to_rfc3339() } "from" => { - if !mh.written_from.is_empty() { - return Err(Error::SortOrder("from already set".into())); + if !written_from.is_empty() { + const FROM_MULTIPLE_TIMES_ERROR: &str = "from already set"; + return Err(MailParseError::Generic(FROM_MULTIPLE_TIMES_ERROR)); } - mh.written_from = parse_mail_addrs(y)? + written_from = parse_mail_addrs(y)? } - "sender" => mh.sender = parse_mail_addrs(y)?.drain(0..1).next().into(), - "reply-to" => mh.reply_to = parse_mail_addrs(y)?, - "to" => mh.send_to = parse_mail_addrs(y)?, - "cc" => mh.cc = parse_mail_addrs(y)?, - "bcc" => mh.bcc = parse_mail_addrs(y)?, + "sender" => sender = parse_mail_addrs(y)?.drain(0..1).next(), + "reply-to" => reply_to = parse_mail_addrs(y)?, + "to" => send_to = parse_mail_addrs(y)?, + "cc" => cc = parse_mail_addrs(y)?, + "bcc" => bcc = parse_mail_addrs(y)?, "subject" => { - mh.subject = val; + subject = val; } "comments" => { - mh.comments.push(val); + comments.push(val); } "keywords" => { - mh.keywords.push(val); + keywords.push(val); } "mime-version" => { strip_comments(&mut val); if val.trim() != "1.0" { - return Err(Error::MailEntryError(maildir::MailEntryError::DateError( - "unknown mime version", - ))); + const UNKNOWN_MIME_VERSION_ERROR: &str = "unknown mime version"; + return Err(MailParseError::Generic(UNKNOWN_MIME_VERSION_ERROR)); } } "content-disposition" => { let cd = val.to_ascii_lowercase(); match cd.as_ref() { - "inline" => mimeh.contentdispo = CONTENT_DISPOSITION_INLINE.into(), - "attachment" => mimeh.contentdispo = CONTENT_DISPOSITION_ATTACHMENT.into(), + "inline" => content_dispo = CD::inline, + "attachment" => content_dispo = CD::attachment, _ => {} }; } "filename" => { - mimeh.file_name = Some(val); + file_name = Some(val); } _ => {} }; key.clear(); } - mh.mime = Some(mimeh).into(); - Ok(mh) + Ok(MailHeader { + written_from, + sender, + send_to, + cc, + bcc, + reply_to, + subject, + send_date, + keywords, + comments, + mime: MIMEHeader { + mime_type: MIMEHeader_mime_type { + main_type, + sub_type, + }, + content_dispo, + file_name, + }, + }) } -fn parse_mail_body(pm: &ParsedMail) -> Result { +fn parse_mail_body(pm: &ParsedMail) -> std::result::Result { let body = if pm.ctype.mimetype.starts_with("message/") { - let mut mb = MailBody::new(); - mb.set_mail(parsed_mail_to_mail(mailparse::parse_mail( - pm.get_body()?.as_ref(), - )?)?); - mb + MailBody { + mail: Some(Box::new(parsed_mail_to_mail(mailparse::parse_mail( + pm.get_body()?.as_ref(), + )?)?)), + discrete: None, + multipart: None, + } } else if pm.subparts.is_empty() && pm.ctype.mimetype.starts_with("text/") { - let mut mb = MailBody::new(); - mb.set_discrete(pm.get_body()?); - mb + MailBody { + discrete: Some(pm.get_body()?), + multipart: None, + mail: None, + } } else if pm.subparts.is_empty() { - let b = match pm.get_body_encoded() { - Body::Base64(eb) => { - let db = eb.get_raw(); - if db.len() < 512 * 1024 { - String::from_utf8_lossy(db).into_owned() - } else { - String::new() + MailBody { + discrete: Some(match pm.get_body_encoded() { + Body::Base64(eb) => { + let db = eb.get_raw(); + if db.len() < 512 * 1024 { + String::from_utf8_lossy(db).into_owned() + } else { + String::new() + } } - } - Body::SevenBit(eb) => eb.get_as_string()?, - Body::QuotedPrintable(eb) => eb.get_decoded_as_string()?, - _ => todo!(), - }; - let mut mb = MailBody::new(); - mb.set_discrete(b); - mb + Body::SevenBit(eb) => eb.get_as_string()?, + Body::QuotedPrintable(eb) => eb.get_decoded_as_string()?, + _ => todo!(), + }), + multipart: None, + mail: None, + } } else { - let mut mb = MailBody::new(); - let mut mp = Multipart::new(); - mp.parts = pm - .subparts - .iter() - .map(|part| -> Result { - let mut mp = MIMEPart::new(); - mp.mime_header = Some(parse_mail_content(part)?).into(); - mp.body = Some(parse_mail_body(part)?).into(); - Ok(mp) - }) - .filter_map(|p| p.ok()) - .collect(); - mb.set_multipart(mp); - mb + let mp = Multipart { + parts: pm + .subparts + .iter() + .map(|part| -> std::result::Result { + Ok(MIMEPart { + mime_header: parse_mail_content(part), + body: parse_mail_body(part)?, + }) + }) + .filter_map(|p| p.ok()) + .collect(), + preamble: None, + epilogue: None, + }; + MailBody { + multipart: Some(mp), + discrete: None, + mail: None, + } }; Ok(body) } @@ -316,86 +360,9 @@ fn strip_comments(s: &mut String) { } } -pub fn parsed_mail_to_mail(pm: ParsedMail) -> Result { - let mut m = Mail::new(); - m.head = Some(parse_mail_header(&pm)?).into(); - m.body = Some(parse_mail_body(&pm)?).into(); - - Ok(m) -} - -/* -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn comment() { - let mut x = r#"(this is ((some) text)) a "some text with (comment \" in) quotes)(" (example) \( included) (xx)b()"#.to_owned(); - strip_comments(&mut x); - assert_eq!( - &x, - r#" a "some text with (comment \" in) quotes)(" \( included) b"# - ); - } - - #[test] - fn unclosed_comment() { - let mut x = "(this is (some text) example b".to_owned(); - strip_comments(&mut x); - assert_eq!(&x, "(this is example b"); - } - - #[test] - fn find_first_pair() { - let mut r = find_pair(0, "abc def"); - assert_eq!(r, None); - - r = find_pair(0, "abc ( def"); - assert_eq!(r, None); - - r = find_pair(0, "abc ) def"); - assert_eq!(r, None); - - let s = "(abc) def"; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 0..5); - assert_eq!(&s[i], "(abc)"); - } else { - assert!(false, "Got None expected Some!"); - } - - let s = "abc (def) ghi"; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 4..9); - assert_eq!(&s[i], "(def)"); - } else { - assert!(false, "Got None expected Some!"); - } - - let s = "(abc (def) ghi"; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 5..10); - assert_eq!(&s[i], "(def)"); - } else { - assert!(false, "Got None expected Some!"); - } - - let s = "abc ((def) ghi)"; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 4..15); - assert_eq!(&s[i], "((def) ghi)"); - } else { - assert!(false, "Got None expected Some!"); - } - - let s = r#" a "some text with (comment \" in) quotes)(" (example)"#; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 45..54); - assert_eq!(&s[i], "(example)"); - } else { - assert!(false, "Got None expected Some!"); - } - } +pub fn parsed_mail_to_mail(pm: ParsedMail) -> std::result::Result { + Ok(Mail { + head: parse_mail_header(&pm)?, + body: parse_mail_body(&pm)?, + }) } -*/ -- cgit v1.2.3