1 ///
2 module kaleidic.sil.plugin.imap.register;
3 import imap.set;
4 version(SIL):
5 
6 import kaleidic.sil.lang.handlers:Handlers;
7 import kaleidic.sil.lang.types : Variable,Function,SILdoc;
8 import std.meta:AliasSeq;
9 
10 version (SIL_Plugin)
11 {
12 	import kaleidic.sil.lang.plugin : pluginImpl;
13 	mixin pluginImpl!registerImap;
14 }
15 
16 
17 import imap.defines;
18 import imap.socket;
19 import imap.session : Session;
20 
21 import core.stdc.stdio;
22 import core.stdc.string;
23 import core.stdc.errno;
24 import std.socket;
25 import core.time : Duration;
26 import std.datetime : Date, DateTime, SysTime, TimeZone;
27 
28 import deimos.openssl.ssl;
29 import deimos.openssl.err;
30 import deimos.openssl.sha;
31 import arsd.email : MimeContainer;
32 
33 ///
34 void registerGrammar(ref Handlers handlers)
35 {
36 	import pegged.grammar;
37 	import imap.grammar;
38 	handlers.registerHandler!parse;
39 	handlers.registerHandler!parseTest;
40 	// handlers.registerType!ParseTree;
41 }
42 
43 enum TestImap=`
44 * 51235 EXISTS
45 * 0 RECENT
46 * FLAGS (\Answered \Flagged \Draft \Deleted \Seen $X-ME-Annot-2 $IsMailingList $IsNotification $HasAttachment $HasTD $IsTrusted Recent $NotJunk $client $kaleidic $Forwarded $has_cal Junk $nina $personal $symmetry $sym/feng $contacts $contacts/mf $research/macro $research NonJunk $Junk)
47 * OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen $X-ME-Annot-2 $IsMailingList $IsNotification $HasAttachment $HasTD $IsTrusted Recent $NotJunk $client $kaleidic $Forwarded $has_cal Junk $nina $personal $symmetry $sym/feng $contacts $contacts/mf $research/macro $research NonJunk $Junk \*)] Ok
48 * OK [UNSEEN 7] Ok
49 * OK [UIDVALIDITY 1484418500] Ok
50 * OK [UIDNEXT 70481] Ok
51 * OK [HIGHESTMODSEQ 10835882] Ok
52 * OK [URLMECH INTERNAL] Ok
53 * OK [ANNOTATIONS 65536] Ok
54 D1004 OK [READ-WRITE] Completed
55 `;
56 
57 auto parseTest()
58 {
59 	import imap.grammar;
60 	import std.stdio;
61 	auto results = Imap(TestImap);
62 	writeln(results);
63 	results = results.tee;
64 	writeln(results);
65 	return results;
66 }
67 
68 
69 auto parse(string arg)
70 {
71 	import imap.grammar;
72 	return Imap(arg).tee;
73 }
74 
75 
76 void writeBinaryString(string file, string data)
77 {
78 	import std.file;
79 	write(file,data);
80 }
81 
82 struct X509_
83 {
84 	import deimos.openssl.x509;
85     X509* handle;
86 }
87 
88 struct MimeContainer_
89 {
90     MimeContainer h;
91     string contentType() { return h._contentType; }
92     string boundary() { return h.boundary; }
93     string[] headers() { return h.headers; }
94     string content() {return h.content; }
95 
96     MimeContainer_[] stuff()
97     {
98         import std.algorithm : map;
99         import std.array : array;
100         return h.stuff.map!(s => MimeContainer_(s)).array;
101     }
102 
103     this(MimeContainer h)
104     {
105         this.h = h;
106     }
107 }
108 
109 MimeContainer_ accessMimeContainer(MimeContainer mimeContainer)
110 {
111     return MimeContainer_(mimeContainer);
112 }
113 
114 ///
115 void registerImap(ref Handlers handlers)
116 {
117 	import imap.session;
118 	import imap.system;
119 	import imap.request;
120 	import imap.response;
121 	import imap.namespace;
122 	import core.sys.linux.termios;
123 	import std.meta : AliasSeq;
124 	import imap.ssl;
125 	import deimos.openssl.ssl;
126 	import deimos.openssl.err;
127 	import deimos.openssl.x509;
128 	import deimos.openssl.pem;
129 	import deimos.openssl.evp;
130 	import std.stdio : File;
131 	import arsd.email : IncomingEmailMessage,RelayInfo,ToType,EmailMessage,MimePart;
132 
133 	{
134 		handlers.openModule("dates");
135 		handlers.registerHandler!add;
136 		handlers.openModule("imap");
137 		scope(exit) handlers.closeModule();
138 		handlers.registerGrammar();
139 		     
140 		static foreach(T; AliasSeq!(MailboxImapStatus, MailboxList,Mailbox,ImapResult,ImapStatus,Result!string,
141 				Status, FlagResult,SearchResult,Status,Session,ProtocolSSL, ImapServer, ImapLogin,
142 				MailboxImapStatus, MailboxList,Mailbox,ImapResult,ImapStatus,Result!string,
143 				Status, FlagResult,SearchResult,Status,StatusResult,BodyResponse,ListResponse,ListEntry,
144 				IncomingEmailMessage,RelayInfo,ToType,EmailMessage,MimePart, Options,
145 				MimeContainer_,MimePart,
146 				MimeAttachment, SearchQuery, UidRange,SearchResultType,StoreMode // proxy from imap not arsd
147 		))
148 			handlers.registerType!T;
149 
150 		handlers.registerType!(Set!Capability)("Capabilities");
151 		handlers.registerType!(Set!ulong)("UidSet");
152 	    handlers.registerType!SysTime;
153 		handlers.registerType!TimeZone;
154 		handlers.registerType!Duration;
155 
156 		handlers.registerHandler!(addSet!ulong)("addUidSet");
157 		handlers.registerHandler!(removeSet!ulong)("removeUidSet");
158 		// FIXME - finish and add append	
159 		static foreach(F; AliasSeq!(noop,login,logout,status,examine,select,close,expunge,list,lsub,
160 					search,fetchFast,fetchFlags,fetchDate,fetchSize, fetchStructure, fetchHeader,
161 					fetchText,fetchFields,fetchPart,logout,store,copy,create, delete_,rename,subscribe,
162 					unsubscribe, idle, openConnection, closeConnection,raw,fetchRFC822,attachments,writeBinaryString,
163 					createQuery,searchQuery,searchQueries, rfcDate,esearch,multiSearch,move,multiMove,moveUIDs,
164 					extractAddress,
165 		))
166 			handlers.registerHandler!F;
167 		handlers.registerType!Socket;
168 
169 		import jmap : registerHandlersJmap;
170 		handlers.registerHandlersJmap();
171 	}
172 	/+
173 	{
174 		handlers.openModule("imap.impl");
175 		scope(exit) handlers.closeModule();
176 		version(linux)
177 		{
178 			handlers.registerType!termios;
179 			handlers.registerHandler!getTerminalAttributes;
180 			handlers.registerHandler!setTerminalAttributes;
181 			handlers.registerHandler!enableEcho;
182 			handlers.registerHandler!disableEcho;
183 		}
184 
185 		static foreach(T; AliasSeq!( AddressInfo, Socket,ImapServer,ImapLogin,File
186 		))
187 			handlers.registerType!T;
188 		handlers.registerHandler!(add!Capability)("addCapability");
189 		handlers.registerHandler!(remove!Capability)("removeCapability");
190 		handlers.registerHandler!(addSet!Capability)("addCapabilities");
191 		handlers.registerHandler!(removeSet!Capability)("removeCapabilities");
192 
193 		static foreach(F; AliasSeq!(socketRead,socketWrite,
194 						getTerminalAttributes,setTerminalAttributes,enableEcho,disableEcho,
195 						socketSecureRead,socketSecureWrite,closeSecureConnection,openSecureConnection,
196 						isLoginRequest, sendRequest, sendContinuation, 
197 		))
198 			handlers.registerHandler!F;
199 	}
200 	+/
201 	// FIXME - add current tag as SIL vairable - static int tag = 0x1000;
202 
203 	{
204 		handlers.openModule("ssl");
205 		scope(exit) handlers.closeModule();
206 /+		
207 		static foreach(F; AliasSeq!(getPeerCertificate, getCert, checkCert, readX509,
208 					getDigest,getIssuerName,getSubject,asHex,printCert,getSerial,storeCert,
209 					getFilePath,
210 		))
211 		handlers.registerHandler!F; +/
212 		static foreach(T; AliasSeq!(EVP_MD,SSL_))
213 			handlers.registerType!T;
214         handlers.registerType!X509_("X509");
215 	}
216 }
217 
218 struct UidRange
219 {
220 	long start = -1;
221 	long end = -1;
222 
223 	string toString()
224 	{
225 		import std.string: format;
226 		import std.conv : to;
227 
228 		return format!"%s:%s"(
229 				(start == -1 ) ? 0 : start,
230 				(end == -1) ? "*" : end.to!string
231 		);
232 	}
233 }
234 
235 struct SearchQuery
236 {
237 	@SILdoc("not flag if applied inverts the whole query")
238 	@("NOT")
239 	bool not;
240 
241 	ImapFlag[] flags;
242 
243 	@("FROM")
244 	string fromContains;
245 
246 	@("CC")
247 	string ccContains;
248 
249 	@("BCC")
250 	string bccContains;
251 
252 	@("TO")
253 	string toContains;
254 
255 	@("SUBJECT")
256 	string subjectContains;
257 
258 	@("BODY")
259 	string bodyContains;
260 
261 	@("TEXT")
262 	string textContains;
263 
264 	@("BEFORE")
265 	Date beforeDate;
266 
267 	@("HEADER")
268 	string[string] headerFieldContains;
269 
270 	@("KEYWORD")
271 	string[] hasKeyword;
272 
273 	@("SMALLER")
274 	ulong smallerThanBytes;
275 
276 	@("LARGER")
277 	ulong largerThanBytes;
278 	
279 	@("NEW")
280 	bool isNew;
281 
282 	@("OLD")
283 	bool isOld;
284 
285 	@("ON")
286 	Date onDate;
287 
288 	@("SENTBEFORE")
289 	Date sentBefore;
290 
291 	@("SENTON")
292 	Date sentOn;
293 
294 	@("SENTSINCE")
295 	Date sentSince;
296 
297 	@("SINCE")
298 	Date since;
299 
300 	@("UID")
301 	ulong[] uniqueIdentifiers;
302 
303 	@("UID")
304 	UidRange[] uniqueIdentifierRanges;
305 
306 	string applyNot(string s)
307 	{
308 		import std.string : join;
309 
310 		return not ? ("NOT " ~ s) : s;
311 	}
312 
313 	template isSILdoc(alias T)
314 	{
315 		enum isSILdoc = is(typeof(T) == SILdoc);
316 	}
317 
318 	void toString(scope void delegate(const(char)[]) sink)
319 	{
320 		import std.range : dropOne;
321 		import std.string : toUpper;
322 		import std.conv : to;
323 		import std.meta : Filter, templateNot;
324 		import std.traits : isFunction;
325 		foreach(flag;flags)
326 		{
327 			sink(applyNot(flag.to!string.dropOne.toUpper));
328 		}
329 		static foreach(M; Filter!(templateNot!isFunction,__traits(allMembers,typeof(this))))
330 		{{
331 			enum udas = Filter!(templateNot!isSILdoc,__traits(getAttributes, __traits(getMember,this,M)));
332 			static if(udas.length > 0)
333 			{
334 				alias T = typeof( __traits(getMember,this,M));
335 				enum name = udas[0].to!string;
336 				auto v = __traits(getMember,this,M);
337 				static if (is(T==string))
338 				{
339 					if (v.length > 0)
340 					{
341 						sink(applyNot(name));
342 						sink(" \"");
343 						sink(v);
344 						sink("\" ");
345 					}
346 				}
347 				else static if (is(T==Date))
348 				{
349 					if (v != Date.init)
350 					{
351 						sink(applyNot(name));
352 						sink(" ");
353 						sink(__traits(getMember,this,M).rfcDate);
354 						sink(" ");
355 					}
356 				}
357 				else static if (is(T==bool) && (name != "NOT"))
358 				{
359 					if (v)
360 					{
361 						sink(applyNot(name));
362 						sink(" ");
363 					}
364 				}
365 				else static if (is(T==string[string]))
366 				{
367 					foreach(entry;__traits(getMember,this,M).byKeyValue)
368 					{
369 						sink(applyNot(name));
370 						sink(" ");
371 						sink(entry.key);
372 						sink(" \"");
373 						sink(entry.value);
374 						sink("\" ");
375 					}
376 				}
377 				else static if (is(T==string[]))
378 				{
379 					foreach(entry;__traits(getMember,this,M))
380 					{
381 						sink(applyNot(name));
382 						sink(" \"");
383 						sink(entry);
384 						sink("\" ");
385 					}
386 				}
387 				else static if (is(T==ulong[]))
388 				{
389 					if (v.length > 0)
390 					{
391 						sink(applyNot(name));
392 						sink(" ");
393 						auto len = __traits(getMember,this,M).length;
394 						foreach(i,entry;__traits(getMember,this,M))
395 						{
396 							sink(entry.to!string);
397 							if (i != len-1)
398 								sink(",");
399 						}
400 						static if (name == "UID")
401 						{
402 							if (len > 0 && uniqueIdentifierRanges.length > 0)
403 								sink(",");
404 							len = uniqueIdentifierRanges.length;
405 							foreach(i,entry;uniqueIdentifierRanges)
406 							{
407 								sink(entry.to!string);
408 								if (i != len-1)
409 									sink(",");
410 							}
411 						}
412 					}
413 				}
414 			}
415 		}}
416 	}
417 }
418 
419 @SILdoc(`Generate query string to serch the selected mailbox according to the supplied criteria.
420 This string may be passed to imap.search.
421 
422 The searchQueries are ORed together.  There is an implicit AND within a searchQuery
423 For NOT, set not within the query to be true - this applies to all the conditions within
424 the query.
425 `)
426 string createQuery(SearchQuery[] searchQueries)
427 {
428 	import std.range : chain, repeat;
429 	import std.algorithm : map;
430 	import std.string : join, strip;
431 	import std.conv : to;
432 
433 	if (searchQueries.length == 0)
434 		return "ALL";
435 
436 	return chain("OR".repeat(searchQueries.length - 1),
437 				searchQueries.map!(q => q.to!string.strip)).join(" ").strip;
438 }
439 
440 @SILdoc(`Search selected mailbox according to the supplied search criteria.
441 There is an implicit AND within a searchQuery. For NOT, set not within the query
442 to be true - this applies to all the conditions within the query.
443 `)
444 auto searchQuery(ref Session session, string mailbox, SearchQuery searchQuery, string charset = null)
445 {
446 	import imap.namespace : Mailbox;
447 	import imap.request;
448 	select(session,Mailbox(mailbox));
449 	return search(session,createQuery([searchQuery]),charset);
450 }
451 
452 @SILdoc(`Search selected mailbox according to the supplied search criteria.
453 The searchQueries are ORed together.  There is an implicit AND within a searchQuery
454 For NOT, set not within the query to be true - this applies to all the conditions within
455 the query.
456 `)
457 auto searchQueries(ref Session session, string mailbox, SearchQuery[] searchQueries, string charset = null)
458 {
459 	import imap.namespace : Mailbox;
460 	import imap.request;
461 	select(session,Mailbox(mailbox));
462 	return search(session,createQuery(searchQueries),charset);
463 }
464 
465 @SILdoc("Convert a SIL date to an RFC-2822 / IMAP Date string")
466 string rfcDate(Date date)
467 {
468 	import std.format : format;
469 	import std.conv : to;
470 	import std.string : capitalize;
471 	return format!"%02d-%s-%04d"(date.day,date.month.to!string.capitalize,date.year);
472 }
473 
474 @SILdoc("Extract email address from sender/recipient eg Laeeth <laeeth@nospam-laeeth.com>")
475 string extractAddress(string arg)
476 {
477 	import std.string : indexOf;
478 	auto i = arg.indexOf("<");
479 	auto j = arg.indexOf(">");
480 	if  ((i == -1) || (j == -1) || (j <= i))
481 		return "";
482 	return arg[i+1 .. j];
483 }
484 
485 Variable add(Variable date, Duration dur)
486 {
487 	return Variable(date.get!DateTime + dur);
488 }