1 ///
2 module kaleidic.sil.plugin.imap.register;
3 
4 version (SIL):
5 
6 import imap.set;
7 import kaleidic.sil.lang.handlers : Handlers;
8 import kaleidic.sil.lang.typing.types : SILdoc;
9 import std.meta : AliasSeq;
10 
11 version (SIL_Plugin) {
12     import kaleidic.sil.lang.plugin : pluginImpl;
13     mixin pluginImpl!registerImap;
14 }
15 
16 import std.socket;
17 
18 import imap.defines;
19 import imap.socket;
20 
21 import arsd.email : MimeContainer;
22 
23 ///
24 void registerImap(ref Handlers handlers) {
25     import std.conv;
26     import imap.session;
27     import imap.request;
28     import imap.response;
29     import imap.namespace;
30     import imap.searchquery;
31     import arsd.email : IncomingEmailMessage, RelayInfo, ToType, EmailMessage, MimePart;
32 
33     // Register for imap.*.
34     {
35         handlers.openModule("imap");
36         scope (exit) handlers.closeModule();
37 
38         static foreach (T; AliasSeq!(BodyResponse, EmailMessage, FlagResult, ImapLogin, ImapResult,
39                                      ImapServer, ImapStatus, IncomingEmailMessage, ListEntry,
40                                      ListResponse, Mailbox, MailboxList, MimeAttachment,
41                                      MimeContainer_, MimePart, Options, ProtocolSSL, RelayInfo,
42                                      Result!string, SearchResult, SearchResultType, Session, Status,
43                                      StatusResult, StoreMode, ToType
44                         )) {
45             handlers.registerType!T;
46         }
47 
48         static foreach (F; AliasSeq!(append, attachments, close, closeConnection, copy, create,
49                                      decodeMimeHeader, delete_, enable, esearch, examine, expunge,
50                                      fetchDate, fetchFast, fetchFields, fetchFlags, fetchHeader,
51                                      fetchPart, fetchRFC822, fetchSize, fetchStructure, fetchText,
52                                      idle, list, login, logout, lsub, move, moveUIDs, multiMove,
53                                      multiSearch, noop, openConnection, raw, rename, select, status,
54                                      store, subscribe, unsubscribe, writeBinaryString
55                         )) {
56             handlers.registerHandler!F;
57         }
58 
59         handlers.registerType!Socket;
60         handlers.registerType!SocketFlags;
61         handlers.registerType!SocketOption;
62         handlers.registerType!SocketOptionLevel;
63         handlers.registerType!SocketShutdown;
64         handlers.registerType!Capability;
65         handlers.registerType!(Set!Capability)("Capabilities");
66         handlers.registerType!(Set!ulong)("UidSet");
67 
68         handlers.registerHandler!(addSet!ulong)("addUidSet");
69         handlers.registerHandler!(removeSet!ulong)("removeUidSet");
70 
71         handlers.registerType!SearchQuery("Query");
72         handlers.registerHandlerOverloads!(
73             ((Session session, SearchQuery query) => session.search(query.to!string)),
74             ((Session session, string str) => session.search(str)),
75         )("search");
76 
77         import jmap : registerHandlersJmap;
78         handlers.registerHandlersJmap();
79     }
80 
81     // Register for imap.query.*.
82     {
83         handlers.openModule("imap.query");
84         scope (exit) handlers.closeModule();
85 
86         auto opDoc = SILdoc(`Compose search query terms with boolean operations. A query expression can be
87 created with 'and', 'or' and 'not', and also with 'andNot' and 'orNot'.
88 
89 Query terms are specific filter criteria such as 'old()' or
90 'from("alice@example.com")'.
91 
92   E.g.,
93   // Equivalent to (flagged OR subject contains "urgent") AND NOT from "gmail.com".
94   query = imap.Query()
95       |> and(flagged())
96       |> or(subject("urgent"))
97       |> andNot(from("gmail.com"))
98 
99 This query can then be passed to 'imap.search()'.
100 
101 When applying an or() operator the passed argument is OR'd with whatever is
102 already in the query.  Queries may be nested to enforce a precedence or to
103 essentially introduce parentheses.
104 
105   E.g.,
106   // Equivalent to NOT flagged AND (seen OR recent) AND from "barry"
107   query = imap.Query()
108       |> not(flagged())
109       |> and(imap.Query() |> or(seen()) |> or(recent()))
110       |> and(from("barry"))
111 
112 NOTE: These operators modify the Query in-place.  Be careful when re-using sub-queries:
113 
114   a = imap.Query(recent())    // 'a' matches 'recent'.
115   b = a |> and(flagged())     // *Both* 'a' and 'b' now match ("recent" AND "flagged").
116 
117 To use these operators and terms as shown above, use:
118 
119   import imap
120   import * from imap.query`);
121 
122         // Boolean ops.
123         handlers.registerHandlerOverloads!(
124             (SearchQuery this_, const(SearchExpr) *expr) => this_.and(expr),
125             (SearchQuery this_, const SearchQuery other) => this_.and(other),
126         )("and", opDoc);
127         handlers.registerHandlerOverloads!(
128             (SearchQuery this_, const(SearchExpr) *expr) => this_.or(expr),
129             (SearchQuery this_, const SearchQuery other) => this_.or(other),
130         )("or", opDoc);
131         handlers.registerHandler!((SearchQuery this_, const(SearchExpr) *expr) => this_.not(expr))("not", opDoc);
132         handlers.registerHandlerOverloads!(
133             (SearchQuery this_, const(SearchExpr) *expr) => this_.andNot(expr),
134             (SearchQuery this_, const SearchQuery other) => this_.andNot(other),
135         )("andNot", opDoc);
136         handlers.registerHandlerOverloads!(
137             (SearchQuery this_, const(SearchExpr) *expr) => this_.orNot(expr),
138             (SearchQuery this_, const SearchQuery other) => this_.orNot(other),
139         )("orNot", opDoc);
140 
141         auto termDoc = SILdoc(`Search terms used to build a query to pass to imap.search().  Also see imap.query
142 functions, e.g., imap.query.and().
143 
144 Flag terms, where the flag is set or unset:
145     answered(), deleted(), draft(), flagged(), new(), old(), recent(), seen(),
146     unanswered(), undeleted(), undraft(), unflagged, unseen(), keyword(str),
147     unkeyword(str).
148 
149 Field terms, where the field contains 'str':
150     bcc(str), body(str), cc(str), from(str), subject(str), text(str), to(str).
151 
152 Date terms, where date may be a dates.Date.
153     before(date), on(date), sentBefore(date), sentOn(date), sendSince(date),
154     since(date).
155 
156 Size terms, where size is the entire message size in bytes.
157     larger(size), smaller(size).`);
158 
159         // Flags.
160         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Answered)))("answered", termDoc);
161         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Deleted)))("deleted", termDoc);
162         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Draft)))("draft", termDoc);
163         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Flagged)))("flagged", termDoc);
164         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.New)))("new", termDoc);
165         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Old)))("old", termDoc);
166         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Recent)))("recent", termDoc);
167         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Seen)))("seen", termDoc);
168         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Unanswered)))("unanswered", termDoc);
169         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Undeleted)))("undeleted", termDoc);
170         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Undraft)))("undraft", termDoc);
171         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Unflagged)))("unflagged", termDoc);
172         handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Unseen)))("unseen", termDoc);
173 
174         // Keyword.
175         handlers.registerHandler!((string keyw) => new const SearchExpr(KeywordTerm(keyw)))("keyword", termDoc);
176         handlers.registerHandler!((string keyw) => new const SearchExpr(KeywordTerm(keyw, true)))("unkeyword", termDoc);
177 
178         // Fields.
179         handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Bcc, str)))("bcc", termDoc);
180         handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Body, str)))("body", termDoc);
181         handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Cc, str)))("cc", termDoc);
182         handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.From, str)))("from", termDoc);
183         handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Subject, str)))("subject", termDoc);
184         handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Text, str)))("text", termDoc);
185         handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.To, str)))("to", termDoc);
186 
187         handlers.registerHandler!((string hdr, string str) => new const SearchExpr(HeaderTerm(hdr, str)))("header", termDoc);
188 
189         // Dates.
190         import std.datetime : Date;
191         handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.Before, date)))("before", termDoc);
192         handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.On, date)))("on", termDoc);
193         handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.SentBefore, date)))("sentBefore", termDoc);
194         handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.SentOn, date)))("sentOn", termDoc);
195         handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.SentSince, date)))("sentSince", termDoc);
196         handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.Since, date)))("since", termDoc);
197 
198         // Sizes.
199         handlers.registerHandler!((int size) => new const SearchExpr(SizeTerm(SizeTerm.Relation.Larger, size)))("larger", termDoc);
200         handlers.registerHandler!((int size) => new const SearchExpr(SizeTerm(SizeTerm.Relation.Smaller, size)))("smaller", termDoc);
201 
202         // UID sequences.
203         // XXX This is tricky.  We'd like some simple syntax to be able to declare them in SIL (I
204         // assume?  Or do we?) but mostly we'd like to use whatever is returned from other APIs
205         // (most likely prior searches).
206     }
207 }
208 
209 void writeBinaryString(string file, string data) {
210     import std.file;
211     write(file, data);
212 }
213 
214 struct MimeContainer_ {
215     MimeContainer h;
216     string contentType() { return h._contentType; }
217     string boundary() { return h.boundary; }
218     string[] headers() { return h.headers; }
219     string content() { return h.content; }
220 
221     MimeContainer_[] stuff() {
222         import std.algorithm : map;
223         import std.array : array;
224         return h.stuff.map!(s => MimeContainer_(s)).array;
225     }
226 
227     this(MimeContainer h) {
228         this.h = h;
229     }
230 }
231 
232 MimeContainer_ accessMimeContainer(MimeContainer mimeContainer) {
233     return MimeContainer_(mimeContainer);
234 }
235