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