Browse Source

Split into crates 2

main
Manos Pitsidianakis 11 months ago
parent
commit
ae36860ad1
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 104
      Cargo.lock
  2. 137
      README.md
  3. 1
      cli/Cargo.toml
  4. 5
      cli/README.md
  5. 37
      cli/src/main.rs
  6. 1
      core/Cargo.toml
  7. 35
      core/migrations/2020-09-09-165759_membership_enabled/down.sql
  8. 5
      core/migrations/2020-09-09-165759_membership_enabled/up.sql
  9. 138
      core/src/db.rs
  10. 1
      core/src/lib.rs
  11. 67
      core/src/models.rs
  12. 20
      core/src/post.rs
  13. 1
      core/src/schema.rs
  14. 1
      core/src/schema.sql
  15. 1
      core/src/schema.sql.m4

104
Cargo.lock

@ -131,12 +131,12 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "atty"
version = "0.2.14"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
dependencies = [
"hermit-abi",
"libc",
"termion",
"winapi 0.3.9",
]
@ -378,7 +378,7 @@ dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"lazy_static",
"lazy_static 1.4.0",
"maybe-uninit",
"memoffset",
"scopeguard",
@ -403,7 +403,7 @@ checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if",
"lazy_static",
"lazy_static 1.4.0",
]
[[package]]
@ -893,6 +893,7 @@ checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9"
dependencies = [
"autocfg",
"hashbrown",
"serde",
]
[[package]]
@ -948,6 +949,12 @@ checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
[[package]]
name = "lazy_static"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
@ -1036,6 +1043,7 @@ dependencies = [
"chrono",
"diesel",
"error-chain",
"log 0.4.11",
"melib",
"rusqlite",
"serde",
@ -1049,6 +1057,7 @@ name = "mailpot-cli"
version = "0.1.0"
dependencies = [
"mailpot",
"stderrlog",
"structopt",
]
@ -1085,11 +1094,13 @@ dependencies = [
"data-encoding",
"encoding",
"futures",
"indexmap",
"libc",
"libloading",
"native-tls",
"nix",
"nom",
"once_cell",
"serde",
"serde_derive",
"smallvec",
@ -1181,7 +1192,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
dependencies = [
"lazy_static",
"lazy_static 1.4.0",
"libc",
"log 0.4.11",
"openssl",
@ -1276,6 +1287,12 @@ dependencies = [
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "object"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1302,7 +1319,7 @@ dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"lazy_static",
"lazy_static 1.4.0",
"libc",
"openssl-sys",
]
@ -1546,6 +1563,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
dependencies = [
"redox_syscall",
]
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1668,7 +1694,7 @@ version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"lazy_static 1.4.0",
"winapi 0.3.9",
]
@ -1739,6 +1765,12 @@ dependencies = [
]
[[package]]
name = "sha1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1811,6 +1843,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stderrlog"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25"
dependencies = [
"atty",
"chrono",
"log 0.4.11",
"termcolor",
"thread_local",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1823,7 +1868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5472fb24d7e80ae84a7801b7978f95a19ec32cb1876faea59ab711eb901976"
dependencies = [
"clap",
"lazy_static",
"lazy_static 1.4.0",
"structopt-derive",
]
@ -1889,6 +1934,27 @@ dependencies = [
]
[[package]]
name = "termcolor"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
dependencies = [
"winapi-util",
]
[[package]]
name = "termion"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1898,6 +1964,16 @@ dependencies = [
]
[[package]]
name = "thread_local"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14"
dependencies = [
"lazy_static 0.2.11",
"unreachable",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -2011,6 +2087,15 @@ dependencies = [
]
[[package]]
name = "unreachable"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
dependencies = [
"void",
]
[[package]]
name = "url"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -2029,6 +2114,7 @@ checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
"rand",
"serde",
"sha1",
]
[[package]]

137
README.md

@ -1,5 +1,4 @@
# Mailpot
## WIP mailing list manager
# Mailpot - WIP mailing list manager
Crates:
@ -7,6 +6,19 @@ Crates:
- `cli` a command line tool to manage lists
- `rest-http` a REST http server to manage lists
## Project goals
- easy setup
- extensible through Rust API as a library
- extensible through HTTP REST API as an HTTP server, with webhooks
- basic management through CLI
- replaceable lightweight web archiver
- custom storage?
- useful for both newsletters, discussions
## Examples
```text
% mpot help
mailpot 0.1.0
@ -31,3 +43,124 @@ SUBCOMMANDS:
list-lists Lists all registered mailing lists
post Post message from STDIN to list
```
### Receiving mail
```shell
$ cat list-request.eml | cargo run --bin mpot -- -vvvvvv post --dry-run
```
<details><summary>output</summary>
```shell
TRACE - Received envelope to post: Envelope {
Subject: "unsubscribe",
Date: "Tue, 04 Aug 2020 14:10:13 +0300",
From: [
Address::Mailbox {
display_name: "Mxxxx Pxxxxxxxxxxxx",
address_spec: "exxxxx@localhost",
},
],
To: [
Address::Mailbox {
display_name: "",
address_spec: "test-announce+request@localhost",
},
],
Message-ID: "<ejduu.fddf8sgen4j7@localhost>",
In-Reply-To: None,
References: None,
Hash: 12581897380059220314,
}
TRACE - unsubscribe action for addresses [Address::Mailbox { display_name: "Mxxxx Pxxxxxxxxxxxx", address_spec: "exxxxx@localhost" }] in list [#2 test-announce] test announcements <test-announce@localhost>
TRACE - Is post related to list [#1 test] Test list <test@localhost>? false
```
</details>
```shell
$ cat list-post.eml | cargo run --bin mpot -- -vvvvvv post --dry-run
```
<details><summary>output</summary>
```shell
TRACE - Received envelope to post: Envelope {
Subject: "[test-announce] new test releases",
Date: "Tue, 04 Aug 2020 14:10:13 +0300",
From: [
Address::Mailbox {
display_name: "Mxxxx Pxxxxxxxxxxxx",
address_spec: "exxxxx@localhost",
},
],
To: [
Address::Mailbox {
display_name: "",
address_spec: "test-announce@localhost",
},
],
Message-ID: "<ejduu.sddf8sgen4j7@localhost>",
In-Reply-To: None,
References: None,
Hash: 10220641455578979007,
}
TRACE - Is post related to list [#1 test] Test list <test@localhost>? false
TRACE - Is post related to list [#2 test-announce] test announcements <test-announce@localhost>? true
TRACE - Examining list "test announcements" <test-announce@localhost>
TRACE - List members [
ListMembership {
list: 2,
address: "exxxxx@localhost",
name: None,
digest: false,
hide_address: false,
receive_duplicates: false,
receive_own_posts: true,
receive_confirmation: true,
enabled: true,
},
]
TRACE - Running FixCRLF filter
TRACE - Running PostRightsCheck filter
TRACE - Running AddListHeaders filter
TRACE - Running FinalizeRecipients filter
TRACE - examining member ListMembership { list: 2, address: "exxxxx@localhost", name: None, digest: false, hide_address: false, receive_duplicates: false, receive_own_posts: true, receive_confirmation: true, enabled: true }
TRACE - member is submitter
TRACE - Member gets copy
TRACE - result Ok(
Post {
list: MailingList {
pk: 2,
name: "test announcements",
id: "test-announce",
address: "test-announce@localhost",
description: None,
archive_url: None,
},
from: Address::Mailbox {
display_name: "Mxxxx Pxxxxxxxxxxxx",
address_spec: "exxxxx@localhost",
},
members: 1,
bytes: 851,
policy: None,
to: [
Address::Mailbox {
display_name: "",
address_spec: "test-announce@localhost",
},
],
action: Accept {
recipients: [
Address::Mailbox {
display_name: "",
address_spec: "exxxxx@localhost",
},
],
digests: [],
},
},
)
```
</details>

1
cli/Cargo.toml

@ -18,3 +18,4 @@ path = "src/main.rs"
[dependencies]
mailpot = { version = "0.1.0", path = "../core" }
structopt = "0.3.16"
stderrlog = "0.4"

5
cli/README.md

@ -0,0 +1,5 @@
# mailpot-cli
```shell
cargo run --bin mpot -- help
```

37
cli/src/main.rs

@ -18,6 +18,7 @@
*/
extern crate mailpot;
extern crate stderrlog;
pub use mailpot::config::*;
pub use mailpot::db::*;
@ -44,6 +45,15 @@ struct Opt {
config: Option<PathBuf>,
#[structopt(flatten)]
cmd: Command,
/// Silence all output
#[structopt(short = "q", long = "quiet")]
quiet: bool,
/// Verbose mode (-v, -vv, -vvv, etc)
#[structopt(short = "v", long = "verbose", parse(from_occurrences))]
verbose: usize,
/// Timestamp (sec, ms, ns, none)
#[structopt(short = "t", long = "timestamp")]
ts: Option<stderrlog::Timestamp>,
}
#[derive(Debug, StructOpt)]
@ -108,6 +118,8 @@ enum ListCommand {
receive_duplicates: Option<bool>,
#[structopt(long)]
receive_own_posts: Option<bool>,
#[structopt(long)]
enabled: Option<bool>,
},
RemoveMember {
#[structopt(long)]
@ -144,6 +156,21 @@ fn run_app(opt: Opt) -> Result<()> {
} else {
for l in lists {
println!("- {} {:?}", l.id, l);
let list_owners = db.get_list_owners(l.pk)?;
if list_owners.is_empty() {
println!("\tList owners: None");
} else {
println!("\tList owners:");
for o in db.get_list_owners(l.pk)? {
println!("\t- {}", o);
}
}
if let Some(s) = db.get_list_policy(l.pk)? {
println!("\tList policy: {}", s);
} else {
println!("\tList policy: None");
}
println!("");
}
}
}
@ -176,6 +203,7 @@ fn run_app(opt: Opt) -> Result<()> {
receive_confirmation,
receive_duplicates,
receive_own_posts,
enabled,
} => {
db.add_member(
list.pk,
@ -188,6 +216,7 @@ fn run_app(opt: Opt) -> Result<()> {
receive_confirmation: receive_confirmation.unwrap_or(true),
receive_duplicates: receive_duplicates.unwrap_or(true),
receive_own_posts: receive_own_posts.unwrap_or(false),
enabled: enabled.unwrap_or(true),
},
)?;
}
@ -260,6 +289,14 @@ fn run_app(opt: Opt) -> Result<()> {
fn main() -> std::result::Result<(), i32> {
let opt = Opt::from_args();
stderrlog::new()
.module(module_path!())
.module("mailpot")
.quiet(opt.quiet)
.verbosity(opt.verbose)
.timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off))
.init()
.unwrap();
if let Err(err) = run_app(opt) {
println!("{}", err);
std::process::exit(-1);

1
core/Cargo.toml

@ -20,4 +20,5 @@ rusqlite = {version = "0.20.0"}
serde = { version = "1.0.114" }
serde_json = "1.0.57"
toml = "^0.5"
log = "0.4"
xdg = "2.1.0"

35
core/migrations/2020-09-09-165759_membership_enabled/down.sql

@ -0,0 +1,35 @@
-- This file should undo anything in `up.sql`
BEGIN TRANSACTION;
PRAGMA foreign_keys = false;
CREATE TEMPORARY TABLE membership_backup(
list INTEGER NOT NULL,
address TEXT NOT NULL,
name TEXT,
enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1,
digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
receive_own_posts BOOLEAN CHECK (receive_own_posts in (0, 1)) NOT NULL DEFAULT 0,
receive_confirmation BOOLEAN CHECK (receive_confirmation in (0, 1)) NOT NULL DEFAULT 1,
PRIMARY KEY (list, address),
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
);
INSERT INTO membership_backup SELECT list,address,name,enabled,digest,hide_address,receive_own_posts,receive_duplicates,receive_confirmation FROM membership;
DROP TABLE membership;
CREATE TABLE membership(
list INTEGER NOT NULL,
address TEXT NOT NULL,
name TEXT,
digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
receive_own_posts BOOLEAN CHECK (receive_own_posts in (0, 1)) NOT NULL DEFAULT 0,
receive_confirmation BOOLEAN CHECK (receive_confirmation in (0, 1)) NOT NULL DEFAULT 1,
PRIMARY KEY (list, address),
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
);
INSERT INTO membership SELECT list,address,name,digest,hide_address,receive_own_posts,receive_duplicates,receive_confirmation FROM membership_backup;
DROP TABLE membership_backup;
PRAGMA foreign_keys = true;
COMMIT TRANSACTION;

5
core/migrations/2020-09-09-165759_membership_enabled/up.sql

@ -0,0 +1,5 @@
-- Your SQL goes here
PRAGMA foreign_keys = false;
ALTER TABLE membership ADD COLUMN enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1;
PRAGMA foreign_keys = true;

138
core/src/db.rs

@ -123,7 +123,7 @@ impl Database {
let db_path = Self::db_path()?;
let mut set_mode = false;
if !db_path.exists() {
println!("Creating {} database in {}", DB_NAME, db_path.display());
info!("Creating {} database in {}", DB_NAME, db_path.display());
set_mode = true;
}
let conn = SqliteConnection::establish(&db_path.to_str().unwrap())?;
@ -152,32 +152,75 @@ impl Database {
}
pub fn post(&self, env: Envelope, raw: &[u8]) -> Result<()> {
trace!("Received envelope to post: {:#?}", &env);
let mut lists = self.list_lists()?;
let tos = env
.to()
.iter()
.map(|addr| addr.get_email())
.collect::<Vec<String>>();
let tos = env.to().iter().cloned().collect::<Vec<_>>();
if tos.is_empty() {
return Err("Envelope To: field is empty!".into());
}
if env.from().is_empty() {
return Err("Envelope From: field is empty!".into());
}
for t in &tos {
if let Some((addr, subaddr)) = t.subaddress("+") {
lists.retain(|list| {
if !addr.contains_address(&list.list_address()) {
return true;
}
if let Err(err) = self.request(
list,
match subaddr.as_str() {
"subscribe" | "request" if env.subject().trim() == "subscribe" => {
ListRequest::Subscribe
}
"unsubscribe" | "request" if env.subject().trim() == "unsubscribe" => {
ListRequest::Unsubscribe
}
"request" => ListRequest::Other(env.subject().trim().to_string()),
_ => {
trace!(
"unknown action = {} for addresses {:?} in list {}",
subaddr,
env.from(),
list
);
ListRequest::Other(subaddr.trim().to_string())
}
},
&env,
raw,
) {
info!("Processing request returned error: {}", err);
}
false
});
}
}
lists.retain(|list| {
trace!(
"Is post related to list {}? {}",
&list,
tos.iter().any(|a| a.contains_address(&list.list_address()))
);
lists.retain(|list| tos.iter().any(|a| a == &list.address));
tos.iter().any(|a| a.contains_address(&list.list_address()))
});
if lists.is_empty() {
return Err("Envelope To: field doesn't contain any known lists!".into());
return Ok(());
}
let mut configuration = crate::config::Configuration::new();
crate::config::CONFIG.with(|f| {
configuration = f.borrow().clone();
});
trace!("Configuration is {:#?}", &configuration);
use crate::post::{Post, PostAction};
for mut list in lists {
trace!("Examining list {}", list.list_id());
let filters = self.get_list_filters(&list);
let memberships = self.list_members(list.pk)?;
trace!("List members {:#?}", &memberships);
let mut post = Post {
policy: self.get_list_policy(list.pk)?,
list_owners: self.get_list_owners(list.pk)?,
@ -191,10 +234,9 @@ impl Database {
let result = filters
.into_iter()
.fold(Ok(&mut post), |p, f| p.and_then(|p| f.feed(p)));
eprintln!("result {:?}", result);
trace!("result {:#?}", result);
let Post { bytes, action, .. } = post;
eprintln!("send_mail {:?}", &configuration.send_mail);
match configuration.send_mail {
crate::config::SendMail::Smtp(ref smtp_conf) => {
let smtp_conf = smtp_conf.clone();
@ -233,4 +275,80 @@ impl Database {
Ok(())
}
pub fn request(
&self,
list: &MailingList,
request: ListRequest,
env: &Envelope,
_raw: &[u8],
) -> Result<()> {
match request {
ListRequest::Subscribe => {
trace!(
"subscribe action for addresses {:?} in list {}",
env.from(),
list
);
let list_policy = self.get_list_policy(list.pk)?;
let approval_needed = list_policy
.as_ref()
.map(|p| p.approval_needed)
.unwrap_or(false);
for f in env.from() {
let membership = ListMembership {
list: list.pk,
address: f.get_email(),
name: f.get_display_name(),
digest: false,
hide_address: false,
receive_duplicates: true,
receive_own_posts: false,
receive_confirmation: true,
enabled: !approval_needed,
};
if approval_needed {
//FIXME: send notification to list-owner
}
if let Err(_err) = self.add_member(list.pk, membership) {
//FIXME: send failure notice to f
} else {
//FIXME: send success notice
}
}
}
ListRequest::Unsubscribe => {
trace!(
"unsubscribe action for addresses {:?} in list {}",
env.from(),
list
);
for f in env.from() {
if let Err(_err) = self.remove_member(list.pk, &f.get_email()) {
//FIXME: send failure notice to f
} else {
//FIXME: send success notice to f
}
}
}
ListRequest::Other(ref req) if req == "owner" => {
trace!(
"list-owner mail action for addresses {:?} in list {}",
env.from(),
list
);
//FIXME: mail to list-owner
}
ListRequest::Other(ref req) => {
trace!(
"unknown request action {} for addresses {:?} in list {}",
req,
env.from(),
list
);
}
}
Ok(())
}
}

1
core/src/lib.rs

@ -20,6 +20,7 @@
#![recursion_limit = "1024"]
//#![warn(missing_docs)]
use log::{info, trace};
#[macro_use]
extern crate error_chain;
#[macro_use]

67
core/src/models.rs

@ -20,6 +20,8 @@
use super::*;
use schema::*;
use melib::email::Address;
#[derive(Debug, Clone, Insertable, Queryable, Deserialize, Serialize)]
#[table_name = "mailing_lists"]
pub struct MailingList {
@ -69,6 +71,10 @@ impl MailingList {
pub fn list_archive(&self) -> Option<String> {
self.archive_url.as_ref().map(|url| format!("<{}>", url))
}
pub fn list_address(&self) -> Address {
Address::new(Some(self.name.clone()), self.address.clone())
}
}
#[derive(Debug, Clone, Insertable, Queryable, Deserialize, Serialize)]
@ -82,36 +88,29 @@ pub struct ListMembership {
pub receive_duplicates: bool,
pub receive_own_posts: bool,
pub receive_confirmation: bool,
pub enabled: bool,
}
impl std::fmt::Display for ListMembership {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(name) = self.name.as_ref() {
write!(
fmt,
"{} <{}> [digest: {}, hide_address: {}]",
name, self.address, self.digest, self.hide_address
)
} else {
write!(
fmt,
"{} [digest: {}, hide_address: {}]",
self.address, self.digest, self.hide_address
)
}
write!(
fmt,
"{} [digest: {}, hide_address: {} {}]",
self.into_address(),
self.digest,
self.hide_address,
if self.enabled {
"enabled"
} else {
"not enabled"
},
)
}
}
impl ListMembership {
pub fn into_address(&self) -> melib::email::Address {
use melib::email::Address;
use melib::email::StrBuilder;
use melib::MailboxAddress;
if let Some(name) = self.name.as_ref() {
melib::make_address!(name, self.address)
} else {
melib::make_address!("", self.address)
}
pub fn into_address(&self) -> Address {
Address::new(self.name.clone(), self.address.clone())
}
}
@ -142,15 +141,7 @@ pub struct ListOwner {
impl std::fmt::Display for ListOwner {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(ref name) = self.name {
write!(
fmt,
"[#{} {}] \"{}\" <{}>",
self.pk, self.list, name, self.address
)
} else {
write!(fmt, "[#{} {}] {}", self.pk, self.list, self.address)
}
write!(fmt, "[#{} {}] {}", self.pk, self.list, self.into_address())
}
}
@ -165,6 +156,20 @@ impl From<ListOwner> for ListMembership {
receive_duplicates: true,
receive_own_posts: false,
receive_confirmation: true,
enabled: true,
}
}
}
impl ListOwner {
pub fn into_address(&self) -> Address {
Address::new(self.name.clone(), self.address.clone())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ListRequest {
Subscribe,
Unsubscribe,
Other(String),
}

20
core/src/post.rs

@ -77,8 +77,10 @@ impl PostFilter for PostRightsCheck {
self: Box<Self>,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
trace!("Running PostRightsCheck filter");
if let Some(ref policy) = post.policy {
if policy.announce_only {
trace!("post policy is announce_only");
let owner_addresses = post
.list_owners
.iter()
@ -87,15 +89,23 @@ impl PostFilter for PostRightsCheck {
lm.into_address()
})
.collect::<Vec<Address>>();
trace!("Owner addresses are: {:#?}", &owner_addresses);
trace!("Envelope from is: {:?}", &post.from);
if !owner_addresses.iter().any(|addr| *addr == post.from) {
trace!("Envelope From does not include any owner");
return Err("You are not allowed to post on this list.".to_string());
}
} else if policy.subscriber_only {
trace!("post policy is subscriber_only");
let email_from = post.from.get_email();
trace!("post from is {:?}", &email_from);
trace!("post memberships are {:#?}", &post.memberships);
if !post.memberships.iter().any(|lm| lm.address == email_from) {
trace!("Envelope from is not subscribed to this list");
return Err("You are not subscribed to this list.".to_string());
}
} else if policy.approval_needed {
trace!("post policy says approval_needed");
post.action = PostAction::Defer {
reason: "Approval from the list's moderators is required.".to_string(),
};
@ -112,6 +122,7 @@ impl PostFilter for FixCRLF {
self: Box<Self>,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
trace!("Running FixCRLF filter");
use std::io::prelude::*;
let mut new_vec = Vec::with_capacity(post.bytes.len());
for line in post.bytes.lines() {
@ -130,6 +141,7 @@ impl PostFilter for AddListHeaders {
self: Box<Self>,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
trace!("Running AddListHeaders filter");
let (mut headers, body) = melib::email::parser::mail(&post.bytes).unwrap();
let list_id = post.list.list_id();
headers.push((&b"List-ID"[..], list_id.as_bytes()));
@ -174,6 +186,7 @@ impl PostFilter for ArchivedAtLink {
self: Box<Self>,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
trace!("Running ArchivedAtLink filter");
Ok(post)
}
}
@ -186,14 +199,20 @@ impl PostFilter for FinalizeRecipients {
self: Box<Self>,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
trace!("Running FinalizeRecipients filter");
let mut recipients = vec![];
let mut digests = vec![];
let email_from = post.from.get_email();
for member in post.memberships {
trace!("examining member {:?}", &member);
if member.address != email_from {
trace!("member is submitter");
}
if member.digest {
if (member.address == email_from && member.receive_own_posts)
|| (member.address != email_from)
{
trace!("Member gets digest");
digests.push(member.into_address());
}
continue;
@ -201,6 +220,7 @@ impl PostFilter for FinalizeRecipients {
if (member.address == email_from && member.receive_own_posts)
|| (member.address != email_from)
{
trace!("Member gets copy");
recipients.push(member.into_address());
}
// TODO:

1
core/src/schema.rs

@ -28,6 +28,7 @@ table! {
receive_duplicates -> Bool,
receive_own_posts -> Bool,
receive_confirmation -> Bool,
enabled -> Bool,
}
}

1
core/src/schema.sql

@ -32,6 +32,7 @@ CREATE TABLE IF NOT EXISTS membership (
list INTEGER NOT NULL,
address TEXT NOT NULL,
name TEXT,
enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1,
digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,

1
core/src/schema.sql.m4

@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS membership (
list INTEGER NOT NULL,
address TEXT NOT NULL,
name TEXT,
BOOLEAN_TYPE(enabled) DEFAULT BOOLEAN_TRUE(),
BOOLEAN_TYPE(digest) DEFAULT BOOLEAN_FALSE(),
BOOLEAN_TYPE(hide_address) DEFAULT BOOLEAN_FALSE(),
BOOLEAN_TYPE(receive_duplicates) DEFAULT BOOLEAN_TRUE(),

Loading…
Cancel
Save