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 }