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, 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("imap");
135 		scope(exit) handlers.closeModule();
136 		handlers.registerGrammar();
137 		     
138 		static foreach(T; AliasSeq!(MailboxImapStatus, MailboxList,Mailbox,ImapResult,ImapStatus,Result!string,
139 				Status, FlagResult,SearchResult,Status,Session,ProtocolSSL, ImapServer, ImapLogin,
140 				MailboxImapStatus, MailboxList,Mailbox,ImapResult,ImapStatus,Result!string,
141 				Status, FlagResult,SearchResult,Status,StatusResult,BodyResponse,ListResponse,ListEntry,
142 				IncomingEmailMessage,RelayInfo,ToType,EmailMessage,MimePart, Options,
143 				MimeContainer_,MimePart,
144 				MimeAttachment, SearchQuery, UidRange,SearchResultType,StoreMode // proxy from imap not arsd
145 		))
146 			handlers.registerType!T;
147 
148 		handlers.registerType!(Set!Capability)("Capabilities");
149 		handlers.registerType!(Set!ulong)("UidSet");
150 	    handlers.registerType!SysTime;
151 		handlers.registerType!TimeZone;
152 		handlers.registerType!Duration;
153 
154 		handlers.registerHandler!(addSet!ulong)("addUidSet");
155 		handlers.registerHandler!(removeSet!ulong)("removeUidSet");
156 		// FIXME - finish and add append	
157 		static foreach(F; AliasSeq!(noop,login,logout,status,examine,select,close,expunge,list,lsub,
158 					search,fetchFast,fetchFlags,fetchDate,fetchSize, fetchStructure, fetchHeader,
159 					fetchText,fetchFields,fetchPart,logout,store,copy,create, delete_,rename,subscribe,
160 					unsubscribe, idle, openConnection, closeConnection,raw,fetchRFC822,attachments,writeBinaryString,
161 					createQuery,searchQuery,searchQueries, rfcDate,esearch,multiSearch,move,multiMove,moveUIDs,
162 					extractAddress,
163 		))
164 			handlers.registerHandler!F;
165 		handlers.registerType!Socket;
166 
167 		version(UseJmap)
168 		{
169 			import jmap : registerHandlersJmap;
170 			handlers.registerHandlersJmap();
171 		}
172 	}
173 	/+
174 	{
175 		handlers.openModule("imap.impl");
176 		scope(exit) handlers.closeModule();
177 		version(linux)
178 		{
179 			handlers.registerType!termios;
180 			handlers.registerHandler!getTerminalAttributes;
181 			handlers.registerHandler!setTerminalAttributes;
182 			handlers.registerHandler!enableEcho;
183 			handlers.registerHandler!disableEcho;
184 		}
185 
186 		static foreach(T; AliasSeq!( AddressInfo, Socket,ImapServer,ImapLogin,File
187 		))
188 			handlers.registerType!T;
189 		handlers.registerHandler!(add!Capability)("addCapability");
190 		handlers.registerHandler!(remove!Capability)("removeCapability");
191 		handlers.registerHandler!(addSet!Capability)("addCapabilities");
192 		handlers.registerHandler!(removeSet!Capability)("removeCapabilities");
193 
194 		static foreach(F; AliasSeq!(socketRead,socketWrite,
195 						getTerminalAttributes,setTerminalAttributes,enableEcho,disableEcho,
196 						socketSecureRead,socketSecureWrite,closeSecureConnection,openSecureConnection,
197 						isLoginRequest, sendRequest, sendContinuation, 
198 		))
199 			handlers.registerHandler!F;
200 	}
201 	+/
202 	// FIXME - add current tag as SIL vairable - static int tag = 0x1000;
203 
204 	{
205 		handlers.openModule("ssl");
206 		scope(exit) handlers.closeModule();
207 /+		
208 		static foreach(F; AliasSeq!(getPeerCertificate, getCert, checkCert, readX509,
209 					getDigest,getIssuerName,getSubject,asHex,printCert,getSerial,storeCert,
210 					getFilePath,
211 		))
212 		handlers.registerHandler!F; +/
213 		static foreach(T; AliasSeq!(EVP_MD,SSL_))
214 			handlers.registerType!T;
215         handlers.registerType!X509_("X509");
216 	}
217 }
218 
219 struct UidRange
220 {
221 	long start = -1;
222 	long end = -1;
223 
224 	string toString()
225 	{
226 		import std.string: format;
227 		import std.conv : to;
228 
229 		return format!"%s:%s"(
230 				(start == -1 ) ? 0 : start,
231 				(end == -1) ? "*" : end.to!string
232 		);
233 	}
234 }
235 
236 struct SearchQuery
237 {
238 	@SILdoc("not flag if applied inverts the whole query")
239 	@("NOT")
240 	bool not;
241 
242 	ImapFlag[] flags;
243 
244 	@("FROM")
245 	string fromContains;
246 
247 	@("CC")
248 	string ccContains;
249 
250 	@("BCC")
251 	string bccContains;
252 
253 	@("TO")
254 	string toContains;
255 
256 	@("SUBJECT")
257 	string subjectContains;
258 
259 	@("BODY")
260 	string bodyContains;
261 
262 	@("TEXT")
263 	string textContains;
264 
265 	@("BEFORE")
266 	Date beforeDate;
267 
268 	@("HEADER")
269 	string[string] headerFieldContains;
270 
271 	@("KEYWORD")
272 	string[] hasKeyword;
273 
274 	@("SMALLER")
275 	ulong smallerThanBytes;
276 
277 	@("LARGER")
278 	ulong largerThanBytes;
279 	
280 	@("NEW")
281 	bool isNew;
282 
283 	@("OLD")
284 	bool isOld;
285 
286 	@("ON")
287 	Date onDate;
288 
289 	@("SENTBEFORE")
290 	Date sentBefore;
291 
292 	@("SENTON")
293 	Date sentOn;
294 
295 	@("SENTSINCE")
296 	Date sentSince;
297 
298 	@("SINCE")
299 	Date since;
300 
301 	@("UID")
302 	ulong[] uniqueIdentifiers;
303 
304 	@("UID")
305 	UidRange[] uniqueIdentifierRanges;
306 
307 	string applyNot(string s)
308 	{
309 		import std.string : join;
310 
311 		return not ? ("NOT " ~ s) : s;
312 	}
313 
314 	template isSILdoc(alias T)
315 	{
316 		enum isSILdoc = is(typeof(T) == SILdoc);
317 	}
318 
319 	void toString(scope void delegate(const(char)[]) sink)
320 	{
321 		import std.range : dropOne;
322 		import std.string : toUpper;
323 		import std.conv : to;
324 		import std.meta : Filter, templateNot;
325 		import std.traits : isFunction;
326 		foreach(flag;flags)
327 		{
328 			sink(applyNot(flag.to!string.dropOne.toUpper));
329 		}
330 		static foreach(M; Filter!(templateNot!isFunction,__traits(allMembers,typeof(this))))
331 		{{
332 			enum udas = Filter!(templateNot!isSILdoc,__traits(getAttributes, __traits(getMember,this,M)));
333 			static if(udas.length > 0)
334 			{
335 				alias T = typeof( __traits(getMember,this,M));
336 				enum name = udas[0].to!string;
337 				auto v = __traits(getMember,this,M);
338 				static if (is(T==string))
339 				{
340 					if (v.length > 0)
341 					{
342 						sink(applyNot(name));
343 						sink(" \"");
344 						sink(v);
345 						sink("\" ");
346 					}
347 				}
348 				else static if (is(T==Date))
349 				{
350 					if (v != Date.init)
351 					{
352 						sink(applyNot(name));
353 						sink(" ");
354 						sink(__traits(getMember,this,M).rfcDate);
355 						sink(" ");
356 					}
357 				}
358 				else static if (is(T==bool) && (name != "NOT"))
359 				{
360 					if (v)
361 					{
362 						sink(applyNot(name));
363 						sink(" ");
364 					}
365 				}
366 				else static if (is(T==string[string]))
367 				{
368 					foreach(entry;__traits(getMember,this,M).byKeyValue)
369 					{
370 						sink(applyNot(name));
371 						sink(" ");
372 						sink(entry.key);
373 						sink(" \"");
374 						sink(entry.value);
375 						sink("\" ");
376 					}
377 				}
378 				else static if (is(T==string[]))
379 				{
380 					foreach(entry;__traits(getMember,this,M))
381 					{
382 						sink(applyNot(name));
383 						sink(" \"");
384 						sink(entry);
385 						sink("\" ");
386 					}
387 				}
388 				else static if (is(T==ulong[]))
389 				{
390 					if (v.length > 0)
391 					{
392 						sink(applyNot(name));
393 						sink(" ");
394 						auto len = __traits(getMember,this,M).length;
395 						foreach(i,entry;__traits(getMember,this,M))
396 						{
397 							sink(entry.to!string);
398 							if (i != len-1)
399 								sink(",");
400 						}
401 						static if (name == "UID")
402 						{
403 							if (len > 0 && uniqueIdentifierRanges.length > 0)
404 								sink(",");
405 							len = uniqueIdentifierRanges.length;
406 							foreach(i,entry;uniqueIdentifierRanges)
407 							{
408 								sink(entry.to!string);
409 								if (i != len-1)
410 									sink(",");
411 							}
412 						}
413 					}
414 				}
415 			}
416 		}}
417 	}
418 }
419 
420 @SILdoc(`Generate query string to serch the selected mailbox according to the supplied criteria.
421 This string may be passed to imap.search.
422 
423 The searchQueries are ORed together.  There is an implicit AND within a searchQuery
424 For NOT, set not within the query to be true - this applies to all the conditions within
425 the query.
426 `)
427 string createQuery(SearchQuery[] searchQueries)
428 {
429 	import std.range : chain, repeat;
430 	import std.algorithm : map;
431 	import std.string : join, strip;
432 	import std.conv : to;
433 
434 	if (searchQueries.length == 0)
435 		return "ALL";
436 
437 	return chain("OR".repeat(searchQueries.length - 1),
438 				searchQueries.map!(q => q.to!string.strip)).join(" ").strip;
439 }
440 
441 @SILdoc(`Search selected mailbox according to the supplied search criteria.
442 There is an implicit AND within a searchQuery. For NOT, set not within the query
443 to be true - this applies to all the conditions within the query.
444 `)
445 auto searchQuery(ref Session session, string mailbox, SearchQuery searchQuery, string charset = null)
446 {
447 	import imap.namespace : Mailbox;
448 	import imap.request;
449 	select(session,Mailbox(mailbox));
450 	return search(session,createQuery([searchQuery]),charset);
451 }
452 
453 @SILdoc(`Search selected mailbox according to the supplied search criteria.
454 The searchQueries are ORed together.  There is an implicit AND within a searchQuery
455 For NOT, set not within the query to be true - this applies to all the conditions within
456 the query.
457 `)
458 auto searchQueries(ref Session session, string mailbox, SearchQuery[] searchQueries, string charset = null)
459 {
460 	import imap.namespace : Mailbox;
461 	import imap.request;
462 	select(session,Mailbox(mailbox));
463 	return search(session,createQuery(searchQueries),charset);
464 }
465 
466 @SILdoc("Convert a SIL date to an RFC-2822 / IMAP Date string")
467 string rfcDate(Date date)
468 {
469 	import std.format : format;
470 	import std.conv : to;
471 	import std.string : capitalize;
472 	return format!"%02d-%s-%04d"(date.day,date.month.to!string.capitalize,date.year);
473 }
474 
475 @SILdoc("Extract email address from sender/recipient eg Laeeth <laeeth@nospam-laeeth.com>")
476 string extractAddress(string arg)
477 {
478 	import std.string : indexOf;
479 	auto i = arg.indexOf("<");
480 	auto j = arg.indexOf(">");
481 	if  ((i == -1) || (j == -1) || (j <= i))
482 		return "";
483 	return arg[i+1 .. j];
484 }
485 
486