1
8
/*
2
 * This file is part of mailpot
3
 *
4
 * Copyright 2020 - Manos Pitsidianakis
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 */
19

            
20
#![deny(
21
    missing_docs,
22
    rustdoc::broken_intra_doc_links,
23
    /* groups */
24
    clippy::correctness,
25
    clippy::suspicious,
26
    clippy::complexity,
27
    clippy::perf,
28
    clippy::style,
29
    clippy::cargo,
30
    clippy::nursery,
31
    /* restriction */
32
    clippy::dbg_macro,
33
    clippy::rc_buffer,
34
    clippy::as_underscore,
35
    clippy::assertions_on_result_states,
36
    /* pedantic */
37
    clippy::cast_lossless,
38
    clippy::cast_possible_wrap,
39
    clippy::ptr_as_ptr,
40
    clippy::bool_to_int_with_if,
41
    clippy::borrow_as_ptr,
42
    clippy::case_sensitive_file_extension_comparisons,
43
    clippy::cast_lossless,
44
    clippy::cast_ptr_alignment,
45
    clippy::naive_bytecount
46
)]
47
#![allow(clippy::multiple_crate_versions, clippy::missing_const_for_fn)]
48

            
49
//! Mailing list manager library.
50
//!
51
//! Data is stored in a `sqlite3` database.
52
//! You can inspect the schema in [`SCHEMA`](crate::Connection::SCHEMA).
53
//!
54
//! # Usage
55
//!
56
//! `mailpot` can be used with the CLI tool in [`mailpot-cli`](mailpot-cli),
57
//! and/or in the web interface of the [`mailpot-web`](mailpot-web) crate.
58
//!
59
//! You can also directly use this crate as a library.
60
//!
61
//! # Example
62
//!
63
//! ```
64
//! use mailpot::{models::*, Configuration, Connection, SendMail};
65
//! # use tempfile::TempDir;
66
//!
67
//! # let tmp_dir = TempDir::new().unwrap();
68
//! # let db_path = tmp_dir.path().join("mpot.db");
69
//! # let config = Configuration {
70
//! #     send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
71
//! #     db_path: db_path.clone(),
72
//! #     data_path: tmp_dir.path().to_path_buf(),
73
//! #     administrators: vec![],
74
//! # };
75
//! #
76
//! # fn do_test(config: Configuration) -> mailpot::Result<()> {
77
//! let db = Connection::open_or_create_db(config)?.trusted();
78
//!
79
//! // Create a new mailing list
80
//! let list_pk = db
81
//!     .create_list(MailingList {
82
//!         pk: 0,
83
//!         name: "foobar chat".into(),
84
//!         id: "foo-chat".into(),
85
//!         address: "foo-chat@example.com".into(),
86
//!         topics: vec![],
87
//!         description: None,
88
//!         archive_url: None,
89
//!     })?
90
//!     .pk;
91
//!
92
//! db.set_list_post_policy(PostPolicy {
93
//!     pk: 0,
94
//!     list: list_pk,
95
//!     announce_only: false,
96
//!     subscription_only: true,
97
//!     approval_needed: false,
98
//!     open: false,
99
//!     custom: false,
100
//! })?;
101
//!
102
//! // Drop privileges; we can only process new e-mail and modify subscriptions from now on.
103
//! let mut db = db.untrusted();
104
//!
105
//! assert_eq!(db.list_subscriptions(list_pk)?.len(), 0);
106
//! assert_eq!(db.list_posts(list_pk, None)?.len(), 0);
107
//!
108
//! // Process a subscription request e-mail
109
//! let subscribe_bytes = b"From: Name <user@example.com>
110
//! To: <foo-chat+subscribe@example.com>
111
//! Subject: subscribe
112
//! Date: Thu, 29 Oct 2020 13:58:16 +0000
113
//! Message-ID: <1@example.com>
114
//!
115
//! ";
116
//! let envelope = melib::Envelope::from_bytes(subscribe_bytes, None)?;
117
//! db.post(&envelope, subscribe_bytes, /* dry_run */ false)?;
118
//!
119
//! assert_eq!(db.list_subscriptions(list_pk)?.len(), 1);
120
//! assert_eq!(db.list_posts(list_pk, None)?.len(), 0);
121
//!
122
//! // Process a post
123
//! let post_bytes = b"From: Name <user@example.com>
124
//! To: <foo-chat@example.com>
125
//! Subject: my first post
126
//! Date: Thu, 29 Oct 2020 14:01:09 +0000
127
//! Message-ID: <2@example.com>
128
//!
129
//! Hello
130
//! ";
131
//! let envelope = melib::Envelope::from_bytes(post_bytes, None).expect("Could not parse message");
132
//! db.post(&envelope, post_bytes, /* dry_run */ false)?;
133
//!
134
//! assert_eq!(db.list_subscriptions(list_pk)?.len(), 1);
135
//! assert_eq!(db.list_posts(list_pk, None)?.len(), 1);
136
//! # Ok(())
137
//! # }
138
//! # do_test(config);
139
//! ```
140

            
141
/* Annotations:
142
 *
143
 * Global tags (in tagref format <https://github.com/stepchowfun/tagref>) for source code
144
 * annotation:
145
 *
146
 * - [tag:needs_unit_test]
147
 * - [tag:needs_user_doc]
148
 * - [tag:needs_dev_doc]
149
 * - [tag:FIXME]
150
 * - [tag:TODO]
151
 * - [tag:VERIFY] Verify whether this is the correct way to do something
152
 */
153

            
154
/// Error library
155
pub extern crate anyhow;
156
/// Date library
157
pub extern crate chrono;
158
/// Sql library
159
pub extern crate rusqlite;
160

            
161
/// Alias for [`chrono::DateTime<chrono::Utc>`].
162
pub type DateTime = chrono::DateTime<chrono::Utc>;
163

            
164
/// Serde
165
#[macro_use]
166
pub extern crate serde;
167
/// Log
168
pub extern crate log;
169
/// melib
170
pub extern crate melib;
171
/// serde_json
172
pub extern crate serde_json;
173

            
174
mod config;
175
mod connection;
176
mod errors;
177
pub mod mail;
178
pub mod message_filters;
179
pub mod models;
180
pub mod policies;
181
#[cfg(not(target_os = "windows"))]
182
pub mod postfix;
183
pub mod posts;
184
pub mod queue;
185
pub mod submission;
186
pub mod subscriptions;
187
mod templates;
188

            
189
pub use config::{Configuration, SendMail};
190
pub use connection::{transaction, *};
191
pub use errors::*;
192
use models::*;
193
pub use templates::*;
194

            
195
/// A `mailto:` value.
196
4
#[derive(Debug, Clone, Deserialize, Serialize)]
197
pub struct MailtoAddress {
198
    /// E-mail address.
199
    pub address: String,
200
    /// Optional subject value.
201
    pub subject: Option<String>,
202
}
203

            
204
#[doc = include_str!("../../README.md")]
205
#[cfg(doctest)]
206
pub struct ReadmeDoctests;
207

            
208
/// Trait for stripping carets ('<','>') from Message IDs.
209
pub trait StripCarets {
210
    /// If `self` is surrounded by carets, strip them.
211
    fn strip_carets(&self) -> &str;
212
}
213

            
214
impl StripCarets for &str {
215
7
    fn strip_carets(&self) -> &str {
216
7
        let mut self_ref = self.trim();
217
7
        if self_ref.starts_with('<') && self_ref.ends_with('>') {
218
7
            self_ref = &self_ref[1..self_ref.len().saturating_sub(1)];
219
        }
220
7
        self_ref
221
7
    }
222
}
223

            
224
use percent_encoding::CONTROLS;
225
pub use percent_encoding::{utf8_percent_encode, AsciiSet};
226

            
227
// from https://github.com/servo/rust-url/blob/master/url/src/parser.rs
228
const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`');
229
const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
230

            
231
/// Set for percent encoding URL components.
232
pub const PATH_SEGMENT: &AsciiSet = &PATH.add(b'/').add(b'%');