Author: Manos Pitsidianakis [el13635@mail.ntua.gr]
Hash: cdae585ee64e466e843b10dd043fe02dba7d4af0
Timestamp: Sat, 15 Aug 2020 19:10:49 +0000 (2 years ago)

+5222 -0 +/-24 browse
Initial commit
1diff --git a/.gitignore b/.gitignore
2new file mode 100644
3index 0000000..ea8c4bf
4--- /dev/null
5+++ b/.gitignore
6 @@ -0,0 +1 @@
7+ /target
8 diff --git a/Cargo.lock b/Cargo.lock
9new file mode 100644
10index 0000000..ac35ea5
11--- /dev/null
12+++ b/Cargo.lock
13 @@ -0,0 +1,2149 @@
14+ # This file is automatically @generated by Cargo.
15+ # It is not intended for manual editing.
16+ version = 3
17+
18+ [[package]]
19+ name = "addr2line"
20+ version = "0.17.0"
21+ source = "registry+https://github.com/rust-lang/crates.io-index"
22+ checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
23+ dependencies = [
24+ "gimli",
25+ ]
26+
27+ [[package]]
28+ name = "adler"
29+ version = "1.0.2"
30+ source = "registry+https://github.com/rust-lang/crates.io-index"
31+ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
32+
33+ [[package]]
34+ name = "ahash"
35+ version = "0.7.6"
36+ source = "registry+https://github.com/rust-lang/crates.io-index"
37+ checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
38+ dependencies = [
39+ "getrandom",
40+ "once_cell",
41+ "version_check",
42+ ]
43+
44+ [[package]]
45+ name = "ansi_term"
46+ version = "0.12.1"
47+ source = "registry+https://github.com/rust-lang/crates.io-index"
48+ checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
49+ dependencies = [
50+ "winapi",
51+ ]
52+
53+ [[package]]
54+ name = "async-channel"
55+ version = "1.6.1"
56+ source = "registry+https://github.com/rust-lang/crates.io-index"
57+ checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
58+ dependencies = [
59+ "concurrent-queue",
60+ "event-listener",
61+ "futures-core",
62+ ]
63+
64+ [[package]]
65+ name = "async-executor"
66+ version = "1.4.1"
67+ source = "registry+https://github.com/rust-lang/crates.io-index"
68+ checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
69+ dependencies = [
70+ "async-task",
71+ "concurrent-queue",
72+ "fastrand",
73+ "futures-lite",
74+ "once_cell",
75+ "slab",
76+ ]
77+
78+ [[package]]
79+ name = "async-fs"
80+ version = "1.5.0"
81+ source = "registry+https://github.com/rust-lang/crates.io-index"
82+ checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2"
83+ dependencies = [
84+ "async-lock",
85+ "blocking",
86+ "futures-lite",
87+ ]
88+
89+ [[package]]
90+ name = "async-io"
91+ version = "1.6.0"
92+ source = "registry+https://github.com/rust-lang/crates.io-index"
93+ checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
94+ dependencies = [
95+ "concurrent-queue",
96+ "futures-lite",
97+ "libc",
98+ "log",
99+ "once_cell",
100+ "parking",
101+ "polling",
102+ "slab",
103+ "socket2",
104+ "waker-fn",
105+ "winapi",
106+ ]
107+
108+ [[package]]
109+ name = "async-lock"
110+ version = "2.5.0"
111+ source = "registry+https://github.com/rust-lang/crates.io-index"
112+ checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
113+ dependencies = [
114+ "event-listener",
115+ ]
116+
117+ [[package]]
118+ name = "async-net"
119+ version = "1.6.1"
120+ source = "registry+https://github.com/rust-lang/crates.io-index"
121+ checksum = "5373304df79b9b4395068fb080369ec7178608827306ce4d081cba51cac551df"
122+ dependencies = [
123+ "async-io",
124+ "blocking",
125+ "futures-lite",
126+ ]
127+
128+ [[package]]
129+ name = "async-process"
130+ version = "1.4.0"
131+ source = "registry+https://github.com/rust-lang/crates.io-index"
132+ checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c"
133+ dependencies = [
134+ "async-io",
135+ "blocking",
136+ "cfg-if",
137+ "event-listener",
138+ "futures-lite",
139+ "libc",
140+ "once_cell",
141+ "signal-hook",
142+ "winapi",
143+ ]
144+
145+ [[package]]
146+ name = "async-stream"
147+ version = "0.3.3"
148+ source = "registry+https://github.com/rust-lang/crates.io-index"
149+ checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
150+ dependencies = [
151+ "async-stream-impl",
152+ "futures-core",
153+ ]
154+
155+ [[package]]
156+ name = "async-stream-impl"
157+ version = "0.3.3"
158+ source = "registry+https://github.com/rust-lang/crates.io-index"
159+ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
160+ dependencies = [
161+ "proc-macro2",
162+ "quote",
163+ "syn",
164+ ]
165+
166+ [[package]]
167+ name = "async-task"
168+ version = "4.2.0"
169+ source = "registry+https://github.com/rust-lang/crates.io-index"
170+ checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
171+
172+ [[package]]
173+ name = "atomic-waker"
174+ version = "1.0.0"
175+ source = "registry+https://github.com/rust-lang/crates.io-index"
176+ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
177+
178+ [[package]]
179+ name = "atty"
180+ version = "0.2.14"
181+ source = "registry+https://github.com/rust-lang/crates.io-index"
182+ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
183+ dependencies = [
184+ "hermit-abi",
185+ "libc",
186+ "winapi",
187+ ]
188+
189+ [[package]]
190+ name = "autocfg"
191+ version = "1.1.0"
192+ source = "registry+https://github.com/rust-lang/crates.io-index"
193+ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
194+
195+ [[package]]
196+ name = "backtrace"
197+ version = "0.3.65"
198+ source = "registry+https://github.com/rust-lang/crates.io-index"
199+ checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
200+ dependencies = [
201+ "addr2line",
202+ "cc",
203+ "cfg-if",
204+ "libc",
205+ "miniz_oxide",
206+ "object",
207+ "rustc-demangle",
208+ ]
209+
210+ [[package]]
211+ name = "base64"
212+ version = "0.13.0"
213+ source = "registry+https://github.com/rust-lang/crates.io-index"
214+ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
215+
216+ [[package]]
217+ name = "bincode"
218+ version = "1.3.3"
219+ source = "registry+https://github.com/rust-lang/crates.io-index"
220+ checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
221+ dependencies = [
222+ "serde",
223+ ]
224+
225+ [[package]]
226+ name = "bitflags"
227+ version = "1.3.2"
228+ source = "registry+https://github.com/rust-lang/crates.io-index"
229+ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
230+
231+ [[package]]
232+ name = "block-buffer"
233+ version = "0.9.0"
234+ source = "registry+https://github.com/rust-lang/crates.io-index"
235+ checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
236+ dependencies = [
237+ "generic-array",
238+ ]
239+
240+ [[package]]
241+ name = "block-buffer"
242+ version = "0.10.2"
243+ source = "registry+https://github.com/rust-lang/crates.io-index"
244+ checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
245+ dependencies = [
246+ "generic-array",
247+ ]
248+
249+ [[package]]
250+ name = "blocking"
251+ version = "1.2.0"
252+ source = "registry+https://github.com/rust-lang/crates.io-index"
253+ checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc"
254+ dependencies = [
255+ "async-channel",
256+ "async-task",
257+ "atomic-waker",
258+ "fastrand",
259+ "futures-lite",
260+ "once_cell",
261+ ]
262+
263+ [[package]]
264+ name = "buf_redux"
265+ version = "0.8.4"
266+ source = "registry+https://github.com/rust-lang/crates.io-index"
267+ checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
268+ dependencies = [
269+ "memchr",
270+ "safemem",
271+ ]
272+
273+ [[package]]
274+ name = "byteorder"
275+ version = "1.4.3"
276+ source = "registry+https://github.com/rust-lang/crates.io-index"
277+ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
278+
279+ [[package]]
280+ name = "bytes"
281+ version = "1.1.0"
282+ source = "registry+https://github.com/rust-lang/crates.io-index"
283+ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
284+
285+ [[package]]
286+ name = "cache-padded"
287+ version = "1.2.0"
288+ source = "registry+https://github.com/rust-lang/crates.io-index"
289+ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
290+
291+ [[package]]
292+ name = "cc"
293+ version = "1.0.73"
294+ source = "registry+https://github.com/rust-lang/crates.io-index"
295+ checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
296+
297+ [[package]]
298+ name = "cfg-if"
299+ version = "1.0.0"
300+ source = "registry+https://github.com/rust-lang/crates.io-index"
301+ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
302+
303+ [[package]]
304+ name = "chrono"
305+ version = "0.4.19"
306+ source = "registry+https://github.com/rust-lang/crates.io-index"
307+ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
308+ dependencies = [
309+ "libc",
310+ "num-integer",
311+ "num-traits",
312+ "serde",
313+ "time",
314+ "winapi",
315+ ]
316+
317+ [[package]]
318+ name = "clap"
319+ version = "2.34.0"
320+ source = "registry+https://github.com/rust-lang/crates.io-index"
321+ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
322+ dependencies = [
323+ "ansi_term",
324+ "atty",
325+ "bitflags",
326+ "strsim",
327+ "textwrap",
328+ "unicode-width",
329+ "vec_map",
330+ ]
331+
332+ [[package]]
333+ name = "concurrent-queue"
334+ version = "1.2.2"
335+ source = "registry+https://github.com/rust-lang/crates.io-index"
336+ checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
337+ dependencies = [
338+ "cache-padded",
339+ ]
340+
341+ [[package]]
342+ name = "core-foundation"
343+ version = "0.9.3"
344+ source = "registry+https://github.com/rust-lang/crates.io-index"
345+ checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
346+ dependencies = [
347+ "core-foundation-sys",
348+ "libc",
349+ ]
350+
351+ [[package]]
352+ name = "core-foundation-sys"
353+ version = "0.8.3"
354+ source = "registry+https://github.com/rust-lang/crates.io-index"
355+ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
356+
357+ [[package]]
358+ name = "cpufeatures"
359+ version = "0.2.2"
360+ source = "registry+https://github.com/rust-lang/crates.io-index"
361+ checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
362+ dependencies = [
363+ "libc",
364+ ]
365+
366+ [[package]]
367+ name = "crypto-common"
368+ version = "0.1.3"
369+ source = "registry+https://github.com/rust-lang/crates.io-index"
370+ checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
371+ dependencies = [
372+ "generic-array",
373+ "typenum",
374+ ]
375+
376+ [[package]]
377+ name = "data-encoding"
378+ version = "2.3.2"
379+ source = "registry+https://github.com/rust-lang/crates.io-index"
380+ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
381+
382+ [[package]]
383+ name = "digest"
384+ version = "0.9.0"
385+ source = "registry+https://github.com/rust-lang/crates.io-index"
386+ checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
387+ dependencies = [
388+ "generic-array",
389+ ]
390+
391+ [[package]]
392+ name = "digest"
393+ version = "0.10.3"
394+ source = "registry+https://github.com/rust-lang/crates.io-index"
395+ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
396+ dependencies = [
397+ "block-buffer 0.10.2",
398+ "crypto-common",
399+ ]
400+
401+ [[package]]
402+ name = "dirs"
403+ version = "4.0.0"
404+ source = "registry+https://github.com/rust-lang/crates.io-index"
405+ checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
406+ dependencies = [
407+ "dirs-sys",
408+ ]
409+
410+ [[package]]
411+ name = "dirs-sys"
412+ version = "0.3.7"
413+ source = "registry+https://github.com/rust-lang/crates.io-index"
414+ checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
415+ dependencies = [
416+ "libc",
417+ "redox_users",
418+ "winapi",
419+ ]
420+
421+ [[package]]
422+ name = "encoding"
423+ version = "0.2.33"
424+ source = "registry+https://github.com/rust-lang/crates.io-index"
425+ checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
426+ dependencies = [
427+ "encoding-index-japanese",
428+ "encoding-index-korean",
429+ "encoding-index-simpchinese",
430+ "encoding-index-singlebyte",
431+ "encoding-index-tradchinese",
432+ ]
433+
434+ [[package]]
435+ name = "encoding-index-japanese"
436+ version = "1.20141219.5"
437+ source = "registry+https://github.com/rust-lang/crates.io-index"
438+ checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
439+ dependencies = [
440+ "encoding_index_tests",
441+ ]
442+
443+ [[package]]
444+ name = "encoding-index-korean"
445+ version = "1.20141219.5"
446+ source = "registry+https://github.com/rust-lang/crates.io-index"
447+ checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
448+ dependencies = [
449+ "encoding_index_tests",
450+ ]
451+
452+ [[package]]
453+ name = "encoding-index-simpchinese"
454+ version = "1.20141219.5"
455+ source = "registry+https://github.com/rust-lang/crates.io-index"
456+ checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
457+ dependencies = [
458+ "encoding_index_tests",
459+ ]
460+
461+ [[package]]
462+ name = "encoding-index-singlebyte"
463+ version = "1.20141219.5"
464+ source = "registry+https://github.com/rust-lang/crates.io-index"
465+ checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
466+ dependencies = [
467+ "encoding_index_tests",
468+ ]
469+
470+ [[package]]
471+ name = "encoding-index-tradchinese"
472+ version = "1.20141219.5"
473+ source = "registry+https://github.com/rust-lang/crates.io-index"
474+ checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
475+ dependencies = [
476+ "encoding_index_tests",
477+ ]
478+
479+ [[package]]
480+ name = "encoding_index_tests"
481+ version = "0.1.4"
482+ source = "registry+https://github.com/rust-lang/crates.io-index"
483+ checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
484+
485+ [[package]]
486+ name = "error-chain"
487+ version = "0.12.4"
488+ source = "registry+https://github.com/rust-lang/crates.io-index"
489+ checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
490+ dependencies = [
491+ "backtrace",
492+ "version_check",
493+ ]
494+
495+ [[package]]
496+ name = "event-listener"
497+ version = "2.5.2"
498+ source = "registry+https://github.com/rust-lang/crates.io-index"
499+ checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
500+
501+ [[package]]
502+ name = "fallible-iterator"
503+ version = "0.2.0"
504+ source = "registry+https://github.com/rust-lang/crates.io-index"
505+ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
506+
507+ [[package]]
508+ name = "fallible-streaming-iterator"
509+ version = "0.1.9"
510+ source = "registry+https://github.com/rust-lang/crates.io-index"
511+ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
512+
513+ [[package]]
514+ name = "fastrand"
515+ version = "1.7.0"
516+ source = "registry+https://github.com/rust-lang/crates.io-index"
517+ checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
518+ dependencies = [
519+ "instant",
520+ ]
521+
522+ [[package]]
523+ name = "fnv"
524+ version = "1.0.7"
525+ source = "registry+https://github.com/rust-lang/crates.io-index"
526+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
527+
528+ [[package]]
529+ name = "foreign-types"
530+ version = "0.3.2"
531+ source = "registry+https://github.com/rust-lang/crates.io-index"
532+ checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
533+ dependencies = [
534+ "foreign-types-shared",
535+ ]
536+
537+ [[package]]
538+ name = "foreign-types-shared"
539+ version = "0.1.1"
540+ source = "registry+https://github.com/rust-lang/crates.io-index"
541+ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
542+
543+ [[package]]
544+ name = "form_urlencoded"
545+ version = "1.0.1"
546+ source = "registry+https://github.com/rust-lang/crates.io-index"
547+ checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
548+ dependencies = [
549+ "matches",
550+ "percent-encoding",
551+ ]
552+
553+ [[package]]
554+ name = "futures"
555+ version = "0.3.21"
556+ source = "registry+https://github.com/rust-lang/crates.io-index"
557+ checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
558+ dependencies = [
559+ "futures-channel",
560+ "futures-core",
561+ "futures-executor",
562+ "futures-io",
563+ "futures-sink",
564+ "futures-task",
565+ "futures-util",
566+ ]
567+
568+ [[package]]
569+ name = "futures-channel"
570+ version = "0.3.21"
571+ source = "registry+https://github.com/rust-lang/crates.io-index"
572+ checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
573+ dependencies = [
574+ "futures-core",
575+ "futures-sink",
576+ ]
577+
578+ [[package]]
579+ name = "futures-core"
580+ version = "0.3.21"
581+ source = "registry+https://github.com/rust-lang/crates.io-index"
582+ checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
583+
584+ [[package]]
585+ name = "futures-executor"
586+ version = "0.3.21"
587+ source = "registry+https://github.com/rust-lang/crates.io-index"
588+ checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
589+ dependencies = [
590+ "futures-core",
591+ "futures-task",
592+ "futures-util",
593+ ]
594+
595+ [[package]]
596+ name = "futures-io"
597+ version = "0.3.21"
598+ source = "registry+https://github.com/rust-lang/crates.io-index"
599+ checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
600+
601+ [[package]]
602+ name = "futures-lite"
603+ version = "1.12.0"
604+ source = "registry+https://github.com/rust-lang/crates.io-index"
605+ checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
606+ dependencies = [
607+ "fastrand",
608+ "futures-core",
609+ "futures-io",
610+ "memchr",
611+ "parking",
612+ "pin-project-lite",
613+ "waker-fn",
614+ ]
615+
616+ [[package]]
617+ name = "futures-macro"
618+ version = "0.3.21"
619+ source = "registry+https://github.com/rust-lang/crates.io-index"
620+ checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
621+ dependencies = [
622+ "proc-macro2",
623+ "quote",
624+ "syn",
625+ ]
626+
627+ [[package]]
628+ name = "futures-sink"
629+ version = "0.3.21"
630+ source = "registry+https://github.com/rust-lang/crates.io-index"
631+ checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
632+
633+ [[package]]
634+ name = "futures-task"
635+ version = "0.3.21"
636+ source = "registry+https://github.com/rust-lang/crates.io-index"
637+ checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
638+
639+ [[package]]
640+ name = "futures-util"
641+ version = "0.3.21"
642+ source = "registry+https://github.com/rust-lang/crates.io-index"
643+ checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
644+ dependencies = [
645+ "futures-channel",
646+ "futures-core",
647+ "futures-io",
648+ "futures-macro",
649+ "futures-sink",
650+ "futures-task",
651+ "memchr",
652+ "pin-project-lite",
653+ "pin-utils",
654+ "slab",
655+ ]
656+
657+ [[package]]
658+ name = "generic-array"
659+ version = "0.14.5"
660+ source = "registry+https://github.com/rust-lang/crates.io-index"
661+ checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
662+ dependencies = [
663+ "typenum",
664+ "version_check",
665+ ]
666+
667+ [[package]]
668+ name = "getrandom"
669+ version = "0.2.6"
670+ source = "registry+https://github.com/rust-lang/crates.io-index"
671+ checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
672+ dependencies = [
673+ "cfg-if",
674+ "libc",
675+ "wasi 0.10.2+wasi-snapshot-preview1",
676+ ]
677+
678+ [[package]]
679+ name = "gimli"
680+ version = "0.26.1"
681+ source = "registry+https://github.com/rust-lang/crates.io-index"
682+ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
683+
684+ [[package]]
685+ name = "h2"
686+ version = "0.3.13"
687+ source = "registry+https://github.com/rust-lang/crates.io-index"
688+ checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57"
689+ dependencies = [
690+ "bytes",
691+ "fnv",
692+ "futures-core",
693+ "futures-sink",
694+ "futures-util",
695+ "http",
696+ "indexmap",
697+ "slab",
698+ "tokio",
699+ "tokio-util 0.7.1",
700+ "tracing",
701+ ]
702+
703+ [[package]]
704+ name = "hashbrown"
705+ version = "0.11.2"
706+ source = "registry+https://github.com/rust-lang/crates.io-index"
707+ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
708+ dependencies = [
709+ "ahash",
710+ ]
711+
712+ [[package]]
713+ name = "hashlink"
714+ version = "0.7.0"
715+ source = "registry+https://github.com/rust-lang/crates.io-index"
716+ checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
717+ dependencies = [
718+ "hashbrown",
719+ ]
720+
721+ [[package]]
722+ name = "headers"
723+ version = "0.3.7"
724+ source = "registry+https://github.com/rust-lang/crates.io-index"
725+ checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d"
726+ dependencies = [
727+ "base64",
728+ "bitflags",
729+ "bytes",
730+ "headers-core",
731+ "http",
732+ "httpdate",
733+ "mime",
734+ "sha-1 0.10.0",
735+ ]
736+
737+ [[package]]
738+ name = "headers-core"
739+ version = "0.2.0"
740+ source = "registry+https://github.com/rust-lang/crates.io-index"
741+ checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
742+ dependencies = [
743+ "http",
744+ ]
745+
746+ [[package]]
747+ name = "heck"
748+ version = "0.3.3"
749+ source = "registry+https://github.com/rust-lang/crates.io-index"
750+ checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
751+ dependencies = [
752+ "unicode-segmentation",
753+ ]
754+
755+ [[package]]
756+ name = "hermit-abi"
757+ version = "0.1.19"
758+ source = "registry+https://github.com/rust-lang/crates.io-index"
759+ checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
760+ dependencies = [
761+ "libc",
762+ ]
763+
764+ [[package]]
765+ name = "http"
766+ version = "0.2.7"
767+ source = "registry+https://github.com/rust-lang/crates.io-index"
768+ checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
769+ dependencies = [
770+ "bytes",
771+ "fnv",
772+ "itoa",
773+ ]
774+
775+ [[package]]
776+ name = "http-body"
777+ version = "0.4.4"
778+ source = "registry+https://github.com/rust-lang/crates.io-index"
779+ checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
780+ dependencies = [
781+ "bytes",
782+ "http",
783+ "pin-project-lite",
784+ ]
785+
786+ [[package]]
787+ name = "httparse"
788+ version = "1.7.1"
789+ source = "registry+https://github.com/rust-lang/crates.io-index"
790+ checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
791+
792+ [[package]]
793+ name = "httpdate"
794+ version = "1.0.2"
795+ source = "registry+https://github.com/rust-lang/crates.io-index"
796+ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
797+
798+ [[package]]
799+ name = "hyper"
800+ version = "0.14.18"
801+ source = "registry+https://github.com/rust-lang/crates.io-index"
802+ checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2"
803+ dependencies = [
804+ "bytes",
805+ "futures-channel",
806+ "futures-core",
807+ "futures-util",
808+ "h2",
809+ "http",
810+ "http-body",
811+ "httparse",
812+ "httpdate",
813+ "itoa",
814+ "pin-project-lite",
815+ "socket2",
816+ "tokio",
817+ "tower-service",
818+ "tracing",
819+ "want",
820+ ]
821+
822+ [[package]]
823+ name = "idna"
824+ version = "0.2.3"
825+ source = "registry+https://github.com/rust-lang/crates.io-index"
826+ checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
827+ dependencies = [
828+ "matches",
829+ "unicode-bidi",
830+ "unicode-normalization",
831+ ]
832+
833+ [[package]]
834+ name = "indexmap"
835+ version = "1.8.1"
836+ source = "registry+https://github.com/rust-lang/crates.io-index"
837+ checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
838+ dependencies = [
839+ "autocfg",
840+ "hashbrown",
841+ "serde",
842+ ]
843+
844+ [[package]]
845+ name = "instant"
846+ version = "0.1.12"
847+ source = "registry+https://github.com/rust-lang/crates.io-index"
848+ checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
849+ dependencies = [
850+ "cfg-if",
851+ ]
852+
853+ [[package]]
854+ name = "itoa"
855+ version = "1.0.1"
856+ source = "registry+https://github.com/rust-lang/crates.io-index"
857+ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
858+
859+ [[package]]
860+ name = "lazy_static"
861+ version = "1.4.0"
862+ source = "registry+https://github.com/rust-lang/crates.io-index"
863+ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
864+
865+ [[package]]
866+ name = "libc"
867+ version = "0.2.125"
868+ source = "registry+https://github.com/rust-lang/crates.io-index"
869+ checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
870+
871+ [[package]]
872+ name = "libloading"
873+ version = "0.7.3"
874+ source = "registry+https://github.com/rust-lang/crates.io-index"
875+ checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
876+ dependencies = [
877+ "cfg-if",
878+ "winapi",
879+ ]
880+
881+ [[package]]
882+ name = "libsqlite3-sys"
883+ version = "0.24.2"
884+ source = "registry+https://github.com/rust-lang/crates.io-index"
885+ checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
886+ dependencies = [
887+ "pkg-config",
888+ "vcpkg",
889+ ]
890+
891+ [[package]]
892+ name = "lock_api"
893+ version = "0.4.7"
894+ source = "registry+https://github.com/rust-lang/crates.io-index"
895+ checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
896+ dependencies = [
897+ "autocfg",
898+ "scopeguard",
899+ ]
900+
901+ [[package]]
902+ name = "log"
903+ version = "0.4.16"
904+ source = "registry+https://github.com/rust-lang/crates.io-index"
905+ checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
906+ dependencies = [
907+ "cfg-if",
908+ ]
909+
910+ [[package]]
911+ name = "mailpot"
912+ version = "0.1.0"
913+ dependencies = [
914+ "chrono",
915+ "error-chain",
916+ "log",
917+ "melib",
918+ "rusqlite",
919+ "serde",
920+ "serde_json",
921+ "toml",
922+ "xdg",
923+ ]
924+
925+ [[package]]
926+ name = "mailpot-cli"
927+ version = "0.1.0"
928+ dependencies = [
929+ "mailpot",
930+ "stderrlog",
931+ "structopt",
932+ ]
933+
934+ [[package]]
935+ name = "mailpot-http"
936+ version = "0.1.0"
937+ dependencies = [
938+ "mailpot",
939+ "tokio",
940+ "warp",
941+ ]
942+
943+ [[package]]
944+ name = "matches"
945+ version = "0.1.9"
946+ source = "registry+https://github.com/rust-lang/crates.io-index"
947+ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
948+
949+ [[package]]
950+ name = "melib"
951+ version = "0.7.2"
952+ source = "git+https://github.com/meli/meli?branch=master#721891c2955e9f5e223949bde2dd43604cec8390"
953+ dependencies = [
954+ "async-stream",
955+ "base64",
956+ "bincode",
957+ "bitflags",
958+ "data-encoding",
959+ "encoding",
960+ "futures",
961+ "indexmap",
962+ "libc",
963+ "libloading",
964+ "native-tls",
965+ "nix",
966+ "nom",
967+ "serde",
968+ "serde_derive",
969+ "smallvec",
970+ "smol",
971+ "unicode-segmentation",
972+ "uuid",
973+ "xdg",
974+ "xdg-utils",
975+ ]
976+
977+ [[package]]
978+ name = "memchr"
979+ version = "2.5.0"
980+ source = "registry+https://github.com/rust-lang/crates.io-index"
981+ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
982+
983+ [[package]]
984+ name = "memoffset"
985+ version = "0.6.5"
986+ source = "registry+https://github.com/rust-lang/crates.io-index"
987+ checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
988+ dependencies = [
989+ "autocfg",
990+ ]
991+
992+ [[package]]
993+ name = "mime"
994+ version = "0.3.16"
995+ source = "registry+https://github.com/rust-lang/crates.io-index"
996+ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
997+
998+ [[package]]
999+ name = "mime_guess"
1000+ version = "2.0.4"
1001+ source = "registry+https://github.com/rust-lang/crates.io-index"
1002+ checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
1003+ dependencies = [
1004+ "mime",
1005+ "unicase",
1006+ ]
1007+
1008+ [[package]]
1009+ name = "minimal-lexical"
1010+ version = "0.2.1"
1011+ source = "registry+https://github.com/rust-lang/crates.io-index"
1012+ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1013+
1014+ [[package]]
1015+ name = "miniz_oxide"
1016+ version = "0.5.1"
1017+ source = "registry+https://github.com/rust-lang/crates.io-index"
1018+ checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
1019+ dependencies = [
1020+ "adler",
1021+ ]
1022+
1023+ [[package]]
1024+ name = "mio"
1025+ version = "0.8.2"
1026+ source = "registry+https://github.com/rust-lang/crates.io-index"
1027+ checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
1028+ dependencies = [
1029+ "libc",
1030+ "log",
1031+ "miow",
1032+ "ntapi",
1033+ "wasi 0.11.0+wasi-snapshot-preview1",
1034+ "winapi",
1035+ ]
1036+
1037+ [[package]]
1038+ name = "miow"
1039+ version = "0.3.7"
1040+ source = "registry+https://github.com/rust-lang/crates.io-index"
1041+ checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
1042+ dependencies = [
1043+ "winapi",
1044+ ]
1045+
1046+ [[package]]
1047+ name = "multipart"
1048+ version = "0.18.0"
1049+ source = "registry+https://github.com/rust-lang/crates.io-index"
1050+ checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
1051+ dependencies = [
1052+ "buf_redux",
1053+ "httparse",
1054+ "log",
1055+ "mime",
1056+ "mime_guess",
1057+ "quick-error",
1058+ "rand",
1059+ "safemem",
1060+ "tempfile",
1061+ "twoway",
1062+ ]
1063+
1064+ [[package]]
1065+ name = "native-tls"
1066+ version = "0.2.10"
1067+ source = "registry+https://github.com/rust-lang/crates.io-index"
1068+ checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
1069+ dependencies = [
1070+ "lazy_static",
1071+ "libc",
1072+ "log",
1073+ "openssl",
1074+ "openssl-probe",
1075+ "openssl-sys",
1076+ "schannel",
1077+ "security-framework",
1078+ "security-framework-sys",
1079+ "tempfile",
1080+ ]
1081+
1082+ [[package]]
1083+ name = "nix"
1084+ version = "0.24.1"
1085+ source = "registry+https://github.com/rust-lang/crates.io-index"
1086+ checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9"
1087+ dependencies = [
1088+ "bitflags",
1089+ "cfg-if",
1090+ "libc",
1091+ "memoffset",
1092+ ]
1093+
1094+ [[package]]
1095+ name = "nom"
1096+ version = "7.1.1"
1097+ source = "registry+https://github.com/rust-lang/crates.io-index"
1098+ checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
1099+ dependencies = [
1100+ "memchr",
1101+ "minimal-lexical",
1102+ ]
1103+
1104+ [[package]]
1105+ name = "ntapi"
1106+ version = "0.3.7"
1107+ source = "registry+https://github.com/rust-lang/crates.io-index"
1108+ checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
1109+ dependencies = [
1110+ "winapi",
1111+ ]
1112+
1113+ [[package]]
1114+ name = "num-integer"
1115+ version = "0.1.45"
1116+ source = "registry+https://github.com/rust-lang/crates.io-index"
1117+ checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
1118+ dependencies = [
1119+ "autocfg",
1120+ "num-traits",
1121+ ]
1122+
1123+ [[package]]
1124+ name = "num-traits"
1125+ version = "0.2.14"
1126+ source = "registry+https://github.com/rust-lang/crates.io-index"
1127+ checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
1128+ dependencies = [
1129+ "autocfg",
1130+ ]
1131+
1132+ [[package]]
1133+ name = "num_cpus"
1134+ version = "1.13.1"
1135+ source = "registry+https://github.com/rust-lang/crates.io-index"
1136+ checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
1137+ dependencies = [
1138+ "hermit-abi",
1139+ "libc",
1140+ ]
1141+
1142+ [[package]]
1143+ name = "object"
1144+ version = "0.28.3"
1145+ source = "registry+https://github.com/rust-lang/crates.io-index"
1146+ checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456"
1147+ dependencies = [
1148+ "memchr",
1149+ ]
1150+
1151+ [[package]]
1152+ name = "once_cell"
1153+ version = "1.10.0"
1154+ source = "registry+https://github.com/rust-lang/crates.io-index"
1155+ checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
1156+
1157+ [[package]]
1158+ name = "opaque-debug"
1159+ version = "0.3.0"
1160+ source = "registry+https://github.com/rust-lang/crates.io-index"
1161+ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
1162+
1163+ [[package]]
1164+ name = "openssl"
1165+ version = "0.10.38"
1166+ source = "registry+https://github.com/rust-lang/crates.io-index"
1167+ checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
1168+ dependencies = [
1169+ "bitflags",
1170+ "cfg-if",
1171+ "foreign-types",
1172+ "libc",
1173+ "once_cell",
1174+ "openssl-sys",
1175+ ]
1176+
1177+ [[package]]
1178+ name = "openssl-probe"
1179+ version = "0.1.5"
1180+ source = "registry+https://github.com/rust-lang/crates.io-index"
1181+ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
1182+
1183+ [[package]]
1184+ name = "openssl-sys"
1185+ version = "0.9.72"
1186+ source = "registry+https://github.com/rust-lang/crates.io-index"
1187+ checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
1188+ dependencies = [
1189+ "autocfg",
1190+ "cc",
1191+ "libc",
1192+ "pkg-config",
1193+ "vcpkg",
1194+ ]
1195+
1196+ [[package]]
1197+ name = "parking"
1198+ version = "2.0.0"
1199+ source = "registry+https://github.com/rust-lang/crates.io-index"
1200+ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
1201+
1202+ [[package]]
1203+ name = "parking_lot"
1204+ version = "0.12.0"
1205+ source = "registry+https://github.com/rust-lang/crates.io-index"
1206+ checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
1207+ dependencies = [
1208+ "lock_api",
1209+ "parking_lot_core",
1210+ ]
1211+
1212+ [[package]]
1213+ name = "parking_lot_core"
1214+ version = "0.9.3"
1215+ source = "registry+https://github.com/rust-lang/crates.io-index"
1216+ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
1217+ dependencies = [
1218+ "cfg-if",
1219+ "libc",
1220+ "redox_syscall",
1221+ "smallvec",
1222+ "windows-sys",
1223+ ]
1224+
1225+ [[package]]
1226+ name = "percent-encoding"
1227+ version = "2.1.0"
1228+ source = "registry+https://github.com/rust-lang/crates.io-index"
1229+ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
1230+
1231+ [[package]]
1232+ name = "pin-project"
1233+ version = "1.0.10"
1234+ source = "registry+https://github.com/rust-lang/crates.io-index"
1235+ checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
1236+ dependencies = [
1237+ "pin-project-internal",
1238+ ]
1239+
1240+ [[package]]
1241+ name = "pin-project-internal"
1242+ version = "1.0.10"
1243+ source = "registry+https://github.com/rust-lang/crates.io-index"
1244+ checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
1245+ dependencies = [
1246+ "proc-macro2",
1247+ "quote",
1248+ "syn",
1249+ ]
1250+
1251+ [[package]]
1252+ name = "pin-project-lite"
1253+ version = "0.2.9"
1254+ source = "registry+https://github.com/rust-lang/crates.io-index"
1255+ checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
1256+
1257+ [[package]]
1258+ name = "pin-utils"
1259+ version = "0.1.0"
1260+ source = "registry+https://github.com/rust-lang/crates.io-index"
1261+ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1262+
1263+ [[package]]
1264+ name = "pkg-config"
1265+ version = "0.3.25"
1266+ source = "registry+https://github.com/rust-lang/crates.io-index"
1267+ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
1268+
1269+ [[package]]
1270+ name = "polling"
1271+ version = "2.2.0"
1272+ source = "registry+https://github.com/rust-lang/crates.io-index"
1273+ checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
1274+ dependencies = [
1275+ "cfg-if",
1276+ "libc",
1277+ "log",
1278+ "wepoll-ffi",
1279+ "winapi",
1280+ ]
1281+
1282+ [[package]]
1283+ name = "ppv-lite86"
1284+ version = "0.2.16"
1285+ source = "registry+https://github.com/rust-lang/crates.io-index"
1286+ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
1287+
1288+ [[package]]
1289+ name = "proc-macro-error"
1290+ version = "1.0.4"
1291+ source = "registry+https://github.com/rust-lang/crates.io-index"
1292+ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
1293+ dependencies = [
1294+ "proc-macro-error-attr",
1295+ "proc-macro2",
1296+ "quote",
1297+ "syn",
1298+ "version_check",
1299+ ]
1300+
1301+ [[package]]
1302+ name = "proc-macro-error-attr"
1303+ version = "1.0.4"
1304+ source = "registry+https://github.com/rust-lang/crates.io-index"
1305+ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
1306+ dependencies = [
1307+ "proc-macro2",
1308+ "quote",
1309+ "version_check",
1310+ ]
1311+
1312+ [[package]]
1313+ name = "proc-macro2"
1314+ version = "1.0.37"
1315+ source = "registry+https://github.com/rust-lang/crates.io-index"
1316+ checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
1317+ dependencies = [
1318+ "unicode-xid",
1319+ ]
1320+
1321+ [[package]]
1322+ name = "quick-error"
1323+ version = "1.2.3"
1324+ source = "registry+https://github.com/rust-lang/crates.io-index"
1325+ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
1326+
1327+ [[package]]
1328+ name = "quote"
1329+ version = "1.0.18"
1330+ source = "registry+https://github.com/rust-lang/crates.io-index"
1331+ checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
1332+ dependencies = [
1333+ "proc-macro2",
1334+ ]
1335+
1336+ [[package]]
1337+ name = "rand"
1338+ version = "0.8.5"
1339+ source = "registry+https://github.com/rust-lang/crates.io-index"
1340+ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1341+ dependencies = [
1342+ "libc",
1343+ "rand_chacha",
1344+ "rand_core",
1345+ ]
1346+
1347+ [[package]]
1348+ name = "rand_chacha"
1349+ version = "0.3.1"
1350+ source = "registry+https://github.com/rust-lang/crates.io-index"
1351+ checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
1352+ dependencies = [
1353+ "ppv-lite86",
1354+ "rand_core",
1355+ ]
1356+
1357+ [[package]]
1358+ name = "rand_core"
1359+ version = "0.6.3"
1360+ source = "registry+https://github.com/rust-lang/crates.io-index"
1361+ checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
1362+ dependencies = [
1363+ "getrandom",
1364+ ]
1365+
1366+ [[package]]
1367+ name = "redox_syscall"
1368+ version = "0.2.13"
1369+ source = "registry+https://github.com/rust-lang/crates.io-index"
1370+ checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
1371+ dependencies = [
1372+ "bitflags",
1373+ ]
1374+
1375+ [[package]]
1376+ name = "redox_users"
1377+ version = "0.4.3"
1378+ source = "registry+https://github.com/rust-lang/crates.io-index"
1379+ checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
1380+ dependencies = [
1381+ "getrandom",
1382+ "redox_syscall",
1383+ "thiserror",
1384+ ]
1385+
1386+ [[package]]
1387+ name = "remove_dir_all"
1388+ version = "0.5.3"
1389+ source = "registry+https://github.com/rust-lang/crates.io-index"
1390+ checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1391+ dependencies = [
1392+ "winapi",
1393+ ]
1394+
1395+ [[package]]
1396+ name = "rusqlite"
1397+ version = "0.27.0"
1398+ source = "registry+https://github.com/rust-lang/crates.io-index"
1399+ checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"
1400+ dependencies = [
1401+ "bitflags",
1402+ "fallible-iterator",
1403+ "fallible-streaming-iterator",
1404+ "hashlink",
1405+ "libsqlite3-sys",
1406+ "memchr",
1407+ "smallvec",
1408+ ]
1409+
1410+ [[package]]
1411+ name = "rustc-demangle"
1412+ version = "0.1.21"
1413+ source = "registry+https://github.com/rust-lang/crates.io-index"
1414+ checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
1415+
1416+ [[package]]
1417+ name = "ryu"
1418+ version = "1.0.9"
1419+ source = "registry+https://github.com/rust-lang/crates.io-index"
1420+ checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
1421+
1422+ [[package]]
1423+ name = "safemem"
1424+ version = "0.3.3"
1425+ source = "registry+https://github.com/rust-lang/crates.io-index"
1426+ checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
1427+
1428+ [[package]]
1429+ name = "schannel"
1430+ version = "0.1.19"
1431+ source = "registry+https://github.com/rust-lang/crates.io-index"
1432+ checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
1433+ dependencies = [
1434+ "lazy_static",
1435+ "winapi",
1436+ ]
1437+
1438+ [[package]]
1439+ name = "scoped-tls"
1440+ version = "1.0.0"
1441+ source = "registry+https://github.com/rust-lang/crates.io-index"
1442+ checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
1443+
1444+ [[package]]
1445+ name = "scopeguard"
1446+ version = "1.1.0"
1447+ source = "registry+https://github.com/rust-lang/crates.io-index"
1448+ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1449+
1450+ [[package]]
1451+ name = "security-framework"
1452+ version = "2.6.1"
1453+ source = "registry+https://github.com/rust-lang/crates.io-index"
1454+ checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
1455+ dependencies = [
1456+ "bitflags",
1457+ "core-foundation",
1458+ "core-foundation-sys",
1459+ "libc",
1460+ "security-framework-sys",
1461+ ]
1462+
1463+ [[package]]
1464+ name = "security-framework-sys"
1465+ version = "2.6.1"
1466+ source = "registry+https://github.com/rust-lang/crates.io-index"
1467+ checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
1468+ dependencies = [
1469+ "core-foundation-sys",
1470+ "libc",
1471+ ]
1472+
1473+ [[package]]
1474+ name = "serde"
1475+ version = "1.0.137"
1476+ source = "registry+https://github.com/rust-lang/crates.io-index"
1477+ checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
1478+ dependencies = [
1479+ "serde_derive",
1480+ ]
1481+
1482+ [[package]]
1483+ name = "serde_derive"
1484+ version = "1.0.137"
1485+ source = "registry+https://github.com/rust-lang/crates.io-index"
1486+ checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
1487+ dependencies = [
1488+ "proc-macro2",
1489+ "quote",
1490+ "syn",
1491+ ]
1492+
1493+ [[package]]
1494+ name = "serde_json"
1495+ version = "1.0.80"
1496+ source = "registry+https://github.com/rust-lang/crates.io-index"
1497+ checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944"
1498+ dependencies = [
1499+ "itoa",
1500+ "ryu",
1501+ "serde",
1502+ ]
1503+
1504+ [[package]]
1505+ name = "serde_urlencoded"
1506+ version = "0.7.1"
1507+ source = "registry+https://github.com/rust-lang/crates.io-index"
1508+ checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1509+ dependencies = [
1510+ "form_urlencoded",
1511+ "itoa",
1512+ "ryu",
1513+ "serde",
1514+ ]
1515+
1516+ [[package]]
1517+ name = "sha-1"
1518+ version = "0.9.8"
1519+ source = "registry+https://github.com/rust-lang/crates.io-index"
1520+ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
1521+ dependencies = [
1522+ "block-buffer 0.9.0",
1523+ "cfg-if",
1524+ "cpufeatures",
1525+ "digest 0.9.0",
1526+ "opaque-debug",
1527+ ]
1528+
1529+ [[package]]
1530+ name = "sha-1"
1531+ version = "0.10.0"
1532+ source = "registry+https://github.com/rust-lang/crates.io-index"
1533+ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
1534+ dependencies = [
1535+ "cfg-if",
1536+ "cpufeatures",
1537+ "digest 0.10.3",
1538+ ]
1539+
1540+ [[package]]
1541+ name = "sha1_smol"
1542+ version = "1.0.0"
1543+ source = "registry+https://github.com/rust-lang/crates.io-index"
1544+ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
1545+
1546+ [[package]]
1547+ name = "signal-hook"
1548+ version = "0.3.13"
1549+ source = "registry+https://github.com/rust-lang/crates.io-index"
1550+ checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
1551+ dependencies = [
1552+ "libc",
1553+ "signal-hook-registry",
1554+ ]
1555+
1556+ [[package]]
1557+ name = "signal-hook-registry"
1558+ version = "1.4.0"
1559+ source = "registry+https://github.com/rust-lang/crates.io-index"
1560+ checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
1561+ dependencies = [
1562+ "libc",
1563+ ]
1564+
1565+ [[package]]
1566+ name = "slab"
1567+ version = "0.4.6"
1568+ source = "registry+https://github.com/rust-lang/crates.io-index"
1569+ checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
1570+
1571+ [[package]]
1572+ name = "smallvec"
1573+ version = "1.8.0"
1574+ source = "registry+https://github.com/rust-lang/crates.io-index"
1575+ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
1576+ dependencies = [
1577+ "serde",
1578+ ]
1579+
1580+ [[package]]
1581+ name = "smol"
1582+ version = "1.2.5"
1583+ source = "registry+https://github.com/rust-lang/crates.io-index"
1584+ checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4"
1585+ dependencies = [
1586+ "async-channel",
1587+ "async-executor",
1588+ "async-fs",
1589+ "async-io",
1590+ "async-lock",
1591+ "async-net",
1592+ "async-process",
1593+ "blocking",
1594+ "futures-lite",
1595+ "once_cell",
1596+ ]
1597+
1598+ [[package]]
1599+ name = "socket2"
1600+ version = "0.4.4"
1601+ source = "registry+https://github.com/rust-lang/crates.io-index"
1602+ checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
1603+ dependencies = [
1604+ "libc",
1605+ "winapi",
1606+ ]
1607+
1608+ [[package]]
1609+ name = "stderrlog"
1610+ version = "0.5.1"
1611+ source = "registry+https://github.com/rust-lang/crates.io-index"
1612+ checksum = "45a53e2eff3e94a019afa6265e8ee04cb05b9d33fe9f5078b14e4e391d155a38"
1613+ dependencies = [
1614+ "atty",
1615+ "chrono",
1616+ "log",
1617+ "termcolor",
1618+ "thread_local",
1619+ ]
1620+
1621+ [[package]]
1622+ name = "strsim"
1623+ version = "0.8.0"
1624+ source = "registry+https://github.com/rust-lang/crates.io-index"
1625+ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
1626+
1627+ [[package]]
1628+ name = "structopt"
1629+ version = "0.3.26"
1630+ source = "registry+https://github.com/rust-lang/crates.io-index"
1631+ checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
1632+ dependencies = [
1633+ "clap",
1634+ "lazy_static",
1635+ "structopt-derive",
1636+ ]
1637+
1638+ [[package]]
1639+ name = "structopt-derive"
1640+ version = "0.4.18"
1641+ source = "registry+https://github.com/rust-lang/crates.io-index"
1642+ checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
1643+ dependencies = [
1644+ "heck",
1645+ "proc-macro-error",
1646+ "proc-macro2",
1647+ "quote",
1648+ "syn",
1649+ ]
1650+
1651+ [[package]]
1652+ name = "syn"
1653+ version = "1.0.92"
1654+ source = "registry+https://github.com/rust-lang/crates.io-index"
1655+ checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"
1656+ dependencies = [
1657+ "proc-macro2",
1658+ "quote",
1659+ "unicode-xid",
1660+ ]
1661+
1662+ [[package]]
1663+ name = "tempfile"
1664+ version = "3.3.0"
1665+ source = "registry+https://github.com/rust-lang/crates.io-index"
1666+ checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1667+ dependencies = [
1668+ "cfg-if",
1669+ "fastrand",
1670+ "libc",
1671+ "redox_syscall",
1672+ "remove_dir_all",
1673+ "winapi",
1674+ ]
1675+
1676+ [[package]]
1677+ name = "termcolor"
1678+ version = "1.1.3"
1679+ source = "registry+https://github.com/rust-lang/crates.io-index"
1680+ checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
1681+ dependencies = [
1682+ "winapi-util",
1683+ ]
1684+
1685+ [[package]]
1686+ name = "textwrap"
1687+ version = "0.11.0"
1688+ source = "registry+https://github.com/rust-lang/crates.io-index"
1689+ checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
1690+ dependencies = [
1691+ "unicode-width",
1692+ ]
1693+
1694+ [[package]]
1695+ name = "thiserror"
1696+ version = "1.0.31"
1697+ source = "registry+https://github.com/rust-lang/crates.io-index"
1698+ checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
1699+ dependencies = [
1700+ "thiserror-impl",
1701+ ]
1702+
1703+ [[package]]
1704+ name = "thiserror-impl"
1705+ version = "1.0.31"
1706+ source = "registry+https://github.com/rust-lang/crates.io-index"
1707+ checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
1708+ dependencies = [
1709+ "proc-macro2",
1710+ "quote",
1711+ "syn",
1712+ ]
1713+
1714+ [[package]]
1715+ name = "thread_local"
1716+ version = "1.0.1"
1717+ source = "registry+https://github.com/rust-lang/crates.io-index"
1718+ checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
1719+ dependencies = [
1720+ "lazy_static",
1721+ ]
1722+
1723+ [[package]]
1724+ name = "time"
1725+ version = "0.1.43"
1726+ source = "registry+https://github.com/rust-lang/crates.io-index"
1727+ checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
1728+ dependencies = [
1729+ "libc",
1730+ "winapi",
1731+ ]
1732+
1733+ [[package]]
1734+ name = "tinyvec"
1735+ version = "1.6.0"
1736+ source = "registry+https://github.com/rust-lang/crates.io-index"
1737+ checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
1738+ dependencies = [
1739+ "tinyvec_macros",
1740+ ]
1741+
1742+ [[package]]
1743+ name = "tinyvec_macros"
1744+ version = "0.1.0"
1745+ source = "registry+https://github.com/rust-lang/crates.io-index"
1746+ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
1747+
1748+ [[package]]
1749+ name = "tokio"
1750+ version = "1.18.1"
1751+ source = "registry+https://github.com/rust-lang/crates.io-index"
1752+ checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc"
1753+ dependencies = [
1754+ "bytes",
1755+ "libc",
1756+ "memchr",
1757+ "mio",
1758+ "num_cpus",
1759+ "once_cell",
1760+ "parking_lot",
1761+ "pin-project-lite",
1762+ "signal-hook-registry",
1763+ "socket2",
1764+ "tokio-macros",
1765+ "winapi",
1766+ ]
1767+
1768+ [[package]]
1769+ name = "tokio-macros"
1770+ version = "1.7.0"
1771+ source = "registry+https://github.com/rust-lang/crates.io-index"
1772+ checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
1773+ dependencies = [
1774+ "proc-macro2",
1775+ "quote",
1776+ "syn",
1777+ ]
1778+
1779+ [[package]]
1780+ name = "tokio-stream"
1781+ version = "0.1.8"
1782+ source = "registry+https://github.com/rust-lang/crates.io-index"
1783+ checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
1784+ dependencies = [
1785+ "futures-core",
1786+ "pin-project-lite",
1787+ "tokio",
1788+ ]
1789+
1790+ [[package]]
1791+ name = "tokio-tungstenite"
1792+ version = "0.15.0"
1793+ source = "registry+https://github.com/rust-lang/crates.io-index"
1794+ checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8"
1795+ dependencies = [
1796+ "futures-util",
1797+ "log",
1798+ "pin-project",
1799+ "tokio",
1800+ "tungstenite",
1801+ ]
1802+
1803+ [[package]]
1804+ name = "tokio-util"
1805+ version = "0.6.9"
1806+ source = "registry+https://github.com/rust-lang/crates.io-index"
1807+ checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
1808+ dependencies = [
1809+ "bytes",
1810+ "futures-core",
1811+ "futures-sink",
1812+ "log",
1813+ "pin-project-lite",
1814+ "tokio",
1815+ ]
1816+
1817+ [[package]]
1818+ name = "tokio-util"
1819+ version = "0.7.1"
1820+ source = "registry+https://github.com/rust-lang/crates.io-index"
1821+ checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764"
1822+ dependencies = [
1823+ "bytes",
1824+ "futures-core",
1825+ "futures-sink",
1826+ "pin-project-lite",
1827+ "tokio",
1828+ "tracing",
1829+ ]
1830+
1831+ [[package]]
1832+ name = "toml"
1833+ version = "0.5.9"
1834+ source = "registry+https://github.com/rust-lang/crates.io-index"
1835+ checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
1836+ dependencies = [
1837+ "serde",
1838+ ]
1839+
1840+ [[package]]
1841+ name = "tower-service"
1842+ version = "0.3.1"
1843+ source = "registry+https://github.com/rust-lang/crates.io-index"
1844+ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
1845+
1846+ [[package]]
1847+ name = "tracing"
1848+ version = "0.1.34"
1849+ source = "registry+https://github.com/rust-lang/crates.io-index"
1850+ checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
1851+ dependencies = [
1852+ "cfg-if",
1853+ "log",
1854+ "pin-project-lite",
1855+ "tracing-attributes",
1856+ "tracing-core",
1857+ ]
1858+
1859+ [[package]]
1860+ name = "tracing-attributes"
1861+ version = "0.1.21"
1862+ source = "registry+https://github.com/rust-lang/crates.io-index"
1863+ checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
1864+ dependencies = [
1865+ "proc-macro2",
1866+ "quote",
1867+ "syn",
1868+ ]
1869+
1870+ [[package]]
1871+ name = "tracing-core"
1872+ version = "0.1.26"
1873+ source = "registry+https://github.com/rust-lang/crates.io-index"
1874+ checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
1875+ dependencies = [
1876+ "lazy_static",
1877+ ]
1878+
1879+ [[package]]
1880+ name = "try-lock"
1881+ version = "0.2.3"
1882+ source = "registry+https://github.com/rust-lang/crates.io-index"
1883+ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
1884+
1885+ [[package]]
1886+ name = "tungstenite"
1887+ version = "0.14.0"
1888+ source = "registry+https://github.com/rust-lang/crates.io-index"
1889+ checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
1890+ dependencies = [
1891+ "base64",
1892+ "byteorder",
1893+ "bytes",
1894+ "http",
1895+ "httparse",
1896+ "log",
1897+ "rand",
1898+ "sha-1 0.9.8",
1899+ "thiserror",
1900+ "url",
1901+ "utf-8",
1902+ ]
1903+
1904+ [[package]]
1905+ name = "twoway"
1906+ version = "0.1.8"
1907+ source = "registry+https://github.com/rust-lang/crates.io-index"
1908+ checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
1909+ dependencies = [
1910+ "memchr",
1911+ ]
1912+
1913+ [[package]]
1914+ name = "typenum"
1915+ version = "1.15.0"
1916+ source = "registry+https://github.com/rust-lang/crates.io-index"
1917+ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
1918+
1919+ [[package]]
1920+ name = "unicase"
1921+ version = "2.6.0"
1922+ source = "registry+https://github.com/rust-lang/crates.io-index"
1923+ checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
1924+ dependencies = [
1925+ "version_check",
1926+ ]
1927+
1928+ [[package]]
1929+ name = "unicode-bidi"
1930+ version = "0.3.8"
1931+ source = "registry+https://github.com/rust-lang/crates.io-index"
1932+ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
1933+
1934+ [[package]]
1935+ name = "unicode-normalization"
1936+ version = "0.1.19"
1937+ source = "registry+https://github.com/rust-lang/crates.io-index"
1938+ checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
1939+ dependencies = [
1940+ "tinyvec",
1941+ ]
1942+
1943+ [[package]]
1944+ name = "unicode-segmentation"
1945+ version = "1.9.0"
1946+ source = "registry+https://github.com/rust-lang/crates.io-index"
1947+ checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
1948+
1949+ [[package]]
1950+ name = "unicode-width"
1951+ version = "0.1.9"
1952+ source = "registry+https://github.com/rust-lang/crates.io-index"
1953+ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
1954+
1955+ [[package]]
1956+ name = "unicode-xid"
1957+ version = "0.2.3"
1958+ source = "registry+https://github.com/rust-lang/crates.io-index"
1959+ checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
1960+
1961+ [[package]]
1962+ name = "url"
1963+ version = "2.2.2"
1964+ source = "registry+https://github.com/rust-lang/crates.io-index"
1965+ checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
1966+ dependencies = [
1967+ "form_urlencoded",
1968+ "idna",
1969+ "matches",
1970+ "percent-encoding",
1971+ ]
1972+
1973+ [[package]]
1974+ name = "utf-8"
1975+ version = "0.7.6"
1976+ source = "registry+https://github.com/rust-lang/crates.io-index"
1977+ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
1978+
1979+ [[package]]
1980+ name = "uuid"
1981+ version = "1.0.0"
1982+ source = "registry+https://github.com/rust-lang/crates.io-index"
1983+ checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0"
1984+ dependencies = [
1985+ "getrandom",
1986+ "serde",
1987+ "sha1_smol",
1988+ ]
1989+
1990+ [[package]]
1991+ name = "vcpkg"
1992+ version = "0.2.15"
1993+ source = "registry+https://github.com/rust-lang/crates.io-index"
1994+ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1995+
1996+ [[package]]
1997+ name = "vec_map"
1998+ version = "0.8.2"
1999+ source = "registry+https://github.com/rust-lang/crates.io-index"
2000+ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
2001+
2002+ [[package]]
2003+ name = "version_check"
2004+ version = "0.9.4"
2005+ source = "registry+https://github.com/rust-lang/crates.io-index"
2006+ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
2007+
2008+ [[package]]
2009+ name = "waker-fn"
2010+ version = "1.1.0"
2011+ source = "registry+https://github.com/rust-lang/crates.io-index"
2012+ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
2013+
2014+ [[package]]
2015+ name = "want"
2016+ version = "0.3.0"
2017+ source = "registry+https://github.com/rust-lang/crates.io-index"
2018+ checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
2019+ dependencies = [
2020+ "log",
2021+ "try-lock",
2022+ ]
2023+
2024+ [[package]]
2025+ name = "warp"
2026+ version = "0.3.2"
2027+ source = "registry+https://github.com/rust-lang/crates.io-index"
2028+ checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e"
2029+ dependencies = [
2030+ "bytes",
2031+ "futures-channel",
2032+ "futures-util",
2033+ "headers",
2034+ "http",
2035+ "hyper",
2036+ "log",
2037+ "mime",
2038+ "mime_guess",
2039+ "multipart",
2040+ "percent-encoding",
2041+ "pin-project",
2042+ "scoped-tls",
2043+ "serde",
2044+ "serde_json",
2045+ "serde_urlencoded",
2046+ "tokio",
2047+ "tokio-stream",
2048+ "tokio-tungstenite",
2049+ "tokio-util 0.6.9",
2050+ "tower-service",
2051+ "tracing",
2052+ ]
2053+
2054+ [[package]]
2055+ name = "wasi"
2056+ version = "0.10.2+wasi-snapshot-preview1"
2057+ source = "registry+https://github.com/rust-lang/crates.io-index"
2058+ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
2059+
2060+ [[package]]
2061+ name = "wasi"
2062+ version = "0.11.0+wasi-snapshot-preview1"
2063+ source = "registry+https://github.com/rust-lang/crates.io-index"
2064+ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
2065+
2066+ [[package]]
2067+ name = "wepoll-ffi"
2068+ version = "0.1.2"
2069+ source = "registry+https://github.com/rust-lang/crates.io-index"
2070+ checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
2071+ dependencies = [
2072+ "cc",
2073+ ]
2074+
2075+ [[package]]
2076+ name = "winapi"
2077+ version = "0.3.9"
2078+ source = "registry+https://github.com/rust-lang/crates.io-index"
2079+ checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
2080+ dependencies = [
2081+ "winapi-i686-pc-windows-gnu",
2082+ "winapi-x86_64-pc-windows-gnu",
2083+ ]
2084+
2085+ [[package]]
2086+ name = "winapi-i686-pc-windows-gnu"
2087+ version = "0.4.0"
2088+ source = "registry+https://github.com/rust-lang/crates.io-index"
2089+ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
2090+
2091+ [[package]]
2092+ name = "winapi-util"
2093+ version = "0.1.5"
2094+ source = "registry+https://github.com/rust-lang/crates.io-index"
2095+ checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
2096+ dependencies = [
2097+ "winapi",
2098+ ]
2099+
2100+ [[package]]
2101+ name = "winapi-x86_64-pc-windows-gnu"
2102+ version = "0.4.0"
2103+ source = "registry+https://github.com/rust-lang/crates.io-index"
2104+ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2105+
2106+ [[package]]
2107+ name = "windows-sys"
2108+ version = "0.36.1"
2109+ source = "registry+https://github.com/rust-lang/crates.io-index"
2110+ checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
2111+ dependencies = [
2112+ "windows_aarch64_msvc",
2113+ "windows_i686_gnu",
2114+ "windows_i686_msvc",
2115+ "windows_x86_64_gnu",
2116+ "windows_x86_64_msvc",
2117+ ]
2118+
2119+ [[package]]
2120+ name = "windows_aarch64_msvc"
2121+ version = "0.36.1"
2122+ source = "registry+https://github.com/rust-lang/crates.io-index"
2123+ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
2124+
2125+ [[package]]
2126+ name = "windows_i686_gnu"
2127+ version = "0.36.1"
2128+ source = "registry+https://github.com/rust-lang/crates.io-index"
2129+ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
2130+
2131+ [[package]]
2132+ name = "windows_i686_msvc"
2133+ version = "0.36.1"
2134+ source = "registry+https://github.com/rust-lang/crates.io-index"
2135+ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
2136+
2137+ [[package]]
2138+ name = "windows_x86_64_gnu"
2139+ version = "0.36.1"
2140+ source = "registry+https://github.com/rust-lang/crates.io-index"
2141+ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
2142+
2143+ [[package]]
2144+ name = "windows_x86_64_msvc"
2145+ version = "0.36.1"
2146+ source = "registry+https://github.com/rust-lang/crates.io-index"
2147+ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
2148+
2149+ [[package]]
2150+ name = "xdg"
2151+ version = "2.4.1"
2152+ source = "registry+https://github.com/rust-lang/crates.io-index"
2153+ checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6"
2154+ dependencies = [
2155+ "dirs",
2156+ ]
2157+
2158+ [[package]]
2159+ name = "xdg-utils"
2160+ version = "0.4.0"
2161+ source = "registry+https://github.com/rust-lang/crates.io-index"
2162+ checksum = "db9fefe62d5969721e2cfc529e6a760901cc0da422b6d67e7bfd18e69490dba6"
2163 diff --git a/Cargo.toml b/Cargo.toml
2164new file mode 100644
2165index 0000000..fd216ce
2166--- /dev/null
2167+++ b/Cargo.toml
2168 @@ -0,0 +1,6 @@
2169+ [workspace]
2170+ members = [
2171+ "core",
2172+ "cli",
2173+ "rest-http",
2174+ ]
2175 diff --git a/LICENSE b/LICENSE
2176new file mode 100644
2177index 0000000..0ad25db
2178--- /dev/null
2179+++ b/LICENSE
2180 @@ -0,0 +1,661 @@
2181+ GNU AFFERO GENERAL PUBLIC LICENSE
2182+ Version 3, 19 November 2007
2183+
2184+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
2185+ Everyone is permitted to copy and distribute verbatim copies
2186+ of this license document, but changing it is not allowed.
2187+
2188+ Preamble
2189+
2190+ The GNU Affero General Public License is a free, copyleft license for
2191+ software and other kinds of works, specifically designed to ensure
2192+ cooperation with the community in the case of network server software.
2193+
2194+ The licenses for most software and other practical works are designed
2195+ to take away your freedom to share and change the works. By contrast,
2196+ our General Public Licenses are intended to guarantee your freedom to
2197+ share and change all versions of a program--to make sure it remains free
2198+ software for all its users.
2199+
2200+ When we speak of free software, we are referring to freedom, not
2201+ price. Our General Public Licenses are designed to make sure that you
2202+ have the freedom to distribute copies of free software (and charge for
2203+ them if you wish), that you receive source code or can get it if you
2204+ want it, that you can change the software or use pieces of it in new
2205+ free programs, and that you know you can do these things.
2206+
2207+ Developers that use our General Public Licenses protect your rights
2208+ with two steps: (1) assert copyright on the software, and (2) offer
2209+ you this License which gives you legal permission to copy, distribute
2210+ and/or modify the software.
2211+
2212+ A secondary benefit of defending all users' freedom is that
2213+ improvements made in alternate versions of the program, if they
2214+ receive widespread use, become available for other developers to
2215+ incorporate. Many developers of free software are heartened and
2216+ encouraged by the resulting cooperation. However, in the case of
2217+ software used on network servers, this result may fail to come about.
2218+ The GNU General Public License permits making a modified version and
2219+ letting the public access it on a server without ever releasing its
2220+ source code to the public.
2221+
2222+ The GNU Affero General Public License is designed specifically to
2223+ ensure that, in such cases, the modified source code becomes available
2224+ to the community. It requires the operator of a network server to
2225+ provide the source code of the modified version running there to the
2226+ users of that server. Therefore, public use of a modified version, on
2227+ a publicly accessible server, gives the public access to the source
2228+ code of the modified version.
2229+
2230+ An older license, called the Affero General Public License and
2231+ published by Affero, was designed to accomplish similar goals. This is
2232+ a different license, not a version of the Affero GPL, but Affero has
2233+ released a new version of the Affero GPL which permits relicensing under
2234+ this license.
2235+
2236+ The precise terms and conditions for copying, distribution and
2237+ modification follow.
2238+
2239+ TERMS AND CONDITIONS
2240+
2241+ 0. Definitions.
2242+
2243+ "This License" refers to version 3 of the GNU Affero General Public License.
2244+
2245+ "Copyright" also means copyright-like laws that apply to other kinds of
2246+ works, such as semiconductor masks.
2247+
2248+ "The Program" refers to any copyrightable work licensed under this
2249+ License. Each licensee is addressed as "you". "Licensees" and
2250+ "recipients" may be individuals or organizations.
2251+
2252+ To "modify" a work means to copy from or adapt all or part of the work
2253+ in a fashion requiring copyright permission, other than the making of an
2254+ exact copy. The resulting work is called a "modified version" of the
2255+ earlier work or a work "based on" the earlier work.
2256+
2257+ A "covered work" means either the unmodified Program or a work based
2258+ on the Program.
2259+
2260+ To "propagate" a work means to do anything with it that, without
2261+ permission, would make you directly or secondarily liable for
2262+ infringement under applicable copyright law, except executing it on a
2263+ computer or modifying a private copy. Propagation includes copying,
2264+ distribution (with or without modification), making available to the
2265+ public, and in some countries other activities as well.
2266+
2267+ To "convey" a work means any kind of propagation that enables other
2268+ parties to make or receive copies. Mere interaction with a user through
2269+ a computer network, with no transfer of a copy, is not conveying.
2270+
2271+ An interactive user interface displays "Appropriate Legal Notices"
2272+ to the extent that it includes a convenient and prominently visible
2273+ feature that (1) displays an appropriate copyright notice, and (2)
2274+ tells the user that there is no warranty for the work (except to the
2275+ extent that warranties are provided), that licensees may convey the
2276+ work under this License, and how to view a copy of this License. If
2277+ the interface presents a list of user commands or options, such as a
2278+ menu, a prominent item in the list meets this criterion.
2279+
2280+ 1. Source Code.
2281+
2282+ The "source code" for a work means the preferred form of the work
2283+ for making modifications to it. "Object code" means any non-source
2284+ form of a work.
2285+
2286+ A "Standard Interface" means an interface that either is an official
2287+ standard defined by a recognized standards body, or, in the case of
2288+ interfaces specified for a particular programming language, one that
2289+ is widely used among developers working in that language.
2290+
2291+ The "System Libraries" of an executable work include anything, other
2292+ than the work as a whole, that (a) is included in the normal form of
2293+ packaging a Major Component, but which is not part of that Major
2294+ Component, and (b) serves only to enable use of the work with that
2295+ Major Component, or to implement a Standard Interface for which an
2296+ implementation is available to the public in source code form. A
2297+ "Major Component", in this context, means a major essential component
2298+ (kernel, window system, and so on) of the specific operating system
2299+ (if any) on which the executable work runs, or a compiler used to
2300+ produce the work, or an object code interpreter used to run it.
2301+
2302+ The "Corresponding Source" for a work in object code form means all
2303+ the source code needed to generate, install, and (for an executable
2304+ work) run the object code and to modify the work, including scripts to
2305+ control those activities. However, it does not include the work's
2306+ System Libraries, or general-purpose tools or generally available free
2307+ programs which are used unmodified in performing those activities but
2308+ which are not part of the work. For example, Corresponding Source
2309+ includes interface definition files associated with source files for
2310+ the work, and the source code for shared libraries and dynamically
2311+ linked subprograms that the work is specifically designed to require,
2312+ such as by intimate data communication or control flow between those
2313+ subprograms and other parts of the work.
2314+
2315+ The Corresponding Source need not include anything that users
2316+ can regenerate automatically from other parts of the Corresponding
2317+ Source.
2318+
2319+ The Corresponding Source for a work in source code form is that
2320+ same work.
2321+
2322+ 2. Basic Permissions.
2323+
2324+ All rights granted under this License are granted for the term of
2325+ copyright on the Program, and are irrevocable provided the stated
2326+ conditions are met. This License explicitly affirms your unlimited
2327+ permission to run the unmodified Program. The output from running a
2328+ covered work is covered by this License only if the output, given its
2329+ content, constitutes a covered work. This License acknowledges your
2330+ rights of fair use or other equivalent, as provided by copyright law.
2331+
2332+ You may make, run and propagate covered works that you do not
2333+ convey, without conditions so long as your license otherwise remains
2334+ in force. You may convey covered works to others for the sole purpose
2335+ of having them make modifications exclusively for you, or provide you
2336+ with facilities for running those works, provided that you comply with
2337+ the terms of this License in conveying all material for which you do
2338+ not control copyright. Those thus making or running the covered works
2339+ for you must do so exclusively on your behalf, under your direction
2340+ and control, on terms that prohibit them from making any copies of
2341+ your copyrighted material outside their relationship with you.
2342+
2343+ Conveying under any other circumstances is permitted solely under
2344+ the conditions stated below. Sublicensing is not allowed; section 10
2345+ makes it unnecessary.
2346+
2347+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
2348+
2349+ No covered work shall be deemed part of an effective technological
2350+ measure under any applicable law fulfilling obligations under article
2351+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
2352+ similar laws prohibiting or restricting circumvention of such
2353+ measures.
2354+
2355+ When you convey a covered work, you waive any legal power to forbid
2356+ circumvention of technological measures to the extent such circumvention
2357+ is effected by exercising rights under this License with respect to
2358+ the covered work, and you disclaim any intention to limit operation or
2359+ modification of the work as a means of enforcing, against the work's
2360+ users, your or third parties' legal rights to forbid circumvention of
2361+ technological measures.
2362+
2363+ 4. Conveying Verbatim Copies.
2364+
2365+ You may convey verbatim copies of the Program's source code as you
2366+ receive it, in any medium, provided that you conspicuously and
2367+ appropriately publish on each copy an appropriate copyright notice;
2368+ keep intact all notices stating that this License and any
2369+ non-permissive terms added in accord with section 7 apply to the code;
2370+ keep intact all notices of the absence of any warranty; and give all
2371+ recipients a copy of this License along with the Program.
2372+
2373+ You may charge any price or no price for each copy that you convey,
2374+ and you may offer support or warranty protection for a fee.
2375+
2376+ 5. Conveying Modified Source Versions.
2377+
2378+ You may convey a work based on the Program, or the modifications to
2379+ produce it from the Program, in the form of source code under the
2380+ terms of section 4, provided that you also meet all of these conditions:
2381+
2382+ a) The work must carry prominent notices stating that you modified
2383+ it, and giving a relevant date.
2384+
2385+ b) The work must carry prominent notices stating that it is
2386+ released under this License and any conditions added under section
2387+ 7. This requirement modifies the requirement in section 4 to
2388+ "keep intact all notices".
2389+
2390+ c) You must license the entire work, as a whole, under this
2391+ License to anyone who comes into possession of a copy. This
2392+ License will therefore apply, along with any applicable section 7
2393+ additional terms, to the whole of the work, and all its parts,
2394+ regardless of how they are packaged. This License gives no
2395+ permission to license the work in any other way, but it does not
2396+ invalidate such permission if you have separately received it.
2397+
2398+ d) If the work has interactive user interfaces, each must display
2399+ Appropriate Legal Notices; however, if the Program has interactive
2400+ interfaces that do not display Appropriate Legal Notices, your
2401+ work need not make them do so.
2402+
2403+ A compilation of a covered work with other separate and independent
2404+ works, which are not by their nature extensions of the covered work,
2405+ and which are not combined with it such as to form a larger program,
2406+ in or on a volume of a storage or distribution medium, is called an
2407+ "aggregate" if the compilation and its resulting copyright are not
2408+ used to limit the access or legal rights of the compilation's users
2409+ beyond what the individual works permit. Inclusion of a covered work
2410+ in an aggregate does not cause this License to apply to the other
2411+ parts of the aggregate.
2412+
2413+ 6. Conveying Non-Source Forms.
2414+
2415+ You may convey a covered work in object code form under the terms
2416+ of sections 4 and 5, provided that you also convey the
2417+ machine-readable Corresponding Source under the terms of this License,
2418+ in one of these ways:
2419+
2420+ a) Convey the object code in, or embodied in, a physical product
2421+ (including a physical distribution medium), accompanied by the
2422+ Corresponding Source fixed on a durable physical medium
2423+ customarily used for software interchange.
2424+
2425+ b) Convey the object code in, or embodied in, a physical product
2426+ (including a physical distribution medium), accompanied by a
2427+ written offer, valid for at least three years and valid for as
2428+ long as you offer spare parts or customer support for that product
2429+ model, to give anyone who possesses the object code either (1) a
2430+ copy of the Corresponding Source for all the software in the
2431+ product that is covered by this License, on a durable physical
2432+ medium customarily used for software interchange, for a price no
2433+ more than your reasonable cost of physically performing this
2434+ conveying of source, or (2) access to copy the
2435+ Corresponding Source from a network server at no charge.
2436+
2437+ c) Convey individual copies of the object code with a copy of the
2438+ written offer to provide the Corresponding Source. This
2439+ alternative is allowed only occasionally and noncommercially, and
2440+ only if you received the object code with such an offer, in accord
2441+ with subsection 6b.
2442+
2443+ d) Convey the object code by offering access from a designated
2444+ place (gratis or for a charge), and offer equivalent access to the
2445+ Corresponding Source in the same way through the same place at no
2446+ further charge. You need not require recipients to copy the
2447+ Corresponding Source along with the object code. If the place to
2448+ copy the object code is a network server, the Corresponding Source
2449+ may be on a different server (operated by you or a third party)
2450+ that supports equivalent copying facilities, provided you maintain
2451+ clear directions next to the object code saying where to find the
2452+ Corresponding Source. Regardless of what server hosts the
2453+ Corresponding Source, you remain obligated to ensure that it is
2454+ available for as long as needed to satisfy these requirements.
2455+
2456+ e) Convey the object code using peer-to-peer transmission, provided
2457+ you inform other peers where the object code and Corresponding
2458+ Source of the work are being offered to the general public at no
2459+ charge under subsection 6d.
2460+
2461+ A separable portion of the object code, whose source code is excluded
2462+ from the Corresponding Source as a System Library, need not be
2463+ included in conveying the object code work.
2464+
2465+ A "User Product" is either (1) a "consumer product", which means any
2466+ tangible personal property which is normally used for personal, family,
2467+ or household purposes, or (2) anything designed or sold for incorporation
2468+ into a dwelling. In determining whether a product is a consumer product,
2469+ doubtful cases shall be resolved in favor of coverage. For a particular
2470+ product received by a particular user, "normally used" refers to a
2471+ typical or common use of that class of product, regardless of the status
2472+ of the particular user or of the way in which the particular user
2473+ actually uses, or expects or is expected to use, the product. A product
2474+ is a consumer product regardless of whether the product has substantial
2475+ commercial, industrial or non-consumer uses, unless such uses represent
2476+ the only significant mode of use of the product.
2477+
2478+ "Installation Information" for a User Product means any methods,
2479+ procedures, authorization keys, or other information required to install
2480+ and execute modified versions of a covered work in that User Product from
2481+ a modified version of its Corresponding Source. The information must
2482+ suffice to ensure that the continued functioning of the modified object
2483+ code is in no case prevented or interfered with solely because
2484+ modification has been made.
2485+
2486+ If you convey an object code work under this section in, or with, or
2487+ specifically for use in, a User Product, and the conveying occurs as
2488+ part of a transaction in which the right of possession and use of the
2489+ User Product is transferred to the recipient in perpetuity or for a
2490+ fixed term (regardless of how the transaction is characterized), the
2491+ Corresponding Source conveyed under this section must be accompanied
2492+ by the Installation Information. But this requirement does not apply
2493+ if neither you nor any third party retains the ability to install
2494+ modified object code on the User Product (for example, the work has
2495+ been installed in ROM).
2496+
2497+ The requirement to provide Installation Information does not include a
2498+ requirement to continue to provide support service, warranty, or updates
2499+ for a work that has been modified or installed by the recipient, or for
2500+ the User Product in which it has been modified or installed. Access to a
2501+ network may be denied when the modification itself materially and
2502+ adversely affects the operation of the network or violates the rules and
2503+ protocols for communication across the network.
2504+
2505+ Corresponding Source conveyed, and Installation Information provided,
2506+ in accord with this section must be in a format that is publicly
2507+ documented (and with an implementation available to the public in
2508+ source code form), and must require no special password or key for
2509+ unpacking, reading or copying.
2510+
2511+ 7. Additional Terms.
2512+
2513+ "Additional permissions" are terms that supplement the terms of this
2514+ License by making exceptions from one or more of its conditions.
2515+ Additional permissions that are applicable to the entire Program shall
2516+ be treated as though they were included in this License, to the extent
2517+ that they are valid under applicable law. If additional permissions
2518+ apply only to part of the Program, that part may be used separately
2519+ under those permissions, but the entire Program remains governed by
2520+ this License without regard to the additional permissions.
2521+
2522+ When you convey a copy of a covered work, you may at your option
2523+ remove any additional permissions from that copy, or from any part of
2524+ it. (Additional permissions may be written to require their own
2525+ removal in certain cases when you modify the work.) You may place
2526+ additional permissions on material, added by you to a covered work,
2527+ for which you have or can give appropriate copyright permission.
2528+
2529+ Notwithstanding any other provision of this License, for material you
2530+ add to a covered work, you may (if authorized by the copyright holders of
2531+ that material) supplement the terms of this License with terms:
2532+
2533+ a) Disclaiming warranty or limiting liability differently from the
2534+ terms of sections 15 and 16 of this License; or
2535+
2536+ b) Requiring preservation of specified reasonable legal notices or
2537+ author attributions in that material or in the Appropriate Legal
2538+ Notices displayed by works containing it; or
2539+
2540+ c) Prohibiting misrepresentation of the origin of that material, or
2541+ requiring that modified versions of such material be marked in
2542+ reasonable ways as different from the original version; or
2543+
2544+ d) Limiting the use for publicity purposes of names of licensors or
2545+ authors of the material; or
2546+
2547+ e) Declining to grant rights under trademark law for use of some
2548+ trade names, trademarks, or service marks; or
2549+
2550+ f) Requiring indemnification of licensors and authors of that
2551+ material by anyone who conveys the material (or modified versions of
2552+ it) with contractual assumptions of liability to the recipient, for
2553+ any liability that these contractual assumptions directly impose on
2554+ those licensors and authors.
2555+
2556+ All other non-permissive additional terms are considered "further
2557+ restrictions" within the meaning of section 10. If the Program as you
2558+ received it, or any part of it, contains a notice stating that it is
2559+ governed by this License along with a term that is a further
2560+ restriction, you may remove that term. If a license document contains
2561+ a further restriction but permits relicensing or conveying under this
2562+ License, you may add to a covered work material governed by the terms
2563+ of that license document, provided that the further restriction does
2564+ not survive such relicensing or conveying.
2565+
2566+ If you add terms to a covered work in accord with this section, you
2567+ must place, in the relevant source files, a statement of the
2568+ additional terms that apply to those files, or a notice indicating
2569+ where to find the applicable terms.
2570+
2571+ Additional terms, permissive or non-permissive, may be stated in the
2572+ form of a separately written license, or stated as exceptions;
2573+ the above requirements apply either way.
2574+
2575+ 8. Termination.
2576+
2577+ You may not propagate or modify a covered work except as expressly
2578+ provided under this License. Any attempt otherwise to propagate or
2579+ modify it is void, and will automatically terminate your rights under
2580+ this License (including any patent licenses granted under the third
2581+ paragraph of section 11).
2582+
2583+ However, if you cease all violation of this License, then your
2584+ license from a particular copyright holder is reinstated (a)
2585+ provisionally, unless and until the copyright holder explicitly and
2586+ finally terminates your license, and (b) permanently, if the copyright
2587+ holder fails to notify you of the violation by some reasonable means
2588+ prior to 60 days after the cessation.
2589+
2590+ Moreover, your license from a particular copyright holder is
2591+ reinstated permanently if the copyright holder notifies you of the
2592+ violation by some reasonable means, this is the first time you have
2593+ received notice of violation of this License (for any work) from that
2594+ copyright holder, and you cure the violation prior to 30 days after
2595+ your receipt of the notice.
2596+
2597+ Termination of your rights under this section does not terminate the
2598+ licenses of parties who have received copies or rights from you under
2599+ this License. If your rights have been terminated and not permanently
2600+ reinstated, you do not qualify to receive new licenses for the same
2601+ material under section 10.
2602+
2603+ 9. Acceptance Not Required for Having Copies.
2604+
2605+ You are not required to accept this License in order to receive or
2606+ run a copy of the Program. Ancillary propagation of a covered work
2607+ occurring solely as a consequence of using peer-to-peer transmission
2608+ to receive a copy likewise does not require acceptance. However,
2609+ nothing other than this License grants you permission to propagate or
2610+ modify any covered work. These actions infringe copyright if you do
2611+ not accept this License. Therefore, by modifying or propagating a
2612+ covered work, you indicate your acceptance of this License to do so.
2613+
2614+ 10. Automatic Licensing of Downstream Recipients.
2615+
2616+ Each time you convey a covered work, the recipient automatically
2617+ receives a license from the original licensors, to run, modify and
2618+ propagate that work, subject to this License. You are not responsible
2619+ for enforcing compliance by third parties with this License.
2620+
2621+ An "entity transaction" is a transaction transferring control of an
2622+ organization, or substantially all assets of one, or subdividing an
2623+ organization, or merging organizations. If propagation of a covered
2624+ work results from an entity transaction, each party to that
2625+ transaction who receives a copy of the work also receives whatever
2626+ licenses to the work the party's predecessor in interest had or could
2627+ give under the previous paragraph, plus a right to possession of the
2628+ Corresponding Source of the work from the predecessor in interest, if
2629+ the predecessor has it or can get it with reasonable efforts.
2630+
2631+ You may not impose any further restrictions on the exercise of the
2632+ rights granted or affirmed under this License. For example, you may
2633+ not impose a license fee, royalty, or other charge for exercise of
2634+ rights granted under this License, and you may not initiate litigation
2635+ (including a cross-claim or counterclaim in a lawsuit) alleging that
2636+ any patent claim is infringed by making, using, selling, offering for
2637+ sale, or importing the Program or any portion of it.
2638+
2639+ 11. Patents.
2640+
2641+ A "contributor" is a copyright holder who authorizes use under this
2642+ License of the Program or a work on which the Program is based. The
2643+ work thus licensed is called the contributor's "contributor version".
2644+
2645+ A contributor's "essential patent claims" are all patent claims
2646+ owned or controlled by the contributor, whether already acquired or
2647+ hereafter acquired, that would be infringed by some manner, permitted
2648+ by this License, of making, using, or selling its contributor version,
2649+ but do not include claims that would be infringed only as a
2650+ consequence of further modification of the contributor version. For
2651+ purposes of this definition, "control" includes the right to grant
2652+ patent sublicenses in a manner consistent with the requirements of
2653+ this License.
2654+
2655+ Each contributor grants you a non-exclusive, worldwide, royalty-free
2656+ patent license under the contributor's essential patent claims, to
2657+ make, use, sell, offer for sale, import and otherwise run, modify and
2658+ propagate the contents of its contributor version.
2659+
2660+ In the following three paragraphs, a "patent license" is any express
2661+ agreement or commitment, however denominated, not to enforce a patent
2662+ (such as an express permission to practice a patent or covenant not to
2663+ sue for patent infringement). To "grant" such a patent license to a
2664+ party means to make such an agreement or commitment not to enforce a
2665+ patent against the party.
2666+
2667+ If you convey a covered work, knowingly relying on a patent license,
2668+ and the Corresponding Source of the work is not available for anyone
2669+ to copy, free of charge and under the terms of this License, through a
2670+ publicly available network server or other readily accessible means,
2671+ then you must either (1) cause the Corresponding Source to be so
2672+ available, or (2) arrange to deprive yourself of the benefit of the
2673+ patent license for this particular work, or (3) arrange, in a manner
2674+ consistent with the requirements of this License, to extend the patent
2675+ license to downstream recipients. "Knowingly relying" means you have
2676+ actual knowledge that, but for the patent license, your conveying the
2677+ covered work in a country, or your recipient's use of the covered work
2678+ in a country, would infringe one or more identifiable patents in that
2679+ country that you have reason to believe are valid.
2680+
2681+ If, pursuant to or in connection with a single transaction or
2682+ arrangement, you convey, or propagate by procuring conveyance of, a
2683+ covered work, and grant a patent license to some of the parties
2684+ receiving the covered work authorizing them to use, propagate, modify
2685+ or convey a specific copy of the covered work, then the patent license
2686+ you grant is automatically extended to all recipients of the covered
2687+ work and works based on it.
2688+
2689+ A patent license is "discriminatory" if it does not include within
2690+ the scope of its coverage, prohibits the exercise of, or is
2691+ conditioned on the non-exercise of one or more of the rights that are
2692+ specifically granted under this License. You may not convey a covered
2693+ work if you are a party to an arrangement with a third party that is
2694+ in the business of distributing software, under which you make payment
2695+ to the third party based on the extent of your activity of conveying
2696+ the work, and under which the third party grants, to any of the
2697+ parties who would receive the covered work from you, a discriminatory
2698+ patent license (a) in connection with copies of the covered work
2699+ conveyed by you (or copies made from those copies), or (b) primarily
2700+ for and in connection with specific products or compilations that
2701+ contain the covered work, unless you entered into that arrangement,
2702+ or that patent license was granted, prior to 28 March 2007.
2703+
2704+ Nothing in this License shall be construed as excluding or limiting
2705+ any implied license or other defenses to infringement that may
2706+ otherwise be available to you under applicable patent law.
2707+
2708+ 12. No Surrender of Others' Freedom.
2709+
2710+ If conditions are imposed on you (whether by court order, agreement or
2711+ otherwise) that contradict the conditions of this License, they do not
2712+ excuse you from the conditions of this License. If you cannot convey a
2713+ covered work so as to satisfy simultaneously your obligations under this
2714+ License and any other pertinent obligations, then as a consequence you may
2715+ not convey it at all. For example, if you agree to terms that obligate you
2716+ to collect a royalty for further conveying from those to whom you convey
2717+ the Program, the only way you could satisfy both those terms and this
2718+ License would be to refrain entirely from conveying the Program.
2719+
2720+ 13. Remote Network Interaction; Use with the GNU General Public License.
2721+
2722+ Notwithstanding any other provision of this License, if you modify the
2723+ Program, your modified version must prominently offer all users
2724+ interacting with it remotely through a computer network (if your version
2725+ supports such interaction) an opportunity to receive the Corresponding
2726+ Source of your version by providing access to the Corresponding Source
2727+ from a network server at no charge, through some standard or customary
2728+ means of facilitating copying of software. This Corresponding Source
2729+ shall include the Corresponding Source for any work covered by version 3
2730+ of the GNU General Public License that is incorporated pursuant to the
2731+ following paragraph.
2732+
2733+ Notwithstanding any other provision of this License, you have
2734+ permission to link or combine any covered work with a work licensed
2735+ under version 3 of the GNU General Public License into a single
2736+ combined work, and to convey the resulting work. The terms of this
2737+ License will continue to apply to the part which is the covered work,
2738+ but the work with which it is combined will remain governed by version
2739+ 3 of the GNU General Public License.
2740+
2741+ 14. Revised Versions of this License.
2742+
2743+ The Free Software Foundation may publish revised and/or new versions of
2744+ the GNU Affero General Public License from time to time. Such new versions
2745+ will be similar in spirit to the present version, but may differ in detail to
2746+ address new problems or concerns.
2747+
2748+ Each version is given a distinguishing version number. If the
2749+ Program specifies that a certain numbered version of the GNU Affero General
2750+ Public License "or any later version" applies to it, you have the
2751+ option of following the terms and conditions either of that numbered
2752+ version or of any later version published by the Free Software
2753+ Foundation. If the Program does not specify a version number of the
2754+ GNU Affero General Public License, you may choose any version ever published
2755+ by the Free Software Foundation.
2756+
2757+ If the Program specifies that a proxy can decide which future
2758+ versions of the GNU Affero General Public License can be used, that proxy's
2759+ public statement of acceptance of a version permanently authorizes you
2760+ to choose that version for the Program.
2761+
2762+ Later license versions may give you additional or different
2763+ permissions. However, no additional obligations are imposed on any
2764+ author or copyright holder as a result of your choosing to follow a
2765+ later version.
2766+
2767+ 15. Disclaimer of Warranty.
2768+
2769+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
2770+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
2771+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
2772+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
2773+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2774+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
2775+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
2776+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
2777+
2778+ 16. Limitation of Liability.
2779+
2780+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
2781+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
2782+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
2783+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
2784+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
2785+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
2786+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
2787+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
2788+ SUCH DAMAGES.
2789+
2790+ 17. Interpretation of Sections 15 and 16.
2791+
2792+ If the disclaimer of warranty and limitation of liability provided
2793+ above cannot be given local legal effect according to their terms,
2794+ reviewing courts shall apply local law that most closely approximates
2795+ an absolute waiver of all civil liability in connection with the
2796+ Program, unless a warranty or assumption of liability accompanies a
2797+ copy of the Program in return for a fee.
2798+
2799+ END OF TERMS AND CONDITIONS
2800+
2801+ How to Apply These Terms to Your New Programs
2802+
2803+ If you develop a new program, and you want it to be of the greatest
2804+ possible use to the public, the best way to achieve this is to make it
2805+ free software which everyone can redistribute and change under these terms.
2806+
2807+ To do so, attach the following notices to the program. It is safest
2808+ to attach them to the start of each source file to most effectively
2809+ state the exclusion of warranty; and each file should have at least
2810+ the "copyright" line and a pointer to where the full notice is found.
2811+
2812+ <one line to give the program's name and a brief idea of what it does.>
2813+ Copyright (C) <year> <name of author>
2814+
2815+ This program is free software: you can redistribute it and/or modify
2816+ it under the terms of the GNU Affero General Public License as published
2817+ by the Free Software Foundation, either version 3 of the License, or
2818+ (at your option) any later version.
2819+
2820+ This program is distributed in the hope that it will be useful,
2821+ but WITHOUT ANY WARRANTY; without even the implied warranty of
2822+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2823+ GNU Affero General Public License for more details.
2824+
2825+ You should have received a copy of the GNU Affero General Public License
2826+ along with this program. If not, see <https://www.gnu.org/licenses/>.
2827+
2828+ Also add information on how to contact you by electronic and paper mail.
2829+
2830+ If your software can interact with users remotely through a computer
2831+ network, you should also make sure that it provides a way for users to
2832+ get its source. For example, if your program is a web application, its
2833+ interface could display a "Source" link that leads users to an archive
2834+ of the code. There are many ways you could offer source, and different
2835+ solutions will be better for different programs; see section 13 for the
2836+ specific requirements.
2837+
2838+ You should also get your employer (if you work as a programmer) or school,
2839+ if any, to sign a "copyright disclaimer" for the program, if necessary.
2840+ For more information on this, and how to apply and follow the GNU AGPL, see
2841+ <https://www.gnu.org/licenses/>.
2842 diff --git a/README.md b/README.md
2843new file mode 100644
2844index 0000000..38b09c8
2845--- /dev/null
2846+++ b/README.md
2847 @@ -0,0 +1,190 @@
2848+ # Mailpot - WIP mailing list manager
2849+
2850+ Crates:
2851+
2852+ - `core`
2853+ - `cli` a command line tool to manage lists
2854+ - `rest-http` a REST http server to manage lists
2855+
2856+ ## Project goals
2857+
2858+ - easy setup
2859+ - extensible through Rust API as a library
2860+ - extensible through HTTP REST API as an HTTP server, with webhooks
2861+ - basic management through CLI
2862+ - replaceable lightweight web archiver
2863+ - custom storage?
2864+ - useful for both newsletters, discussions
2865+
2866+ ## Initial setup
2867+
2868+ Check where `mpot` expects your database file to be:
2869+
2870+ ```shell
2871+ $ cargo run --bin mpot -- db-location
2872+ Configuration file /home/user/.config/mailpot/config.toml doesn't exist
2873+ ```
2874+
2875+ Uuugh, oops.
2876+
2877+ ```shell
2878+ $ mkdir -p /home/user/.config/mailpot
2879+ $ echo 'send_mail = { "type" = "ShellCommand", "value" = "/usr/bin/false" }' > /home/user/.config/mailpot/config.toml
2880+ $ cargo run --bin mpot -- db-location
2881+ /home/user/.local/share/mailpot/mpot.db
2882+ ```
2883+
2884+ Now you can initialize the database file:
2885+
2886+ ```shell
2887+ $ mkdir -p /home/user/.local/share/mailpot/
2888+ $ sqlite3 /home/user/.local/share/mailpot/mpot.db < ./core/src/schema.sql
2889+ ```
2890+
2891+ ## Examples
2892+
2893+ ```text
2894+ % mpot help
2895+ mailpot 0.1.0
2896+ mini mailing list manager
2897+
2898+ USAGE:
2899+ mpot [FLAGS] [OPTIONS] <SUBCOMMAND>
2900+
2901+ FLAGS:
2902+ -d, --debug Activate debug mode
2903+ -h, --help Prints help information
2904+ -V, --version Prints version information
2905+
2906+ OPTIONS:
2907+ -c, --config <config> Set config file
2908+
2909+ SUBCOMMANDS:
2910+ create-list Create new list
2911+ db-location Prints database filesystem location
2912+ help Prints this message or the help of the given subcommand(s)
2913+ list Mailing list management
2914+ list-lists Lists all registered mailing lists
2915+ post Post message from STDIN to list
2916+ ```
2917+
2918+ ### Receiving mail
2919+
2920+ ```shell
2921+ $ cat list-request.eml | cargo run --bin mpot -- -vvvvvv post --dry-run
2922+ ```
2923+
2924+ <details><summary>output</summary>
2925+
2926+ ```shell
2927+ TRACE - Received envelope to post: Envelope {
2928+ Subject: "unsubscribe",
2929+ Date: "Tue, 04 Aug 2020 14:10:13 +0300",
2930+ From: [
2931+ Address::Mailbox {
2932+ display_name: "Mxxxx Pxxxxxxxxxxxx",
2933+ address_spec: "exxxxx@localhost",
2934+ },
2935+ ],
2936+ To: [
2937+ Address::Mailbox {
2938+ display_name: "",
2939+ address_spec: "test-announce+request@localhost",
2940+ },
2941+ ],
2942+ Message-ID: "<ejduu.fddf8sgen4j7@localhost>",
2943+ In-Reply-To: None,
2944+ References: None,
2945+ Hash: 12581897380059220314,
2946+ }
2947+ 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>
2948+ TRACE - Is post related to list [#1 test] Test list <test@localhost>? false
2949+ ```
2950+ </details>
2951+
2952+ ```shell
2953+ $ cat list-post.eml | cargo run --bin mpot -- -vvvvvv post --dry-run
2954+ ```
2955+
2956+ <details><summary>output</summary>
2957+
2958+ ```shell
2959+ TRACE - Received envelope to post: Envelope {
2960+ Subject: "[test-announce] new test releases",
2961+ Date: "Tue, 04 Aug 2020 14:10:13 +0300",
2962+ From: [
2963+ Address::Mailbox {
2964+ display_name: "Mxxxx Pxxxxxxxxxxxx",
2965+ address_spec: "exxxxx@localhost",
2966+ },
2967+ ],
2968+ To: [
2969+ Address::Mailbox {
2970+ display_name: "",
2971+ address_spec: "test-announce@localhost",
2972+ },
2973+ ],
2974+ Message-ID: "<ejduu.sddf8sgen4j7@localhost>",
2975+ In-Reply-To: None,
2976+ References: None,
2977+ Hash: 10220641455578979007,
2978+ }
2979+ TRACE - Is post related to list [#1 test] Test list <test@localhost>? false
2980+ TRACE - Is post related to list [#2 test-announce] test announcements <test-announce@localhost>? true
2981+ TRACE - Examining list "test announcements" <test-announce@localhost>
2982+ TRACE - List members [
2983+ ListMembership {
2984+ list: 2,
2985+ address: "exxxxx@localhost",
2986+ name: None,
2987+ digest: false,
2988+ hide_address: false,
2989+ receive_duplicates: false,
2990+ receive_own_posts: true,
2991+ receive_confirmation: true,
2992+ enabled: true,
2993+ },
2994+ ]
2995+ TRACE - Running FixCRLF filter
2996+ TRACE - Running PostRightsCheck filter
2997+ TRACE - Running AddListHeaders filter
2998+ TRACE - Running FinalizeRecipients filter
2999+ 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 }
3000+ TRACE - member is submitter
3001+ TRACE - Member gets copy
3002+ TRACE - result Ok(
3003+ Post {
3004+ list: MailingList {
3005+ pk: 2,
3006+ name: "test announcements",
3007+ id: "test-announce",
3008+ address: "test-announce@localhost",
3009+ description: None,
3010+ archive_url: None,
3011+ },
3012+ from: Address::Mailbox {
3013+ display_name: "Mxxxx Pxxxxxxxxxxxx",
3014+ address_spec: "exxxxx@localhost",
3015+ },
3016+ members: 1,
3017+ bytes: 851,
3018+ policy: None,
3019+ to: [
3020+ Address::Mailbox {
3021+ display_name: "",
3022+ address_spec: "test-announce@localhost",
3023+ },
3024+ ],
3025+ action: Accept {
3026+ recipients: [
3027+ Address::Mailbox {
3028+ display_name: "",
3029+ address_spec: "exxxxx@localhost",
3030+ },
3031+ ],
3032+ digests: [],
3033+ },
3034+ },
3035+ )
3036+ ```
3037+ </details>
3038 diff --git a/cli/Cargo.toml b/cli/Cargo.toml
3039new file mode 100644
3040index 0000000..b3743e0
3041--- /dev/null
3042+++ b/cli/Cargo.toml
3043 @@ -0,0 +1,21 @@
3044+ [package]
3045+ name = "mailpot-cli"
3046+ version = "0.1.0"
3047+ authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
3048+ edition = "2018"
3049+ license = "LICENSE"
3050+ readme = "README.md"
3051+ description = "mailing list manager"
3052+ repository = "https://github.com/meli/mailpot"
3053+ keywords = ["mail", "mailing-lists" ]
3054+ categories = ["email"]
3055+ default-run = "mpot"
3056+
3057+ [[bin]]
3058+ name = "mpot"
3059+ path = "src/main.rs"
3060+
3061+ [dependencies]
3062+ mailpot = { version = "0.1.0", path = "../core" }
3063+ structopt = "0.3.16"
3064+ stderrlog = "^0.5"
3065 diff --git a/cli/README.md b/cli/README.md
3066new file mode 100644
3067index 0000000..f5e323d
3068--- /dev/null
3069+++ b/cli/README.md
3070 @@ -0,0 +1,5 @@
3071+ # mailpot-cli
3072+
3073+ ```shell
3074+ cargo run --bin mpot -- help
3075+ ```
3076 diff --git a/cli/src/main.rs b/cli/src/main.rs
3077new file mode 100644
3078index 0000000..f2302f4
3079--- /dev/null
3080+++ b/cli/src/main.rs
3081 @@ -0,0 +1,472 @@
3082+ /*
3083+ * This file is part of mailpot
3084+ *
3085+ * Copyright 2020 - Manos Pitsidianakis
3086+ *
3087+ * This program is free software: you can redistribute it and/or modify
3088+ * it under the terms of the GNU Affero General Public License as
3089+ * published by the Free Software Foundation, either version 3 of the
3090+ * License, or (at your option) any later version.
3091+ *
3092+ * This program is distributed in the hope that it will be useful,
3093+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3094+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3095+ * GNU Affero General Public License for more details.
3096+ *
3097+ * You should have received a copy of the GNU Affero General Public License
3098+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
3099+ */
3100+
3101+ extern crate mailpot;
3102+ extern crate stderrlog;
3103+
3104+ pub use mailpot::config::*;
3105+ pub use mailpot::db::*;
3106+ pub use mailpot::errors::*;
3107+ pub use mailpot::mail::*;
3108+ pub use mailpot::models::changesets::*;
3109+ pub use mailpot::models::*;
3110+ pub use mailpot::*;
3111+ use std::path::PathBuf;
3112+ use structopt::StructOpt;
3113+
3114+ #[derive(Debug, StructOpt)]
3115+ #[structopt(
3116+ name = "mailpot",
3117+ about = "mini mailing list manager",
3118+ author = "Manos Pitsidianakis <epilys@nessuent.xyz>"
3119+ )]
3120+ struct Opt {
3121+ /// Activate debug mode
3122+ #[structopt(short, long)]
3123+ debug: bool,
3124+
3125+ /// Set config file
3126+ #[structopt(short, long, parse(from_os_str))]
3127+ #[allow(dead_code)]
3128+ config: Option<PathBuf>,
3129+ #[structopt(flatten)]
3130+ cmd: Command,
3131+ /// Silence all output
3132+ #[structopt(short = "q", long = "quiet")]
3133+ quiet: bool,
3134+ /// Verbose mode (-v, -vv, -vvv, etc)
3135+ #[structopt(short = "v", long = "verbose", parse(from_occurrences))]
3136+ verbose: usize,
3137+ /// Timestamp (sec, ms, ns, none)
3138+ #[structopt(short = "t", long = "timestamp")]
3139+ ts: Option<stderrlog::Timestamp>,
3140+ }
3141+
3142+ #[derive(Debug, StructOpt)]
3143+ enum Command {
3144+ ///Prints database filesystem location
3145+ DbLocation,
3146+ ///Dumps database data to STDOUT
3147+ DumpDatabase,
3148+ ///Lists all registered mailing lists
3149+ ListLists,
3150+ ///Mailing list management
3151+ List {
3152+ ///Selects mailing list to operate on
3153+ list_id: String,
3154+ #[structopt(subcommand)]
3155+ cmd: ListCommand,
3156+ },
3157+ ///Create new list
3158+ CreateList {
3159+ ///List name
3160+ #[structopt(long)]
3161+ name: String,
3162+ ///List ID
3163+ #[structopt(long)]
3164+ id: String,
3165+ ///List e-mail address
3166+ #[structopt(long)]
3167+ address: String,
3168+ ///List description
3169+ #[structopt(long)]
3170+ description: Option<String>,
3171+ ///List archive URL
3172+ #[structopt(long)]
3173+ archive_url: Option<String>,
3174+ },
3175+ ///Post message from STDIN to list
3176+ Post {
3177+ #[structopt(long)]
3178+ dry_run: bool,
3179+ },
3180+ }
3181+
3182+ #[derive(Debug, StructOpt)]
3183+ enum ListCommand {
3184+ /// List members of list.
3185+ Members,
3186+ /// Add member to list.
3187+ AddMember {
3188+ /// E-mail address
3189+ #[structopt(long)]
3190+ address: String,
3191+ /// Name
3192+ #[structopt(long)]
3193+ name: Option<String>,
3194+ /// Send messages as digest?
3195+ #[structopt(long)]
3196+ digest: bool,
3197+ /// Hide message from list when posting?
3198+ #[structopt(long)]
3199+ hide_address: bool,
3200+ /// Hide message from list when posting?
3201+ #[structopt(long)]
3202+ /// Receive confirmation email when posting?
3203+ receive_confirmation: Option<bool>,
3204+ #[structopt(long)]
3205+ /// Receive posts from list even if address exists in To or Cc header?
3206+ receive_duplicates: Option<bool>,
3207+ #[structopt(long)]
3208+ /// Receive own posts from list?
3209+ receive_own_posts: Option<bool>,
3210+ #[structopt(long)]
3211+ /// Is subscription enabled?
3212+ enabled: Option<bool>,
3213+ },
3214+ /// Remove member from list.
3215+ RemoveMember {
3216+ #[structopt(long)]
3217+ /// E-mail address
3218+ address: String,
3219+ },
3220+ /// Update membership info.
3221+ UpdateMembership {
3222+ address: String,
3223+ name: Option<String>,
3224+ digest: Option<bool>,
3225+ hide_address: Option<bool>,
3226+ receive_duplicates: Option<bool>,
3227+ receive_own_posts: Option<bool>,
3228+ receive_confirmation: Option<bool>,
3229+ enabled: Option<bool>,
3230+ },
3231+ /// Alias for update-membership --enabled true
3232+ EnableMembership { address: String },
3233+ /// Alias for update-membership --enabled false
3234+ DisableMembership { address: String },
3235+ /// Update mailing list details.
3236+ Update {
3237+ name: Option<String>,
3238+ id: Option<String>,
3239+ address: Option<String>,
3240+ description: Option<String>,
3241+ archive_url: Option<String>,
3242+ },
3243+ /// Show mailing list health status.
3244+ Health,
3245+ /// Show mailing list info.
3246+ Info,
3247+ }
3248+
3249+ fn run_app(opt: Opt) -> Result<()> {
3250+ if opt.debug {
3251+ println!("DEBUG: {:?}", &opt);
3252+ }
3253+ Configuration::init()?;
3254+ use Command::*;
3255+ match opt.cmd {
3256+ DbLocation => {
3257+ println!("{}", Database::db_path()?.display());
3258+ }
3259+ DumpDatabase => {
3260+ let db = Database::open_or_create_db()?;
3261+ let lists = db.list_lists()?;
3262+ let mut stdout = std::io::stdout();
3263+ serde_json::to_writer_pretty(&mut stdout, &lists)?;
3264+ for l in &lists {
3265+ serde_json::to_writer_pretty(&mut stdout, &db.list_members(l.pk)?)?;
3266+ }
3267+ }
3268+ ListLists => {
3269+ let db = Database::open_or_create_db()?;
3270+ let lists = db.list_lists()?;
3271+ if lists.is_empty() {
3272+ println!("No lists found.");
3273+ } else {
3274+ for l in lists {
3275+ println!("- {} {:?}", l.id, l);
3276+ let list_owners = db.get_list_owners(l.pk)?;
3277+ if list_owners.is_empty() {
3278+ println!("\tList owners: None");
3279+ } else {
3280+ println!("\tList owners:");
3281+ for o in list_owners {
3282+ println!("\t- {}", o);
3283+ }
3284+ }
3285+ if let Some(s) = db.get_list_policy(l.pk)? {
3286+ println!("\tList policy: {}", s);
3287+ } else {
3288+ println!("\tList policy: None");
3289+ }
3290+ println!();
3291+ }
3292+ }
3293+ }
3294+ List { list_id, cmd } => {
3295+ let db = Database::open_or_create_db()?;
3296+ let mut lists = db.list_lists()?;
3297+ let list = if let Some(pos) = lists.iter().position(|l| l.id == list_id) {
3298+ lists.remove(pos)
3299+ } else {
3300+ return Err(format!("No list with id {} was found", list_id).into());
3301+ };
3302+ use ListCommand::*;
3303+ match cmd {
3304+ Members => {
3305+ let members = db.list_members(list.pk)?;
3306+ if members.is_empty() {
3307+ println!("No members found.");
3308+ } else {
3309+ println!("Members of list {}", list.id);
3310+ for l in members {
3311+ println!("- {}", &l);
3312+ }
3313+ }
3314+ }
3315+ AddMember {
3316+ address,
3317+ name,
3318+ digest,
3319+ hide_address,
3320+ receive_confirmation,
3321+ receive_duplicates,
3322+ receive_own_posts,
3323+ enabled,
3324+ } => {
3325+ db.add_member(
3326+ list.pk,
3327+ ListMembership {
3328+ pk: 0,
3329+ list: list.pk,
3330+ name,
3331+ address,
3332+ digest,
3333+ hide_address,
3334+ receive_confirmation: receive_confirmation.unwrap_or(true),
3335+ receive_duplicates: receive_duplicates.unwrap_or(true),
3336+ receive_own_posts: receive_own_posts.unwrap_or(false),
3337+ enabled: enabled.unwrap_or(true),
3338+ },
3339+ )?;
3340+ }
3341+ RemoveMember { address } => {
3342+ loop {
3343+ println!(
3344+ "Are you sure you want to remove membership of {} from list {}? [Yy/n]",
3345+ address, list
3346+ );
3347+ let mut input = String::new();
3348+ std::io::stdin().read_line(&mut input)?;
3349+ if input.trim() == "Y" || input.trim() == "y" || input.trim() == "" {
3350+ break;
3351+ } else if input.trim() == "n" {
3352+ return Ok(());
3353+ }
3354+ }
3355+
3356+ db.remove_member(list.pk, &address)?;
3357+ }
3358+ Health => {
3359+ println!("{} health:", list);
3360+ let list_owners = db.get_list_owners(list.pk)?;
3361+ let list_policy = db.get_list_policy(list.pk)?;
3362+ if list_owners.is_empty() {
3363+ println!("\tList has no owners: you should add at least one.");
3364+ }
3365+ if list_policy.is_none() {
3366+ } else {
3367+ println!("\tList has no post policy: you should add one.");
3368+ }
3369+ }
3370+ Info => {
3371+ println!("{} info:", list);
3372+ let list_owners = db.get_list_owners(list.pk)?;
3373+ let list_policy = db.get_list_policy(list.pk)?;
3374+ let members = db.list_members(list.pk)?;
3375+ if members.is_empty() {
3376+ println!("No members.");
3377+ } else if members.len() == 1 {
3378+ println!("1 member.");
3379+ } else {
3380+ println!("{} members.", members.len());
3381+ }
3382+ if list_owners.is_empty() {
3383+ println!("List owners: None");
3384+ } else {
3385+ println!("List owners:");
3386+ for o in list_owners {
3387+ println!("\t- {}", o);
3388+ }
3389+ }
3390+ if let Some(s) = list_policy {
3391+ println!("List policy: {}", s);
3392+ } else {
3393+ println!("List policy: None");
3394+ }
3395+ }
3396+ UpdateMembership {
3397+ address,
3398+ name,
3399+ digest,
3400+ hide_address,
3401+ receive_duplicates,
3402+ receive_own_posts,
3403+ receive_confirmation,
3404+ enabled,
3405+ } => {
3406+ let name = if name
3407+ .as_ref()
3408+ .map(|s: &String| s.is_empty())
3409+ .unwrap_or(false)
3410+ {
3411+ None
3412+ } else {
3413+ Some(name)
3414+ };
3415+ let changeset = ListMembershipChangeset {
3416+ list: list.pk,
3417+ address,
3418+ name,
3419+ digest,
3420+ hide_address,
3421+ receive_duplicates,
3422+ receive_own_posts,
3423+ receive_confirmation,
3424+ enabled,
3425+ };
3426+ db.update_member(changeset)?;
3427+ }
3428+ EnableMembership { address } => {
3429+ let changeset = ListMembershipChangeset {
3430+ list: list.pk,
3431+ address,
3432+ name: None,
3433+ digest: None,
3434+ hide_address: None,
3435+ receive_duplicates: None,
3436+ receive_own_posts: None,
3437+ receive_confirmation: None,
3438+ enabled: Some(true),
3439+ };
3440+ db.update_member(changeset)?;
3441+ }
3442+ DisableMembership { address } => {
3443+ let changeset = ListMembershipChangeset {
3444+ list: list.pk,
3445+ address,
3446+ name: None,
3447+ digest: None,
3448+ hide_address: None,
3449+ receive_duplicates: None,
3450+ receive_own_posts: None,
3451+ receive_confirmation: None,
3452+ enabled: Some(false),
3453+ };
3454+ db.update_member(changeset)?;
3455+ }
3456+ Update {
3457+ name,
3458+ id,
3459+ address,
3460+ description,
3461+ archive_url,
3462+ } => {
3463+ let description = if description
3464+ .as_ref()
3465+ .map(|s: &String| s.is_empty())
3466+ .unwrap_or(false)
3467+ {
3468+ None
3469+ } else {
3470+ Some(description)
3471+ };
3472+ let archive_url = if archive_url
3473+ .as_ref()
3474+ .map(|s: &String| s.is_empty())
3475+ .unwrap_or(false)
3476+ {
3477+ None
3478+ } else {
3479+ Some(archive_url)
3480+ };
3481+ let changeset = MailingListChangeset {
3482+ pk: list.pk,
3483+ name,
3484+ id,
3485+ address,
3486+ description,
3487+ archive_url,
3488+ };
3489+ db.update_list(changeset)?;
3490+ }
3491+ }
3492+ }
3493+ CreateList {
3494+ name,
3495+ id,
3496+ address,
3497+ description,
3498+ archive_url,
3499+ } => {
3500+ let db = Database::open_or_create_db()?;
3501+ db.create_list(MailingList {
3502+ pk: 0,
3503+ name,
3504+ id,
3505+ description,
3506+ address,
3507+ archive_url,
3508+ })?;
3509+ }
3510+ Post { dry_run } => {
3511+ if opt.debug {
3512+ println!("post dry_run{:?}", dry_run);
3513+ }
3514+
3515+ use melib::Envelope;
3516+ use std::io::Read;
3517+
3518+ let mut input = String::new();
3519+ std::io::stdin().read_to_string(&mut input)?;
3520+ match Envelope::from_bytes(input.as_bytes(), None) {
3521+ Ok(env) => {
3522+ let db = Database::open_or_create_db()?;
3523+ db.post(env, input.as_bytes(), dry_run)?;
3524+ }
3525+ Err(err) => {
3526+ eprintln!("Could not parse message: {}", err);
3527+ let p = Configuration::save_message(input)?;
3528+ eprintln!("Message saved at {}", p.display());
3529+ return Err(err.into());
3530+ }
3531+ }
3532+ }
3533+ }
3534+
3535+ Ok(())
3536+ }
3537+
3538+ fn main() -> std::result::Result<(), i32> {
3539+ let opt = Opt::from_args();
3540+ stderrlog::new()
3541+ .module(module_path!())
3542+ .module("mailpot")
3543+ .quiet(opt.quiet)
3544+ .verbosity(opt.verbose)
3545+ .timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off))
3546+ .init()
3547+ .unwrap();
3548+ if let Err(err) = run_app(opt) {
3549+ println!("{}", err);
3550+ std::process::exit(-1);
3551+ }
3552+ Ok(())
3553+ }
3554 diff --git a/core/Cargo.toml b/core/Cargo.toml
3555new file mode 100644
3556index 0000000..3669bc2
3557--- /dev/null
3558+++ b/core/Cargo.toml
3559 @@ -0,0 +1,23 @@
3560+ [package]
3561+ name = "mailpot"
3562+ version = "0.1.0"
3563+ authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
3564+ edition = "2018"
3565+ license = "LICENSE"
3566+ readme = "README.md"
3567+ description = "mailing list manager"
3568+ repository = "https://github.com/meli/mailpot"
3569+ keywords = ["mail", "mailing-lists" ]
3570+ categories = ["email"]
3571+
3572+ [dependencies]
3573+ chrono = { version = "^0.4", features = ["serde", ] }
3574+ error-chain = "0.12.4"
3575+ #melib = { version = "*", default-features = false, features = ["smtp", "unicode_algorithms"], path="../../meli/melib", branch = "master" }
3576+ melib = { version = "*", default-features = false, features = ["smtp", "unicode_algorithms"], git="https://github.com/meli/meli", branch = "master" }
3577+ rusqlite = {version = "^0.27" }
3578+ serde = { version = "^1", features = ["derive", ]}
3579+ serde_json = "^1"
3580+ toml = "^0.5"
3581+ log = "0.4"
3582+ xdg = "2.1.0"
3583 diff --git a/core/README.md b/core/README.md
3584new file mode 100644
3585index 0000000..452b1bc
3586--- /dev/null
3587+++ b/core/README.md
3588 @@ -0,0 +1,7 @@
3589+ # mailpot-core
3590+
3591+ Initialize `sqlite3` database
3592+
3593+ ```shell
3594+ sqlite3 mpot.db < ./src/schema.sql
3595+ ```
3596 diff --git a/core/build.rs b/core/build.rs
3597new file mode 100644
3598index 0000000..ce35cba
3599--- /dev/null
3600+++ b/core/build.rs
3601 @@ -0,0 +1,32 @@
3602+ /*
3603+ * This file is part of mailpot
3604+ *
3605+ * Copyright 2020 - Manos Pitsidianakis
3606+ *
3607+ * This program is free software: you can redistribute it and/or modify
3608+ * it under the terms of the GNU Affero General Public License as
3609+ * published by the Free Software Foundation, either version 3 of the
3610+ * License, or (at your option) any later version.
3611+ *
3612+ * This program is distributed in the hope that it will be useful,
3613+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3614+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3615+ * GNU Affero General Public License for more details.
3616+ *
3617+ * You should have received a copy of the GNU Affero General Public License
3618+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
3619+ */
3620+
3621+ use std::io::Write;
3622+ use std::process::Command;
3623+
3624+ fn main() {
3625+ println!("cargo:rerun-if-changed=src/schema.sql.m4");
3626+
3627+ let output = Command::new("m4")
3628+ .arg("./src/schema.sql.m4")
3629+ .output()
3630+ .unwrap();
3631+ let mut file = std::fs::File::create("./src/schema.sql").unwrap();
3632+ file.write_all(&output.stdout).unwrap();
3633+ }
3634 diff --git a/core/src/config.rs b/core/src/config.rs
3635new file mode 100644
3636index 0000000..2e02cc6
3637--- /dev/null
3638+++ b/core/src/config.rs
3639 @@ -0,0 +1,109 @@
3640+ /*
3641+ * This file is part of mailpot
3642+ *
3643+ * Copyright 2020 - Manos Pitsidianakis
3644+ *
3645+ * This program is free software: you can redistribute it and/or modify
3646+ * it under the terms of the GNU Affero General Public License as
3647+ * published by the Free Software Foundation, either version 3 of the
3648+ * License, or (at your option) any later version.
3649+ *
3650+ * This program is distributed in the hope that it will be useful,
3651+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3652+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3653+ * GNU Affero General Public License for more details.
3654+ *
3655+ * You should have received a copy of the GNU Affero General Public License
3656+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
3657+ */
3658+
3659+ use super::errors::*;
3660+ use chrono::prelude::*;
3661+ use std::cell::RefCell;
3662+ use std::io::{Read, Write};
3663+ use std::os::unix::fs::PermissionsExt;
3664+ use std::path::PathBuf;
3665+
3666+ thread_local!(pub static CONFIG: RefCell<Configuration> = RefCell::new(Configuration::new()));
3667+
3668+ #[derive(Debug, Serialize, Deserialize, Clone)]
3669+ #[serde(tag = "type", content = "value")]
3670+ pub enum SendMail {
3671+ Smtp(melib::smtp::SmtpServerConf),
3672+ ShellCommand(String),
3673+ }
3674+
3675+ #[derive(Debug, Serialize, Deserialize, Clone)]
3676+ pub struct Configuration {
3677+ pub send_mail: SendMail,
3678+ #[serde(default = "default_storage_fn")]
3679+ pub storage: String,
3680+ }
3681+
3682+ impl Configuration {
3683+ pub(crate) fn new() -> Self {
3684+ Configuration {
3685+ send_mail: SendMail::ShellCommand(String::new()),
3686+ storage: "sqlite3".into(),
3687+ }
3688+ }
3689+
3690+ pub fn init() -> Result<()> {
3691+ let path =
3692+ xdg::BaseDirectories::with_prefix("mailpot")?.place_config_file("config.toml")?;
3693+ if !path.exists() {
3694+ return Err(format!("Configuration file {} doesn't exist", path.display()).into());
3695+ }
3696+ let mut s = String::new();
3697+ let mut file = std::fs::File::open(&path)?;
3698+ file.read_to_string(&mut s)?;
3699+ let config: Configuration = toml::from_str(&s)?;
3700+ CONFIG.with(|f| {
3701+ *f.borrow_mut() = config;
3702+ });
3703+
3704+ Ok(())
3705+ }
3706+
3707+ pub fn data_directory() -> Result<PathBuf> {
3708+ Ok(xdg::BaseDirectories::with_prefix("mailpot")?.get_data_home())
3709+ }
3710+
3711+ pub fn save_message_to_path(msg: &str, mut path: PathBuf) -> Result<PathBuf> {
3712+ let now = Local::now().timestamp();
3713+ path.push(format!("{}-failed.eml", now));
3714+
3715+ let mut file = std::fs::File::create(&path)?;
3716+ let metadata = file.metadata()?;
3717+ let mut permissions = metadata.permissions();
3718+
3719+ permissions.set_mode(0o600); // Read/write for owner only.
3720+ file.set_permissions(permissions)?;
3721+ file.write_all(msg.as_bytes())?;
3722+ file.flush()?;
3723+ Ok(path)
3724+ }
3725+
3726+ pub fn save_message(msg: String) -> Result<PathBuf> {
3727+ match Configuration::data_directory()
3728+ .and_then(|path| Self::save_message_to_path(&msg, path))
3729+ {
3730+ Ok(p) => return Ok(p),
3731+ Err(err) => {
3732+ eprintln!("{}", err);
3733+ }
3734+ };
3735+ match Self::save_message_to_path(&msg, PathBuf::from(".")) {
3736+ Ok(p) => return Ok(p),
3737+ Err(err) => {
3738+ eprintln!("{}", err);
3739+ }
3740+ }
3741+ let temp_path = std::env::temp_dir();
3742+ Self::save_message_to_path(&msg, temp_path)
3743+ }
3744+ }
3745+
3746+ fn default_storage_fn() -> String {
3747+ "sqlite3".to_string()
3748+ }
3749 diff --git a/core/src/db.rs b/core/src/db.rs
3750new file mode 100644
3751index 0000000..9e3abdf
3752--- /dev/null
3753+++ b/core/src/db.rs
3754 @@ -0,0 +1,604 @@
3755+ /*
3756+ * This file is part of mailpot
3757+ *
3758+ * Copyright 2020 - Manos Pitsidianakis
3759+ *
3760+ * This program is free software: you can redistribute it and/or modify
3761+ * it under the terms of the GNU Affero General Public License as
3762+ * published by the Free Software Foundation, either version 3 of the
3763+ * License, or (at your option) any later version.
3764+ *
3765+ * This program is distributed in the hope that it will be useful,
3766+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3767+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3768+ * GNU Affero General Public License for more details.
3769+ *
3770+ * You should have received a copy of the GNU Affero General Public License
3771+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
3772+ */
3773+
3774+ use super::*;
3775+ use melib::Envelope;
3776+ use models::changesets::*;
3777+ use rusqlite::Connection as DbConnection;
3778+ use rusqlite::OptionalExtension;
3779+ use std::path::PathBuf;
3780+
3781+ const DB_NAME: &str = "mpot.db";
3782+
3783+ pub struct Database {
3784+ pub connection: DbConnection,
3785+ }
3786+
3787+ impl Database {
3788+ pub fn list_lists(&self) -> Result<Vec<DbVal<MailingList>>> {
3789+ let mut stmt = self.connection.prepare("SELECT * FROM mailing_lists;")?;
3790+ let list_iter = stmt.query_map([], |row| {
3791+ let pk = row.get("pk")?;
3792+ Ok(DbVal(
3793+ MailingList {
3794+ pk,
3795+ name: row.get("name")?,
3796+ id: row.get("id")?,
3797+ address: row.get("address")?,
3798+ description: row.get("description")?,
3799+ archive_url: row.get("archive_url")?,
3800+ },
3801+ pk,
3802+ ))
3803+ })?;
3804+
3805+ let mut ret = vec![];
3806+ for list in list_iter {
3807+ let list = list?;
3808+ ret.push(list);
3809+ }
3810+ Ok(ret)
3811+ }
3812+
3813+ pub fn get_list(&self, pk: i64) -> Result<Option<DbVal<MailingList>>> {
3814+ let mut stmt = self
3815+ .connection
3816+ .prepare("SELECT * FROM mailing_lists WHERE pk = ?;")?;
3817+ let ret = stmt
3818+ .query_row([&pk], |row| {
3819+ let pk = row.get("pk")?;
3820+ Ok(DbVal(
3821+ MailingList {
3822+ pk,
3823+ name: row.get("name")?,
3824+ id: row.get("id")?,
3825+ address: row.get("address")?,
3826+ description: row.get("description")?,
3827+ archive_url: row.get("archive_url")?,
3828+ },
3829+ pk,
3830+ ))
3831+ })
3832+ .optional()?;
3833+
3834+ Ok(ret)
3835+ }
3836+
3837+ pub fn create_list(&self, new_val: MailingList) -> Result<DbVal<MailingList>> {
3838+ let mut stmt = self
3839+ .connection
3840+ .prepare("INSERT INTO mailing_lists(name, id, address, description, archive_url) VALUES(?, ?, ?, ?, ?) RETURNING *;")?;
3841+ let ret = stmt.query_row(
3842+ rusqlite::params![
3843+ &new_val.name,
3844+ &new_val.id,
3845+ &new_val.address,
3846+ new_val.description.as_ref(),
3847+ new_val.archive_url.as_ref(),
3848+ ],
3849+ |row| {
3850+ let pk = row.get("pk")?;
3851+ Ok(DbVal(
3852+ MailingList {
3853+ pk,
3854+ name: row.get("name")?,
3855+ id: row.get("id")?,
3856+ address: row.get("address")?,
3857+ description: row.get("description")?,
3858+ archive_url: row.get("archive_url")?,
3859+ },
3860+ pk,
3861+ ))
3862+ },
3863+ )?;
3864+
3865+ trace!("create_list {:?}.", &ret);
3866+ Ok(ret)
3867+ }
3868+
3869+ pub fn update_list(&self, _change_set: MailingListChangeset) -> Result<()> {
3870+ /*
3871+ diesel::update(mailing_lists::table)
3872+ .set(&set)
3873+ .execute(&self.connection)?;
3874+ */
3875+ Ok(())
3876+ }
3877+
3878+ pub fn get_list_policy(&self, pk: i64) -> Result<Option<DbVal<PostPolicy>>> {
3879+ let mut stmt = self
3880+ .connection
3881+ .prepare("SELECT * FROM post_policy WHERE list = ?;")?;
3882+ let ret = stmt
3883+ .query_row([&pk], |row| {
3884+ let pk = row.get("pk")?;
3885+ Ok(DbVal(
3886+ PostPolicy {
3887+ pk,
3888+ list: row.get("list")?,
3889+ announce_only: row.get("announce_only")?,
3890+ subscriber_only: row.get("subscriber_only")?,
3891+ approval_needed: row.get("approval_needed")?,
3892+ },
3893+ pk,
3894+ ))
3895+ })
3896+ .optional()?;
3897+
3898+ Ok(ret)
3899+ }
3900+
3901+ pub fn get_list_owners(&self, pk: i64) -> Result<Vec<DbVal<ListOwner>>> {
3902+ let mut stmt = self
3903+ .connection
3904+ .prepare("SELECT * FROM list_owner WHERE list = ?;")?;
3905+ let list_iter = stmt.query_map([&pk], |row| {
3906+ let pk = row.get("pk")?;
3907+ Ok(DbVal(
3908+ ListOwner {
3909+ pk,
3910+ list: row.get("list")?,
3911+ address: row.get("address")?,
3912+ name: row.get("name")?,
3913+ },
3914+ pk,
3915+ ))
3916+ })?;
3917+
3918+ let mut ret = vec![];
3919+ for list in list_iter {
3920+ let list = list?;
3921+ ret.push(list);
3922+ }
3923+ Ok(ret)
3924+ }
3925+
3926+ pub fn list_members(&self, pk: i64) -> Result<Vec<DbVal<ListMembership>>> {
3927+ let mut stmt = self
3928+ .connection
3929+ .prepare("SELECT * FROM membership WHERE list = ?;")?;
3930+ let list_iter = stmt.query_map([&pk], |row| {
3931+ let pk = row.get("pk")?;
3932+ Ok(DbVal(
3933+ ListMembership {
3934+ pk: row.get("pk")?,
3935+ list: row.get("list")?,
3936+ address: row.get("address")?,
3937+ name: row.get("name")?,
3938+ digest: row.get("digest")?,
3939+ hide_address: row.get("hide_address")?,
3940+ receive_duplicates: row.get("receive_duplicates")?,
3941+ receive_own_posts: row.get("receive_own_posts")?,
3942+ receive_confirmation: row.get("receive_confirmation")?,
3943+ enabled: row.get("enabled")?,
3944+ },
3945+ pk,
3946+ ))
3947+ })?;
3948+
3949+ let mut ret = vec![];
3950+ for list in list_iter {
3951+ let list = list?;
3952+ ret.push(list);
3953+ }
3954+ Ok(ret)
3955+ }
3956+
3957+ pub fn add_member(
3958+ &self,
3959+ list_pk: i64,
3960+ mut new_val: ListMembership,
3961+ ) -> Result<DbVal<ListMembership>> {
3962+ new_val.list = list_pk;
3963+ let mut stmt = self
3964+ .connection
3965+ .prepare("INSERT INTO membership(list, address, name, enabled, digest, hide_address, receive_duplicates, receive_own_posts, receive_confirmation) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *;")?;
3966+ let ret = stmt.query_row(
3967+ rusqlite::params![
3968+ &new_val.list,
3969+ &new_val.address,
3970+ &new_val.name,
3971+ &new_val.enabled,
3972+ &new_val.digest,
3973+ &new_val.hide_address,
3974+ &new_val.receive_duplicates,
3975+ &new_val.receive_own_posts,
3976+ &new_val.receive_confirmation
3977+ ],
3978+ |row| {
3979+ let pk = row.get("pk")?;
3980+ Ok(DbVal(
3981+ ListMembership {
3982+ pk,
3983+ list: row.get("list")?,
3984+ address: row.get("address")?,
3985+ name: row.get("name")?,
3986+ digest: row.get("digest")?,
3987+ hide_address: row.get("hide_address")?,
3988+ receive_duplicates: row.get("receive_duplicates")?,
3989+ receive_own_posts: row.get("receive_own_posts")?,
3990+ receive_confirmation: row.get("receive_confirmation")?,
3991+ enabled: row.get("enabled")?,
3992+ },
3993+ pk,
3994+ ))
3995+ },
3996+ )?;
3997+
3998+ trace!("add_member {:?}.", &ret);
3999+ Ok(ret)
4000+ }
4001+
4002+ pub fn add_candidate_member(&self, list_pk: i64, mut new_val: ListMembership) -> Result<i64> {
4003+ new_val.list = list_pk;
4004+ let mut stmt = self
4005+ .connection
4006+ .prepare("INSERT INTO candidate_membership(list, address, name, accepted) VALUES(?, ?, ?, ?) RETURNING pk;")?;
4007+ let ret = stmt.query_row(
4008+ rusqlite::params![&new_val.list, &new_val.address, &new_val.name, &false,],
4009+ |row| {
4010+ let pk: i64 = row.get("pk")?;
4011+ Ok(pk)
4012+ },
4013+ )?;
4014+
4015+ trace!("add_candidate_member {:?}.", &ret);
4016+ Ok(ret)
4017+ }
4018+
4019+ pub fn accept_candidate_member(&mut self, pk: i64) -> Result<DbVal<ListMembership>> {
4020+ let tx = self.connection.transaction()?;
4021+ let mut stmt = tx
4022+ .prepare("INSERT INTO membership(list, address, name, enabled, digest, hide_address, receive_duplicates, receive_own_posts, receive_confirmation) FROM (SELECT list, address, name FROM candidate_membership WHERE pk = ?) RETURNING *;")?;
4023+ let ret = stmt.query_row(rusqlite::params![&pk], |row| {
4024+ let pk = row.get("pk")?;
4025+ Ok(DbVal(
4026+ ListMembership {
4027+ pk,
4028+ list: row.get("list")?,
4029+ address: row.get("address")?,
4030+ name: row.get("name")?,
4031+ digest: row.get("digest")?,
4032+ hide_address: row.get("hide_address")?,
4033+ receive_duplicates: row.get("receive_duplicates")?,
4034+ receive_own_posts: row.get("receive_own_posts")?,
4035+ receive_confirmation: row.get("receive_confirmation")?,
4036+ enabled: row.get("enabled")?,
4037+ },
4038+ pk,
4039+ ))
4040+ })?;
4041+ drop(stmt);
4042+ tx.execute(
4043+ "UPDATE candidate_membership SET accepted = ? WHERE pk = ?;",
4044+ [&pk],
4045+ )?;
4046+ tx.commit()?;
4047+
4048+ trace!("accept_candidate_member {:?}.", &ret);
4049+ Ok(ret)
4050+ }
4051+
4052+ pub fn remove_member(&self, list_pk: i64, address: &str) -> Result<()> {
4053+ self.connection.execute(
4054+ "DELETE FROM membership WHERE list_pk = ? AND address = ?;",
4055+ rusqlite::params![&list_pk, &address],
4056+ )?;
4057+
4058+ Ok(())
4059+ }
4060+
4061+ pub fn update_member(&self, _change_set: ListMembershipChangeset) -> Result<()> {
4062+ /*
4063+ diesel::update(membership::table)
4064+ .set(&set)
4065+ .execute(&self.connection)?;
4066+ */
4067+ Ok(())
4068+ }
4069+
4070+ pub fn db_path() -> Result<PathBuf> {
4071+ let name = DB_NAME;
4072+ let data_dir = xdg::BaseDirectories::with_prefix("mailpot")?;
4073+ Ok(data_dir.place_data_file(name)?)
4074+ }
4075+
4076+ pub fn open_db(db_path: PathBuf) -> Result<Self> {
4077+ if !db_path.exists() {
4078+ return Err("Database doesn't exist".into());
4079+ }
4080+ Ok(Database {
4081+ connection: DbConnection::open(&db_path.to_str().unwrap())?,
4082+ })
4083+ }
4084+
4085+ pub fn open_or_create_db() -> Result<Self> {
4086+ let db_path = Self::db_path()?;
4087+ let mut set_mode = false;
4088+ if !db_path.exists() {
4089+ info!("Creating {} database in {}", DB_NAME, db_path.display());
4090+ set_mode = true;
4091+ }
4092+ let conn = DbConnection::open(&db_path.to_str().unwrap())?;
4093+ if set_mode {
4094+ use std::os::unix::fs::PermissionsExt;
4095+ let file = std::fs::File::open(&db_path)?;
4096+ let metadata = file.metadata()?;
4097+ let mut permissions = metadata.permissions();
4098+
4099+ permissions.set_mode(0o600); // Read/write for owner only.
4100+ file.set_permissions(permissions)?;
4101+ }
4102+
4103+ Ok(Database { connection: conn })
4104+ }
4105+
4106+ pub fn get_list_filters(
4107+ &self,
4108+ _list: &DbVal<MailingList>,
4109+ ) -> Vec<Box<dyn crate::mail::message_filters::PostFilter>> {
4110+ use crate::mail::message_filters::*;
4111+ vec![
4112+ Box::new(FixCRLF),
4113+ Box::new(PostRightsCheck),
4114+ Box::new(AddListHeaders),
4115+ Box::new(FinalizeRecipients),
4116+ ]
4117+ }
4118+
4119+ pub fn insert_post(&self, list_pk: i64, message: &[u8], env: &Envelope) -> Result<i64> {
4120+ let address = env.from()[0].to_string();
4121+ let message_id = env.message_id_display();
4122+ let mut stmt = self.connection.prepare(
4123+ "INSERT INTO post(list, address, message_id, message) VALUES(?, ?, ?, ?) RETURNING pk;",
4124+ )?;
4125+ let pk = stmt.query_row(
4126+ rusqlite::params![&list_pk, &address, &message_id, &message],
4127+ |row| {
4128+ let pk: i64 = row.get("pk")?;
4129+ Ok(pk)
4130+ },
4131+ )?;
4132+
4133+ trace!(
4134+ "insert_post list_pk {}, from {:?} message_id {:?} post_pk {}.",
4135+ list_pk,
4136+ address,
4137+ message_id,
4138+ pk
4139+ );
4140+ Ok(pk)
4141+ }
4142+
4143+ pub fn post(&self, env: Envelope, raw: &[u8], _dry_run: bool) -> Result<()> {
4144+ trace!("Received envelope to post: {:#?}", &env);
4145+ let tos = env.to().to_vec();
4146+ if tos.is_empty() {
4147+ return Err("Envelope To: field is empty!".into());
4148+ }
4149+ if env.from().is_empty() {
4150+ return Err("Envelope From: field is empty!".into());
4151+ }
4152+ let mut lists = self.list_lists()?;
4153+ for t in &tos {
4154+ if let Some((addr, subaddr)) = t.subaddress("+") {
4155+ lists.retain(|list| {
4156+ if !addr.contains_address(&list.list_address()) {
4157+ return true;
4158+ }
4159+ if let Err(err) = self.request(
4160+ list,
4161+ match subaddr.as_str() {
4162+ "subscribe" | "request" if env.subject().trim() == "subscribe" => {
4163+ ListRequest::Subscribe
4164+ }
4165+ "unsubscribe" | "request" if env.subject().trim() == "unsubscribe" => {
4166+ ListRequest::Unsubscribe
4167+ }
4168+ "request" => ListRequest::Other(env.subject().trim().to_string()),
4169+ _ => {
4170+ trace!(
4171+ "unknown action = {} for addresses {:?} in list {}",
4172+ subaddr,
4173+ env.from(),
4174+ list
4175+ );
4176+ ListRequest::Other(subaddr.trim().to_string())
4177+ }
4178+ },
4179+ &env,
4180+ raw,
4181+ ) {
4182+ info!("Processing request returned error: {}", err);
4183+ }
4184+ false
4185+ });
4186+ }
4187+ }
4188+
4189+ lists.retain(|list| {
4190+ trace!(
4191+ "Is post related to list {}? {}",
4192+ &list,
4193+ tos.iter().any(|a| a.contains_address(&list.list_address()))
4194+ );
4195+
4196+ tos.iter().any(|a| a.contains_address(&list.list_address()))
4197+ });
4198+ if lists.is_empty() {
4199+ return Ok(());
4200+ }
4201+
4202+ let mut configuration = crate::config::Configuration::new();
4203+ crate::config::CONFIG.with(|f| {
4204+ configuration = f.borrow().clone();
4205+ });
4206+ trace!("Configuration is {:#?}", &configuration);
4207+ use crate::mail::{ListContext, Post, PostAction};
4208+ for mut list in lists {
4209+ trace!("Examining list {}", list.list_id());
4210+ let post_pk = self.insert_post(list.pk, raw, &env)?;
4211+ let filters = self.get_list_filters(&list);
4212+ let memberships = self.list_members(list.pk)?;
4213+ trace!("List members {:#?}", &memberships);
4214+ let mut list_ctx = ListContext {
4215+ policy: self.get_list_policy(list.pk)?,
4216+ list_owners: self.get_list_owners(list.pk)?,
4217+ list: &mut list,
4218+ memberships: &memberships,
4219+ scheduled_jobs: vec![],
4220+ };
4221+ let mut post = Post {
4222+ pk: post_pk,
4223+ from: env.from()[0].clone(),
4224+ bytes: raw.to_vec(),
4225+ to: env.to().to_vec(),
4226+ action: PostAction::Hold,
4227+ };
4228+ let result = filters
4229+ .into_iter()
4230+ .fold(Ok((&mut post, &mut list_ctx)), |p, f| {
4231+ p.and_then(|(p, c)| f.feed(p, c))
4232+ });
4233+ trace!("result {:#?}", result);
4234+
4235+ let Post { bytes, action, .. } = post;
4236+ match configuration.send_mail {
4237+ crate::config::SendMail::Smtp(ref smtp_conf) => {
4238+ let smtp_conf = smtp_conf.clone();
4239+ use melib::futures;
4240+ use melib::smol;
4241+ use melib::smtp::*;
4242+ let mut conn = smol::future::block_on(smol::spawn(
4243+ SmtpConnection::new_connection(smtp_conf.clone()),
4244+ ))?;
4245+ match action {
4246+ PostAction::Accept => {
4247+ for job in list_ctx.scheduled_jobs.iter() {
4248+ if let crate::mail::MailJob::Send {
4249+ message_pk: _,
4250+ recipients,
4251+ } = job
4252+ {
4253+ futures::executor::block_on(conn.mail_transaction(
4254+ &String::from_utf8_lossy(&bytes),
4255+ Some(recipients),
4256+ ))?;
4257+ }
4258+ }
4259+ /* - Save digest metadata in database */
4260+ }
4261+ PostAction::Reject { reason: _ } => {
4262+ /* - Notify submitter */
4263+ //futures::executor::block_on(conn.mail_transaction(&post.bytes, b)).unwrap();
4264+ }
4265+ PostAction::Defer { reason: _ } => {
4266+ /* - Notify submitter
4267+ * - Save in database */
4268+ }
4269+ PostAction::Hold => { /* - Save in database */ }
4270+ }
4271+ }
4272+ _ => {}
4273+ }
4274+ }
4275+
4276+ Ok(())
4277+ }
4278+
4279+ pub fn request(
4280+ &self,
4281+ list: &DbVal<MailingList>,
4282+ request: ListRequest,
4283+ env: &Envelope,
4284+ _raw: &[u8],
4285+ ) -> Result<()> {
4286+ match request {
4287+ ListRequest::Subscribe => {
4288+ trace!(
4289+ "subscribe action for addresses {:?} in list {}",
4290+ env.from(),
4291+ list
4292+ );
4293+
4294+ let list_policy = self.get_list_policy(list.pk)?;
4295+ let approval_needed = list_policy
4296+ .as_ref()
4297+ .map(|p| p.approval_needed)
4298+ .unwrap_or(false);
4299+ for f in env.from() {
4300+ let membership = ListMembership {
4301+ pk: 0,
4302+ list: list.pk,
4303+ address: f.get_email(),
4304+ name: f.get_display_name(),
4305+ digest: false,
4306+ hide_address: false,
4307+ receive_duplicates: true,
4308+ receive_own_posts: false,
4309+ receive_confirmation: true,
4310+ enabled: !approval_needed,
4311+ };
4312+ if approval_needed {
4313+ match self.add_candidate_member(list.pk, membership) {
4314+ Ok(_) => {}
4315+ Err(_err) => {}
4316+ }
4317+ //FIXME: send notification to list-owner
4318+ } else if let Err(_err) = self.add_member(list.pk, membership) {
4319+ //FIXME: send failure notice to f
4320+ } else {
4321+ //FIXME: send success notice
4322+ }
4323+ }
4324+ }
4325+ ListRequest::Unsubscribe => {
4326+ trace!(
4327+ "unsubscribe action for addresses {:?} in list {}",
4328+ env.from(),
4329+ list
4330+ );
4331+ for f in env.from() {
4332+ if let Err(_err) = self.remove_member(list.pk, &f.get_email()) {
4333+ //FIXME: send failure notice to f
4334+ } else {
4335+ //FIXME: send success notice to f
4336+ }
4337+ }
4338+ }
4339+ ListRequest::Other(ref req) if req == "owner" => {
4340+ trace!(
4341+ "list-owner mail action for addresses {:?} in list {}",
4342+ env.from(),
4343+ list
4344+ );
4345+ //FIXME: mail to list-owner
4346+ }
4347+ ListRequest::Other(ref req) => {
4348+ trace!(
4349+ "unknown request action {} for addresses {:?} in list {}",
4350+ req,
4351+ env.from(),
4352+ list
4353+ );
4354+ }
4355+ }
4356+ Ok(())
4357+ }
4358+ }
4359 diff --git a/core/src/errors.rs b/core/src/errors.rs
4360new file mode 100644
4361index 0000000..ae61ca5
4362--- /dev/null
4363+++ b/core/src/errors.rs
4364 @@ -0,0 +1,30 @@
4365+ /*
4366+ * This file is part of mailpot
4367+ *
4368+ * Copyright 2020 - Manos Pitsidianakis
4369+ *
4370+ * This program is free software: you can redistribute it and/or modify
4371+ * it under the terms of the GNU Affero General Public License as
4372+ * published by the Free Software Foundation, either version 3 of the
4373+ * License, or (at your option) any later version.
4374+ *
4375+ * This program is distributed in the hope that it will be useful,
4376+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4377+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4378+ * GNU Affero General Public License for more details.
4379+ *
4380+ * You should have received a copy of the GNU Affero General Public License
4381+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
4382+ */
4383+
4384+ // Create the Error, ErrorKind, ResultExt, and Result types
4385+ error_chain! {
4386+ foreign_links {
4387+ Sql(rusqlite::Error);
4388+ Io(::std::io::Error);
4389+ Xdg(xdg::BaseDirectoriesError);
4390+ Melib(melib::error::MeliError);
4391+ Configuration(toml::de::Error);
4392+ SerdeJson(serde_json::Error);
4393+ }
4394+ }
4395 diff --git a/core/src/lib.rs b/core/src/lib.rs
4396new file mode 100644
4397index 0000000..f997cc3
4398--- /dev/null
4399+++ b/core/src/lib.rs
4400 @@ -0,0 +1,38 @@
4401+ /*
4402+ * This file is part of mailpot
4403+ *
4404+ * Copyright 2020 - Manos Pitsidianakis
4405+ *
4406+ * This program is free software: you can redistribute it and/or modify
4407+ * it under the terms of the GNU Affero General Public License as
4408+ * published by the Free Software Foundation, either version 3 of the
4409+ * License, or (at your option) any later version.
4410+ *
4411+ * This program is distributed in the hope that it will be useful,
4412+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4413+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4414+ * GNU Affero General Public License for more details.
4415+ *
4416+ * You should have received a copy of the GNU Affero General Public License
4417+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
4418+ */
4419+ // `error_chain!` can recurse deeply
4420+ #![recursion_limit = "1024"]
4421+ //#![warn(missing_docs)]
4422+
4423+ use log::{info, trace};
4424+ #[macro_use]
4425+ extern crate error_chain;
4426+ #[macro_use]
4427+ pub extern crate serde;
4428+
4429+ pub use melib;
4430+ pub use serde_json;
4431+
4432+ pub mod config;
4433+ pub mod mail;
4434+ pub mod models;
4435+ use models::*;
4436+ pub mod errors;
4437+ use errors::*;
4438+ pub mod db;
4439 diff --git a/core/src/mail.rs b/core/src/mail.rs
4440new file mode 100644
4441index 0000000..6d8282c
4442--- /dev/null
4443+++ b/core/src/mail.rs
4444 @@ -0,0 +1,85 @@
4445+ /*
4446+ * This file is part of mailpot
4447+ *
4448+ * Copyright 2020 - Manos Pitsidianakis
4449+ *
4450+ * This program is free software: you can redistribute it and/or modify
4451+ * it under the terms of the GNU Affero General Public License as
4452+ * published by the Free Software Foundation, either version 3 of the
4453+ * License, or (at your option) any later version.
4454+ *
4455+ * This program is distributed in the hope that it will be useful,
4456+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4457+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4458+ * GNU Affero General Public License for more details.
4459+ *
4460+ * You should have received a copy of the GNU Affero General Public License
4461+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
4462+ */
4463+
4464+ use super::*;
4465+ use melib::Address;
4466+ pub mod message_filters;
4467+
4468+ #[derive(Debug)]
4469+ pub enum PostAction {
4470+ Hold,
4471+ Accept,
4472+ Reject { reason: String },
4473+ Defer { reason: String },
4474+ }
4475+
4476+ #[derive(Debug)]
4477+ pub struct ListContext<'list> {
4478+ pub list: &'list MailingList,
4479+ pub list_owners: Vec<DbVal<ListOwner>>,
4480+ pub memberships: &'list [DbVal<ListMembership>],
4481+ pub policy: Option<DbVal<PostPolicy>>,
4482+ pub scheduled_jobs: Vec<MailJob>,
4483+ }
4484+
4485+ ///Post to be considered by the list's `PostFilter` stack.
4486+ pub struct Post {
4487+ pub pk: i64,
4488+ pub from: Address,
4489+ pub bytes: Vec<u8>,
4490+ pub to: Vec<Address>,
4491+ pub action: PostAction,
4492+ }
4493+
4494+ impl core::fmt::Debug for Post {
4495+ fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
4496+ fmt.debug_struct("Post")
4497+ .field("pk", &self.pk)
4498+ .field("from", &self.from)
4499+ .field("bytes", &format_args!("{} bytes", self.bytes.len()))
4500+ .field("to", &self.to.as_slice())
4501+ .field("action", &self.action)
4502+ .finish()
4503+ }
4504+ }
4505+
4506+ #[derive(Debug)]
4507+ pub enum MailJob {
4508+ Send {
4509+ message_pk: i64,
4510+ recipients: Vec<Address>,
4511+ },
4512+ Relay {
4513+ message: Vec<u8>,
4514+ recipients: Vec<Address>,
4515+ },
4516+ Error {
4517+ description: String,
4518+ },
4519+ StoreDigest {
4520+ message_pk: i64,
4521+ recipients: Vec<Address>,
4522+ },
4523+ ConfirmSubscription {
4524+ recipient: Address,
4525+ },
4526+ ConfirmUnsubscription {
4527+ recipient: Address,
4528+ },
4529+ }
4530 diff --git a/core/src/mail/message_filters.rs b/core/src/mail/message_filters.rs
4531new file mode 100644
4532index 0000000..dbe157a
4533--- /dev/null
4534+++ b/core/src/mail/message_filters.rs
4535 @@ -0,0 +1,206 @@
4536+ /*
4537+ * This file is part of mailpot
4538+ *
4539+ * Copyright 2020 - Manos Pitsidianakis
4540+ *
4541+ * This program is free software: you can redistribute it and/or modify
4542+ * it under the terms of the GNU Affero General Public License as
4543+ * published by the Free Software Foundation, either version 3 of the
4544+ * License, or (at your option) any later version.
4545+ *
4546+ * This program is distributed in the hope that it will be useful,
4547+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4548+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4549+ * GNU Affero General Public License for more details.
4550+ *
4551+ * You should have received a copy of the GNU Affero General Public License
4552+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
4553+ */
4554+
4555+ use super::*;
4556+
4557+ ///Filter that modifies and/or verifies a post candidate. On rejection, return a string
4558+ ///describing the error and optionally set `post.action` to `Reject` or `Defer`
4559+ pub trait PostFilter {
4560+ fn feed<'p, 'list>(
4561+ self: Box<Self>,
4562+ post: &'p mut Post,
4563+ ctx: &'p mut ListContext<'list>,
4564+ ) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()>;
4565+ }
4566+
4567+ ///Check that submitter can post to list, for now it accepts everything.
4568+ pub struct PostRightsCheck;
4569+ impl PostFilter for PostRightsCheck {
4570+ fn feed<'p, 'list>(
4571+ self: Box<Self>,
4572+ post: &'p mut Post,
4573+ ctx: &'p mut ListContext<'list>,
4574+ ) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
4575+ trace!("Running PostRightsCheck filter");
4576+ if let Some(ref policy) = ctx.policy {
4577+ if policy.announce_only {
4578+ trace!("post policy is announce_only");
4579+ let owner_addresses = ctx
4580+ .list_owners
4581+ .iter()
4582+ .map(|lo| lo.into_address())
4583+ .collect::<Vec<Address>>();
4584+ trace!("Owner addresses are: {:#?}", &owner_addresses);
4585+ trace!("Envelope from is: {:?}", &post.from);
4586+ if !owner_addresses.iter().any(|addr| *addr == post.from) {
4587+ trace!("Envelope From does not include any owner");
4588+ post.action = PostAction::Reject {
4589+ reason: "You are not allowed to post on this list.".to_string(),
4590+ };
4591+ return Err(());
4592+ }
4593+ } else if policy.subscriber_only {
4594+ trace!("post policy is subscriber_only");
4595+ let email_from = post.from.get_email();
4596+ trace!("post from is {:?}", &email_from);
4597+ trace!("post memberships are {:#?}", &ctx.memberships);
4598+ if !ctx.memberships.iter().any(|lm| lm.address == email_from) {
4599+ trace!("Envelope from is not subscribed to this list");
4600+ post.action = PostAction::Reject {
4601+ reason: "Only subscribers can post to this list.".to_string(),
4602+ };
4603+ return Err(());
4604+ }
4605+ } else if policy.approval_needed {
4606+ trace!("post policy says approval_needed");
4607+ post.action = PostAction::Defer {
4608+ reason: "Your posting has been deferred. Approval from the list's moderators is required before it is submitted.".to_string(),
4609+ };
4610+ }
4611+ }
4612+ Ok((post, ctx))
4613+ }
4614+ }
4615+
4616+ ///Ensure message contains only `\r\n` line terminators, required by SMTP.
4617+ pub struct FixCRLF;
4618+ impl PostFilter for FixCRLF {
4619+ fn feed<'p, 'list>(
4620+ self: Box<Self>,
4621+ post: &'p mut Post,
4622+ ctx: &'p mut ListContext<'list>,
4623+ ) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
4624+ trace!("Running FixCRLF filter");
4625+ use std::io::prelude::*;
4626+ let mut new_vec = Vec::with_capacity(post.bytes.len());
4627+ for line in post.bytes.lines() {
4628+ new_vec.extend_from_slice(line.unwrap().as_bytes());
4629+ new_vec.extend_from_slice(b"\r\n");
4630+ }
4631+ post.bytes = new_vec;
4632+ Ok((post, ctx))
4633+ }
4634+ }
4635+
4636+ ///Add `List-*` headers
4637+ pub struct AddListHeaders;
4638+ impl PostFilter for AddListHeaders {
4639+ fn feed<'p, 'list>(
4640+ self: Box<Self>,
4641+ post: &'p mut Post,
4642+ ctx: &'p mut ListContext<'list>,
4643+ ) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
4644+ trace!("Running AddListHeaders filter");
4645+ let (mut headers, body) = melib::email::parser::mail(&post.bytes).unwrap();
4646+ let list_id = ctx.list.list_id();
4647+ headers.push((&b"List-ID"[..], list_id.as_bytes()));
4648+ let list_post = ctx.list.list_post();
4649+ let list_unsubscribe = ctx.list.list_unsubscribe();
4650+ let list_archive = ctx.list.list_archive();
4651+ if let Some(post) = list_post.as_ref() {
4652+ headers.push((&b"List-Post"[..], post.as_bytes()));
4653+ }
4654+ if let Some(unsubscribe) = list_unsubscribe.as_ref() {
4655+ headers.push((&b"List-Unsubscribe"[..], unsubscribe.as_bytes()));
4656+ }
4657+ if let Some(archive) = list_archive.as_ref() {
4658+ headers.push((&b"List-Archive"[..], archive.as_bytes()));
4659+ }
4660+ let mut new_vec = Vec::with_capacity(
4661+ headers
4662+ .iter()
4663+ .map(|(h, v)| h.len() + v.len() + ": \r\n".len())
4664+ .sum::<usize>()
4665+ + "\r\n\r\n".len()
4666+ + body.len(),
4667+ );
4668+ for (h, v) in headers {
4669+ new_vec.extend_from_slice(h);
4670+ new_vec.extend_from_slice(b": ");
4671+ new_vec.extend_from_slice(v);
4672+ new_vec.extend_from_slice(b"\r\n");
4673+ }
4674+ new_vec.extend_from_slice(b"\r\n\r\n");
4675+ new_vec.extend_from_slice(body);
4676+
4677+ post.bytes = new_vec;
4678+ Ok((post, ctx))
4679+ }
4680+ }
4681+
4682+ ///Adds `Archived-At` field, if configured.
4683+ pub struct ArchivedAtLink;
4684+ impl PostFilter for ArchivedAtLink {
4685+ fn feed<'p, 'list>(
4686+ self: Box<Self>,
4687+ post: &'p mut Post,
4688+ ctx: &'p mut ListContext<'list>,
4689+ ) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
4690+ trace!("Running ArchivedAtLink filter");
4691+ Ok((post, ctx))
4692+ }
4693+ }
4694+
4695+ ///Assuming there are no more changes to be done on the post, it finalizes which list members
4696+ ///will receive the post in `post.action` field.
4697+ pub struct FinalizeRecipients;
4698+ impl PostFilter for FinalizeRecipients {
4699+ fn feed<'p, 'list>(
4700+ self: Box<Self>,
4701+ post: &'p mut Post,
4702+ ctx: &'p mut ListContext<'list>,
4703+ ) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
4704+ trace!("Running FinalizeRecipients filter");
4705+ let mut recipients = vec![];
4706+ let mut digests = vec![];
4707+ let email_from = post.from.get_email();
4708+ for member in ctx.memberships {
4709+ trace!("examining member {:?}", &member);
4710+ if member.address != email_from {
4711+ trace!("member is submitter");
4712+ }
4713+ if member.digest {
4714+ if member.address != email_from || member.receive_own_posts {
4715+ trace!("Member gets digest");
4716+ digests.push(member.into_address());
4717+ }
4718+ continue;
4719+ }
4720+ if member.address != email_from || member.receive_own_posts {
4721+ trace!("Member gets copy");
4722+ recipients.push(member.into_address());
4723+ }
4724+ // TODO:
4725+ // - check for duplicates (To,Cc,Bcc)
4726+ // - send confirmation to submitter
4727+ }
4728+ ctx.scheduled_jobs.push(MailJob::Send {
4729+ message_pk: post.pk,
4730+ recipients,
4731+ });
4732+ if !digests.is_empty() {
4733+ ctx.scheduled_jobs.push(MailJob::StoreDigest {
4734+ message_pk: post.pk,
4735+ recipients: digests,
4736+ });
4737+ }
4738+ post.action = PostAction::Accept;
4739+ Ok((post, ctx))
4740+ }
4741+ }
4742 diff --git a/core/src/models.rs b/core/src/models.rs
4743new file mode 100644
4744index 0000000..330db8f
4745--- /dev/null
4746+++ b/core/src/models.rs
4747 @@ -0,0 +1,226 @@
4748+ /*
4749+ * This file is part of mailpot
4750+ *
4751+ * Copyright 2020 - Manos Pitsidianakis
4752+ *
4753+ * This program is free software: you can redistribute it and/or modify
4754+ * it under the terms of the GNU Affero General Public License as
4755+ * published by the Free Software Foundation, either version 3 of the
4756+ * License, or (at your option) any later version.
4757+ *
4758+ * This program is distributed in the hope that it will be useful,
4759+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4760+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4761+ * GNU Affero General Public License for more details.
4762+ *
4763+ * You should have received a copy of the GNU Affero General Public License
4764+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
4765+ */
4766+
4767+ use super::*;
4768+ pub mod changesets;
4769+
4770+ use melib::email::Address;
4771+
4772+ pub struct DbVal<T>(pub T, pub i64);
4773+
4774+ impl<T> std::ops::Deref for DbVal<T> {
4775+ type Target = T;
4776+ fn deref(&self) -> &T {
4777+ &self.0
4778+ }
4779+ }
4780+
4781+ impl<T> std::fmt::Display for DbVal<T>
4782+ where
4783+ T: std::fmt::Display,
4784+ {
4785+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
4786+ write!(fmt, "{}", self.0)
4787+ }
4788+ }
4789+
4790+ impl<T> std::fmt::Debug for DbVal<T>
4791+ where
4792+ T: std::fmt::Debug,
4793+ {
4794+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
4795+ write!(fmt, "{:?}", self.0)
4796+ }
4797+ }
4798+
4799+ impl<T> serde::Serialize for DbVal<T>
4800+ where
4801+ T: serde::Serialize,
4802+ {
4803+ fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
4804+ where
4805+ S: serde::Serializer,
4806+ {
4807+ self.0.serialize(serializer)
4808+ }
4809+ }
4810+
4811+ #[derive(Debug, Clone, Deserialize, Serialize)]
4812+ pub struct MailingList {
4813+ pub pk: i64,
4814+ pub name: String,
4815+ pub id: String,
4816+ pub address: String,
4817+ pub description: Option<String>,
4818+ pub archive_url: Option<String>,
4819+ }
4820+
4821+ impl std::fmt::Display for MailingList {
4822+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
4823+ if let Some(description) = self.description.as_ref() {
4824+ write!(
4825+ fmt,
4826+ "[#{} {}] {} <{}>: {}",
4827+ self.pk, self.id, self.name, self.address, description
4828+ )
4829+ } else {
4830+ write!(
4831+ fmt,
4832+ "[#{} {}] {} <{}>",
4833+ self.pk, self.id, self.name, self.address
4834+ )
4835+ }
4836+ }
4837+ }
4838+
4839+ impl MailingList {
4840+ pub fn list_id(&self) -> String {
4841+ format!("\"{}\" <{}>", self.name, self.address)
4842+ }
4843+
4844+ pub fn list_post(&self) -> Option<String> {
4845+ Some(format!("<mailto:{}>", self.address))
4846+ }
4847+
4848+ pub fn list_unsubscribe(&self) -> Option<String> {
4849+ let p = self.address.split('@').collect::<Vec<&str>>();
4850+ Some(format!(
4851+ "<mailto:{}-request@{}?subject=unsubscribe>",
4852+ p[0], p[1]
4853+ ))
4854+ }
4855+
4856+ pub fn list_archive(&self) -> Option<String> {
4857+ self.archive_url.as_ref().map(|url| format!("<{}>", url))
4858+ }
4859+
4860+ pub fn list_address(&self) -> Address {
4861+ Address::new(Some(self.name.clone()), self.address.clone())
4862+ }
4863+ }
4864+
4865+ #[derive(Debug, Clone, Deserialize, Serialize)]
4866+ pub struct ListMembership {
4867+ pub pk: i64,
4868+ pub list: i64,
4869+ pub address: String,
4870+ pub name: Option<String>,
4871+ pub digest: bool,
4872+ pub hide_address: bool,
4873+ pub receive_duplicates: bool,
4874+ pub receive_own_posts: bool,
4875+ pub receive_confirmation: bool,
4876+ pub enabled: bool,
4877+ }
4878+
4879+ impl std::fmt::Display for ListMembership {
4880+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
4881+ write!(
4882+ fmt,
4883+ "{} [digest: {}, hide_address: {} {}]",
4884+ self.into_address(),
4885+ self.digest,
4886+ self.hide_address,
4887+ if self.enabled {
4888+ "enabled"
4889+ } else {
4890+ "not enabled"
4891+ },
4892+ )
4893+ }
4894+ }
4895+
4896+ impl ListMembership {
4897+ pub fn into_address(&self) -> Address {
4898+ Address::new(self.name.clone(), self.address.clone())
4899+ }
4900+ }
4901+
4902+ #[derive(Debug, Clone, Deserialize, Serialize)]
4903+ pub struct PostPolicy {
4904+ pub pk: i64,
4905+ pub list: i64,
4906+ pub announce_only: bool,
4907+ pub subscriber_only: bool,
4908+ pub approval_needed: bool,
4909+ }
4910+
4911+ impl std::fmt::Display for PostPolicy {
4912+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
4913+ write!(fmt, "{:?}", self)
4914+ }
4915+ }
4916+
4917+ #[derive(Debug, Clone, Deserialize, Serialize)]
4918+ pub struct ListOwner {
4919+ pub pk: i64,
4920+ pub list: i64,
4921+ pub address: String,
4922+ pub name: Option<String>,
4923+ }
4924+
4925+ impl std::fmt::Display for ListOwner {
4926+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
4927+ write!(fmt, "[#{} {}] {}", self.pk, self.list, self.into_address())
4928+ }
4929+ }
4930+
4931+ impl From<ListOwner> for ListMembership {
4932+ fn from(val: ListOwner) -> ListMembership {
4933+ ListMembership {
4934+ pk: 0,
4935+ list: val.list,
4936+ address: val.address,
4937+ name: val.name,
4938+ digest: false,
4939+ hide_address: false,
4940+ receive_duplicates: true,
4941+ receive_own_posts: false,
4942+ receive_confirmation: true,
4943+ enabled: true,
4944+ }
4945+ }
4946+ }
4947+
4948+ impl ListOwner {
4949+ pub fn into_address(&self) -> Address {
4950+ Address::new(self.name.clone(), self.address.clone())
4951+ }
4952+ }
4953+
4954+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
4955+ pub enum ListRequest {
4956+ Subscribe,
4957+ Unsubscribe,
4958+ Other(String),
4959+ }
4960+
4961+ impl std::fmt::Display for ListRequest {
4962+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
4963+ write!(fmt, "{:?}", self)
4964+ }
4965+ }
4966+
4967+ #[derive(Debug, Clone, Deserialize, Serialize)]
4968+ pub struct NewListPost<'s> {
4969+ pub list: i64,
4970+ pub address: &'s str,
4971+ pub message_id: &'s str,
4972+ pub message: &'s [u8],
4973+ }
4974 diff --git a/core/src/models/changesets.rs b/core/src/models/changesets.rs
4975new file mode 100644
4976index 0000000..af4929e
4977--- /dev/null
4978+++ b/core/src/models/changesets.rs
4979 @@ -0,0 +1,80 @@
4980+ /*
4981+ * This file is part of mailpot
4982+ *
4983+ * Copyright 2020 - Manos Pitsidianakis
4984+ *
4985+ * This program is free software: you can redistribute it and/or modify
4986+ * it under the terms of the GNU Affero General Public License as
4987+ * published by the Free Software Foundation, either version 3 of the
4988+ * License, or (at your option) any later version.
4989+ *
4990+ * This program is distributed in the hope that it will be useful,
4991+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
4992+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4993+ * GNU Affero General Public License for more details.
4994+ *
4995+ * You should have received a copy of the GNU Affero General Public License
4996+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
4997+ */
4998+
4999+ #[derive(Debug, Clone, Deserialize, Serialize)]
5000+ pub struct MailingListChangeset {
5001+ pub pk: i64,
5002+ pub name: Option<String>,
5003+ pub id: Option<String>,
5004+ pub address: Option<String>,
5005+ pub description: Option<Option<String>>,
5006+ pub archive_url: Option<Option<String>>,
5007+ }
5008+
5009+ #[derive(Debug, Clone, Deserialize, Serialize)]
5010+ pub struct ListMembershipChangeset {
5011+ pub list: i64,
5012+ pub address: String,
5013+ pub name: Option<Option<String>>,
5014+ pub digest: Option<bool>,
5015+ pub hide_address: Option<bool>,
5016+ pub receive_duplicates: Option<bool>,
5017+ pub receive_own_posts: Option<bool>,
5018+ pub receive_confirmation: Option<bool>,
5019+ pub enabled: Option<bool>,
5020+ }
5021+
5022+ #[derive(Debug, Clone, Deserialize, Serialize)]
5023+ pub struct PostPolicyChangeset {
5024+ pub pk: i64,
5025+ pub list: i64,
5026+ pub announce_only: Option<bool>,
5027+ pub subscriber_only: Option<bool>,
5028+ pub approval_needed: Option<bool>,
5029+ }
5030+
5031+ #[derive(Debug, Clone, Deserialize, Serialize)]
5032+ pub struct ListOwnerChangeset {
5033+ pub pk: i64,
5034+ pub list: i64,
5035+ pub address: Option<String>,
5036+ pub name: Option<Option<String>>,
5037+ }
5038+
5039+ impl std::fmt::Display for MailingListChangeset {
5040+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
5041+ write!(fmt, "{:?}", self)
5042+ }
5043+ }
5044+
5045+ impl std::fmt::Display for ListMembershipChangeset {
5046+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
5047+ write!(fmt, "{:?}", self)
5048+ }
5049+ }
5050+ impl std::fmt::Display for PostPolicyChangeset {
5051+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
5052+ write!(fmt, "{:?}", self)
5053+ }
5054+ }
5055+ impl std::fmt::Display for ListOwnerChangeset {
5056+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
5057+ write!(fmt, "{:?}", self)
5058+ }
5059+ }
5060 diff --git a/core/src/schema.sql b/core/src/schema.sql
5061new file mode 100644
5062index 0000000..81d200b
5063--- /dev/null
5064+++ b/core/src/schema.sql
5065 @@ -0,0 +1,74 @@
5066+ PRAGMA foreign_keys = true;
5067+ PRAGMA encoding = 'UTF-8';
5068+
5069+ CREATE TABLE IF NOT EXISTS mailing_lists (
5070+ pk INTEGER PRIMARY KEY NOT NULL,
5071+ name TEXT NOT NULL,
5072+ id TEXT NOT NULL,
5073+ address TEXT NOT NULL,
5074+ archive_url TEXT,
5075+ description TEXT
5076+ );
5077+
5078+ CREATE TABLE IF NOT EXISTS list_owner (
5079+ pk INTEGER PRIMARY KEY NOT NULL,
5080+ list INTEGER NOT NULL,
5081+ address TEXT NOT NULL,
5082+ name TEXT,
5083+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
5084+ );
5085+
5086+ CREATE TABLE IF NOT EXISTS post_policy (
5087+ pk INTEGER PRIMARY KEY NOT NULL,
5088+ list INTEGER NOT NULL UNIQUE,
5089+ announce_only BOOLEAN CHECK (announce_only in (0, 1)) NOT NULL DEFAULT 0,
5090+ subscriber_only BOOLEAN CHECK (subscriber_only in (0, 1)) NOT NULL DEFAULT 0,
5091+ approval_needed BOOLEAN CHECK (approval_needed in (0, 1)) NOT NULL DEFAULT 0,
5092+ CHECK(((approval_needed) OR (((announce_only) OR (subscriber_only)) AND NOT ((announce_only) AND (subscriber_only)))) AND NOT ((approval_needed) AND (((announce_only) OR (subscriber_only)) AND NOT ((announce_only) AND (subscriber_only))))),
5093+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
5094+ );
5095+
5096+ CREATE TABLE IF NOT EXISTS membership (
5097+ pk INTEGER PRIMARY KEY NOT NULL,
5098+ list INTEGER NOT NULL,
5099+ address TEXT NOT NULL,
5100+ name TEXT,
5101+ enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1,
5102+ digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
5103+ hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
5104+ receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
5105+ receive_own_posts BOOLEAN CHECK (receive_own_posts in (0, 1)) NOT NULL DEFAULT 0,
5106+ receive_confirmation BOOLEAN CHECK (receive_confirmation in (0, 1)) NOT NULL DEFAULT 1,
5107+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
5108+ );
5109+
5110+ CREATE TABLE IF NOT EXISTS candidate_membership (
5111+ pk INTEGER PRIMARY KEY NOT NULL,
5112+ list INTEGER NOT NULL,
5113+ address TEXT NOT NULL,
5114+ name TEXT,
5115+ accepted INTEGER,
5116+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE,
5117+ FOREIGN KEY (accepted) REFERENCES membership(pk) ON DELETE CASCADE
5118+ );
5119+
5120+ CREATE TABLE IF NOT EXISTS post (
5121+ pk INTEGER PRIMARY KEY NOT NULL,
5122+ list INTEGER NOT NULL,
5123+ address TEXT NOT NULL,
5124+ message_id TEXT NOT NULL,
5125+ message BLOB NOT NULL,
5126+ FOREIGN KEY (list, address) REFERENCES membership(list, address) ON DELETE CASCADE
5127+ );
5128+
5129+ CREATE TABLE IF NOT EXISTS post_event (
5130+ pk INTEGER PRIMARY KEY NOT NULL,
5131+ post INTEGER NOT NULL,
5132+ date INTEGER NOT NULL,
5133+ kind CHAR(1) CHECK (kind IN ('R', 'S', 'D', 'B', 'O')) NOT NULL,
5134+ content TEXT NOT NULL,
5135+ FOREIGN KEY (post) REFERENCES post(pk) ON DELETE CASCADE
5136+ );
5137+
5138+ CREATE INDEX IF NOT EXISTS mailing_lists_idx ON mailing_lists(id);
5139+ CREATE INDEX IF NOT EXISTS membership_idx ON membership(address);
5140 diff --git a/core/src/schema.sql.m4 b/core/src/schema.sql.m4
5141new file mode 100644
5142index 0000000..344e8a0
5143--- /dev/null
5144+++ b/core/src/schema.sql.m4
5145 @@ -0,0 +1,78 @@
5146+ define(xor, `(($1) OR ($2)) AND NOT (($1) AND ($2))')dnl
5147+ define(BOOLEAN_TYPE, `$1 BOOLEAN CHECK ($1 in (0, 1)) NOT NULL')dnl
5148+ define(BOOLEAN_FALSE, `0')dnl
5149+ define(BOOLEAN_TRUE, `1')dnl
5150+ PRAGMA foreign_keys = true;
5151+ PRAGMA encoding = 'UTF-8';
5152+
5153+ CREATE TABLE IF NOT EXISTS mailing_lists (
5154+ pk INTEGER PRIMARY KEY NOT NULL,
5155+ name TEXT NOT NULL,
5156+ id TEXT NOT NULL,
5157+ address TEXT NOT NULL,
5158+ archive_url TEXT,
5159+ description TEXT
5160+ );
5161+
5162+ CREATE TABLE IF NOT EXISTS list_owner (
5163+ pk INTEGER PRIMARY KEY NOT NULL,
5164+ list INTEGER NOT NULL,
5165+ address TEXT NOT NULL,
5166+ name TEXT,
5167+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
5168+ );
5169+
5170+ CREATE TABLE IF NOT EXISTS post_policy (
5171+ pk INTEGER PRIMARY KEY NOT NULL,
5172+ list INTEGER NOT NULL UNIQUE,
5173+ BOOLEAN_TYPE(announce_only) DEFAULT BOOLEAN_FALSE(),
5174+ BOOLEAN_TYPE(subscriber_only) DEFAULT BOOLEAN_FALSE(),
5175+ BOOLEAN_TYPE(approval_needed) DEFAULT BOOLEAN_FALSE(),
5176+ CHECK(xor(approval_needed, xor(announce_only, subscriber_only))),
5177+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
5178+ );
5179+
5180+ CREATE TABLE IF NOT EXISTS membership (
5181+ pk INTEGER PRIMARY KEY NOT NULL,
5182+ list INTEGER NOT NULL,
5183+ address TEXT NOT NULL,
5184+ name TEXT,
5185+ BOOLEAN_TYPE(enabled) DEFAULT BOOLEAN_TRUE(),
5186+ BOOLEAN_TYPE(digest) DEFAULT BOOLEAN_FALSE(),
5187+ BOOLEAN_TYPE(hide_address) DEFAULT BOOLEAN_FALSE(),
5188+ BOOLEAN_TYPE(receive_duplicates) DEFAULT BOOLEAN_TRUE(),
5189+ BOOLEAN_TYPE(receive_own_posts) DEFAULT BOOLEAN_FALSE(),
5190+ BOOLEAN_TYPE(receive_confirmation) DEFAULT BOOLEAN_TRUE(),
5191+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
5192+ );
5193+
5194+ CREATE TABLE IF NOT EXISTS candidate_membership (
5195+ pk INTEGER PRIMARY KEY NOT NULL,
5196+ list INTEGER NOT NULL,
5197+ address TEXT NOT NULL,
5198+ name TEXT,
5199+ accepted INTEGER,
5200+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE,
5201+ FOREIGN KEY (accepted) REFERENCES membership(pk) ON DELETE CASCADE
5202+ );
5203+
5204+ CREATE TABLE IF NOT EXISTS post (
5205+ pk INTEGER PRIMARY KEY NOT NULL,
5206+ list INTEGER NOT NULL,
5207+ address TEXT NOT NULL,
5208+ message_id TEXT NOT NULL,
5209+ message BLOB NOT NULL,
5210+ FOREIGN KEY (list, address) REFERENCES membership(list, address) ON DELETE CASCADE
5211+ );
5212+
5213+ CREATE TABLE IF NOT EXISTS post_event (
5214+ pk INTEGER PRIMARY KEY NOT NULL,
5215+ post INTEGER NOT NULL,
5216+ date INTEGER NOT NULL,
5217+ kind CHAR(1) CHECK (kind IN ('R', 'S', 'D', 'B', 'O')) NOT NULL,
5218+ content TEXT NOT NULL,
5219+ FOREIGN KEY (post) REFERENCES post(pk) ON DELETE CASCADE
5220+ );
5221+
5222+ CREATE INDEX IF NOT EXISTS mailing_lists_idx ON mailing_lists(id);
5223+ CREATE INDEX IF NOT EXISTS membership_idx ON membership(address);
5224 diff --git a/rest-http/Cargo.toml b/rest-http/Cargo.toml
5225new file mode 100644
5226index 0000000..f29d0ec
5227--- /dev/null
5228+++ b/rest-http/Cargo.toml
5229 @@ -0,0 +1,21 @@
5230+ [package]
5231+ name = "mailpot-http"
5232+ version = "0.1.0"
5233+ authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
5234+ edition = "2018"
5235+ license = "LICENSE"
5236+ readme = "README.md"
5237+ description = "mailing list manager"
5238+ repository = "https://github.com/meli/mailpot"
5239+ keywords = ["mail", "mailing-lists" ]
5240+ categories = ["email"]
5241+ default-run = "mpot-http"
5242+
5243+ [[bin]]
5244+ name = "mpot-http"
5245+ path = "src/main.rs"
5246+
5247+ [dependencies]
5248+ mailpot = { version = "0.1.0", path = "../core" }
5249+ tokio = { version = "1", features = ["full"] }
5250+ warp = "0.3"
5251 diff --git a/rest-http/README.md b/rest-http/README.md
5252new file mode 100644
5253index 0000000..63a4750
5254--- /dev/null
5255+++ b/rest-http/README.md
5256 @@ -0,0 +1,5 @@
5257+ # mailpot REST http server
5258+
5259+ ```shell
5260+ cargo run --bin mpot-http
5261+ ```
5262 diff --git a/rest-http/src/main.rs b/rest-http/src/main.rs
5263new file mode 100644
5264index 0000000..471f0ed
5265--- /dev/null
5266+++ b/rest-http/src/main.rs
5267 @@ -0,0 +1,99 @@
5268+ /*
5269+ * This file is part of mailpot
5270+ *
5271+ * Copyright 2020 - Manos Pitsidianakis
5272+ *
5273+ * This program is free software: you can redistribute it and/or modify
5274+ * it under the terms of the GNU Affero General Public License as
5275+ * published by the Free Software Foundation, either version 3 of the
5276+ * License, or (at your option) any later version.
5277+ *
5278+ * This program is distributed in the hope that it will be useful,
5279+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
5280+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5281+ * GNU Affero General Public License for more details.
5282+ *
5283+ * You should have received a copy of the GNU Affero General Public License
5284+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
5285+ */
5286+
5287+ extern crate mailpot;
5288+
5289+ pub use mailpot::config::*;
5290+ pub use mailpot::db::*;
5291+ pub use mailpot::errors::*;
5292+ pub use mailpot::models::*;
5293+ pub use mailpot::*;
5294+
5295+ use warp::Filter;
5296+
5297+ /*
5298+ fn json_body() -> impl Filter<Extract = (String,), Error = warp::Rejection> + Clone {
5299+ // When accepting a body, we want a JSON body
5300+ // (and to reject huge payloads)...
5301+ warp::body::content_length_limit(1024 * 16).and(warp::body::json())
5302+ }
5303+ */
5304+
5305+ #[tokio::main]
5306+ async fn main() {
5307+ // GET /lists/:i64/policy
5308+ let policy = warp::path!("lists" / i64 / "policy").map(|list_pk| {
5309+ let db = Database::open_or_create_db().unwrap();
5310+ db.get_list_policy(list_pk)
5311+ .ok()
5312+ .map(|l| warp::reply::json(&l.unwrap()))
5313+ .unwrap()
5314+ });
5315+
5316+ //get("/lists")]
5317+ let lists = warp::path!("lists").map(|| {
5318+ let db = Database::open_or_create_db().unwrap();
5319+ let lists = db.list_lists().unwrap();
5320+ warp::reply::json(&lists)
5321+ });
5322+
5323+ //get("/lists/<num>")]
5324+ let lists_num = warp::path!("lists" / i64).map(|list_pk| {
5325+ let db = Database::open_or_create_db().unwrap();
5326+ let list = db.get_list(list_pk).unwrap();
5327+ warp::reply::json(&list)
5328+ });
5329+
5330+ //get("/lists/<num>/members")]
5331+ let lists_members = warp::path!("lists" / i64 / "members").map(|list_pk| {
5332+ let db = Database::open_or_create_db().unwrap();
5333+ db.list_members(list_pk)
5334+ .ok()
5335+ .map(|l| warp::reply::json(&l))
5336+ .unwrap()
5337+ });
5338+
5339+ //get("/lists/<num>/owners")]
5340+ let lists_owners = warp::path!("lists" / i64 / "owners").map(|list_pk| {
5341+ let db = Database::open_or_create_db().unwrap();
5342+ db.get_list_owners(list_pk)
5343+ .ok()
5344+ .map(|l| warp::reply::json(&l))
5345+ .unwrap()
5346+ });
5347+
5348+ //post("/lists/<num>/owners/add", data = "<new_owner>")]
5349+ let lists_owner_add =
5350+ warp::post().and(warp::path!("lists" / i64 / "owners" / "add").map(|_list_pk| "todo"));
5351+
5352+ let routes = warp::get().and(
5353+ lists
5354+ .or(policy)
5355+ .or(lists_num)
5356+ .or(lists_members)
5357+ .or(lists_owners)
5358+ .or(lists_owner_add),
5359+ );
5360+
5361+ // Note that composing filters for many routes may increase compile times (because it uses a lot of generics).
5362+ // If you wish to use dynamic dispatch instead and speed up compile times while
5363+ // making it slightly slower at runtime, you can use Filter::boxed().
5364+
5365+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
5366+ }