1 /// 2 module imap.request; 3 4 import imap.socket; 5 import imap.session; 6 import imap.namespace; 7 import imap.defines; 8 import imap.auth; 9 import imap.response; 10 import imap.sil : SILdoc; 11 12 /// Every IMAP command is preceded with a unique string 13 static int tag = 0x1000; 14 15 16 auto imapTry(alias F,Args...)(ref Session session, Args args) 17 { 18 import std.traits : isInstanceOf; 19 import std.conv : to; 20 enum noThrow = true; 21 auto result = F(session,args); 22 alias ResultT = typeof(result); 23 static if (is(ResultT == int)) 24 auto status = result; 25 else static if(is(ResultT == ImapResult)) 26 auto status = result.status; 27 else static if(isInstanceOf!(Result,ResultT)) 28 auto status = result.status; 29 else static if(is(ResultT == ListResponse) || is(ResultT == FlagResult)) 30 auto status = result.status; 31 else static assert(0,"unknown type :" ~ ResultT.stringof); 32 33 static if (is(ResultT == int)) 34 { 35 return result; 36 } 37 else 38 { 39 if (status == ImapStatus.unknown) 40 { 41 if (session.options.recoverAll || session.options.recoverErrors) 42 { 43 session.login(); 44 return F(session,args); 45 } 46 else 47 { 48 if(!noThrow) 49 throw new Exception("unknown result " ~ result.to!string); 50 return result; // ImapResult(ImapStatus.unknown,""); 51 } 52 } 53 else if (status == ImapStatus.bye) 54 { 55 session.closeConnection(); 56 if (session.options.recoverAll) 57 { 58 session.login(); 59 return F(session,args); 60 //return ImapResult(ImapStatus.none,""); 61 } 62 } 63 else 64 { 65 if (!noThrow) 66 throw new Exception("IMAP error : " ~ result.to!string ~ "when calling " ~ __traits(identifier,F) ~ " with args " ~ args.to!string); 67 return result; 68 } 69 } 70 assert(0); 71 } 72 73 bool isLoginRequest(string value) 74 { 75 import std.string : strip, toUpper, startsWith; 76 return value.strip.toUpper.startsWith("LOGIN"); 77 } 78 79 @SILdoc("Sends to server data; a command") 80 int sendRequest(ref Session session, string value) 81 { 82 import std.format : format; 83 import std.exception : enforce; 84 import std.experimental.logger : infof,tracef; 85 import std.stdio; 86 import std.string : endsWith; 87 int n; 88 int t = tag; 89 90 enforce(session.socket,"not connected to server"); 91 92 if (value.isLoginRequest) 93 { 94 if (session.options.debugMode) infof("sending command (%s):\n\n%s\n\n", session.socket, 95 value.length - session.imapLogin.password.length - "\"\"\r\n".length, value); 96 if (session.options.debugMode) tracef("C (%s): %s\n", session.socket, value.length, 97 session.imapLogin.password.length - "\"\"\r\n".length, value); 98 } else { 99 if (session.options.debugMode) 100 { 101 infof("sending command (%s):\n\n%s\n", session.socket, value); 102 tracef("C (%s): %s", session.socket, value); 103 } 104 } 105 106 auto taggedValue = format!"D%04X %s%s"(tag,value, value.endsWith("\n") ? "":"\r\n"); 107 version(Trace) stderr.writefln("sending %s",taggedValue); 108 if (session.socketWrite(taggedValue) == -1) 109 return -1; 110 111 if (tag == 0xFFFF) /* Tag always between 0x1000 and 0xFFFF. */ 112 tag = 0x0FFF; 113 tag++; 114 115 return t; 116 } 117 118 @SILdoc("Sends a response to a command continuation request.") 119 int sendContinuation(ref Session session, string data) 120 { 121 import std.exception : enforce; 122 enforce(session.socket,"not connected to server"); 123 session.socketWrite(data ~ "\r\n"); 124 //socketWrite(session, data ~ "\r\n"); 125 return 1; 126 } 127 128 129 @SILdoc("Reset any inactivity autologout timer on the server") 130 void noop(ref Session session) 131 { 132 auto t = session.sendRequest("NOOP"); 133 session.check!responseGeneric(t); 134 } 135 136 /// 137 auto check(alias F, Args...)(ref Session session, Args args) 138 { 139 import std.exception : enforce; 140 import std.format : format; 141 import std.traits : isInstanceOf; 142 import imap.response : ImapResult; 143 144 auto v = F(session,args); 145 alias ResultT = typeof(v); 146 static if (is(ResultT == ImapStatus)) 147 { 148 auto status = v; 149 } 150 else static if (isInstanceOf!(ResultT,Result) || is(ResultT == ImapResult)) 151 { 152 auto status = v.status; 153 } 154 else 155 { 156 auto status = v; 157 } 158 if (status == ImapStatus.bye) 159 { 160 session.closeConnection(); 161 } 162 enforce(status!= -1 && status != ImapStatus.bye, format!"error calling %s"(__traits(identifier,F))); 163 return v; 164 } 165 166 167 @SILdoc("Connect to the server, login to the IMAP server, get its capabilities, get the namespace of the mailboxes.") 168 ref Session login(ref Session session) 169 { 170 import std.format : format; 171 import std.exception : enforce; 172 import std.experimental.logger : errorf, infof; 173 import std.string : strip; 174 import std.stdio; 175 176 int t; 177 ImapResult res; 178 ImapStatus rl = ImapStatus.unknown; 179 auto login = session.imapLogin; 180 181 scope(failure) 182 closeConnection(session); 183 if (session.socket is null || !session.socket.isAlive()) 184 { 185 infof("login called with dead socket, so trying to reconnect"); 186 session = openConnection(session); 187 if (session.useSSL && !session.options.startTLS) 188 session = openSecureConnection(session); 189 } 190 enforce(session.socket.isAlive(), "not connected to server"); 191 192 auto rg = session.check!responseGreeting(); 193 version(Trace) stderr.writefln("got login first stage: %s",rg); 194 /+ 195 if (session.options.debugMode) 196 { 197 t = session.check!sendRequest("NOOP"); 198 session.check!responseGeneric(t); 199 } 200 +/ 201 t = session.check!sendRequest("CAPABILITY"); 202 version(Trace) stderr.writefln("sent capability request"); 203 res = session.check!responseCapability(t); 204 version(Trace) stderr.writefln("got capabilities: %s",res); 205 206 bool needsStartTLS = (session.sslProtocol != ProtocolSSL.none) && 207 session.capabilities.has(Capability.startTLS) && 208 session.options.startTLS; 209 if (needsStartTLS) 210 { 211 version(Trace) stderr.writeln("sending StartTLS"); 212 t = session.check!sendRequest("STARTTLS"); 213 version(Trace) stderr.writeln("checking for StartTLS response "); 214 res = session.check!responseGeneric(t); 215 // enforce(res.status == ImapStatus.ok, "received bad response: " ~ res.to!string); 216 version(Trace) stderr.writeln("opening secure connection"); 217 session.openSecureConnection(); 218 version(Trace) stderr.writeln("opened secure connection; check capabilties"); 219 t = session.check!sendRequest("CAPABILITY"); 220 version(Trace) stderr.writeln("sent capabilties request"); 221 res = session.check!responseCapability(t); 222 version(Trace) stderr.writeln("got capabilities response"); 223 version(Trace) stderr.writeln(res); 224 } 225 226 if (rg.status == ImapStatus.preAuth) 227 { 228 rl = ImapStatus.preAuth; 229 } 230 231 else 232 { 233 if (session.capabilities.has(Capability.cramMD5) && session.options.cramMD5) 234 { 235 version(Trace) stderr.writefln("cram"); 236 t = session.check!sendRequest("AUTHENTICATE CRAM-MD5"); 237 res = session.check!responseAuthenticate(t); 238 version(Trace) stderr.writefln("authenticate cram first response: %s",res); 239 enforce(res.status == ImapStatus.continue_, "login failure"); 240 auto hash = authCramMD5(login.username,login.password,res.value.strip); 241 stderr.writefln("hhash: %s",hash); 242 t = session.check!sendContinuation(hash); 243 res = session.check!responseGeneric(t); 244 version(Trace) stderr.writefln("response: %s",res); 245 rl = res.status; 246 } 247 if (rl != ImapStatus.ok) 248 { 249 t = session.check!sendRequest(format!"LOGIN \"%s\" \"%s\""(login.username, login.password)); 250 res = session.check!responseGeneric(t); 251 rl = res.status; 252 } 253 if (rl == ImapStatus.no) 254 { 255 auto err = format!"username %s or password rejected at %s\n"(login.username, session.server); 256 errorf("username %s or password rejected at %s\n",login.username, session.server); 257 session.closeConnection(); 258 throw new Exception(err); 259 } 260 } 261 262 t = session.check!sendRequest("CAPABILITY"); 263 res = session.check!responseCapability(t); 264 265 if (session.capabilities.has(Capability.namespace) && session.options.namespace) 266 { 267 t = session.check!sendRequest("NAMESPACE"); 268 res = session.check!responseNamespace(t); 269 rl = res.status; 270 } 271 272 if (session.selected != Mailbox.init) 273 { 274 t = session.check!sendRequest(format!"SELECT \"%s\""(session.selected.applyNamespace())); 275 auto selectResult = session.responseSelect(t); 276 enforce(selectResult.status == ImapStatus.ok); 277 rl = selectResult.status; 278 } 279 280 return session.setStatus(rl); 281 } 282 283 284 @SILdoc("Logout from the IMAP server and disconnect") 285 int logout(ref Session session) 286 { 287 288 if (responseGeneric(session, sendRequest(session, "LOGOUT")).status == ImapStatus.unknown) { 289 //sessionDestroy(session); 290 } else { 291 closeConnection(session); 292 // sessionDestroy(session); 293 } 294 return ImapStatus.ok; 295 } 296 297 @SILdoc("Mailbox status returned by IMAP status command") 298 struct MailboxImapStatus 299 { 300 uint exists; 301 uint recent; 302 uint unseen; 303 uint uidnext; 304 } 305 306 @SILdoc("IMAP examine command for mailbox mbox") 307 auto examine(ref Session session, Mailbox mbox) 308 { 309 import std.format : format; 310 auto request = format!`EXAMINE "%s"`(mbox); 311 auto id = session.sendRequest(request); 312 return session.responseExamine(id); 313 } 314 315 316 @SILdoc("Get mailbox status") 317 auto status(ref Session session, Mailbox mbox) 318 { 319 import std.format : format; 320 import std.exception : enforce; 321 enforce(session.imapProtocol == ImapProtocol.imap4Rev1, "status only implemented for Imap4Rev1 - try using examine"); 322 auto mailbox = mbox.toString(); 323 auto request = format!`STATUS "%s" (MESSAGES RECENT UNSEEN UIDNEXT)`(mailbox); 324 auto id = session.sendRequest(request); 325 return session.responseStatus(id,mailbox); 326 } 327 328 @SILdoc("Open mailbox in read-write mode.") 329 auto select(ref Session session, Mailbox mailbox) 330 { 331 import std.format : format; 332 auto request = format!`SELECT "%s"`(mailbox.toString); 333 auto id = session.sendRequest(request); 334 auto ret = session.responseSelect(id); 335 if (ret.status == ImapStatus.ok) 336 session.selected = mailbox; 337 return ret; 338 } 339 340 341 @SILdoc("Close examined/selected mailbox") 342 ImapResult raw(ref Session session, string command) 343 { 344 auto id = sendRequest(session,command); 345 auto response = responseGeneric(session,id); 346 if (response.status == ImapStatus.ok && session.socket.isAlive) 347 { 348 session.close(); 349 session.selected = Mailbox.init; 350 } 351 return response; 352 } 353 354 @SILdoc("Close examined/selected mailbox") 355 ImapResult close(ref Session session) 356 { 357 enum request = "CLOSE"; 358 auto id = sendRequest(session,request); 359 auto response = responseGeneric(session,id); 360 if (response.status == ImapStatus.ok && session.socket.isAlive) 361 { 362 session.close(); 363 session.selected = Mailbox.init; 364 } 365 return response; 366 } 367 368 @SILdoc("Remove all messages marked for deletion from selected mailbox") 369 ImapResult expunge(ref Session session) 370 { 371 enum request = "EXPUNGE"; 372 auto id = sendRequest(session,request); 373 return session.responseGeneric(id); 374 } 375 376 /// 377 struct MailboxList 378 { 379 string[] mailboxes; 380 string[] folders; 381 } 382 383 @SILdoc(`List available mailboxes: 384 The LIST command returns a subset of names from the complete set 385 of all names available to the client. Zero or more untagged LIST 386 replies are returned, containing the name attributes, hierarchy 387 delimiter, and name. 388 389 The reference and mailbox name arguments are interpreted into a 390 canonical form that represents an unambiguous left-to-right 391 hierarchy. 392 393 Here are some examples of how references and mailbox names might 394 be interpreted on a UNIX-based server: 395 396 Reference Mailbox Name Interpretation 397 ------------ ------------ -------------- 398 ~smith/Mail/ foo.* ~smith/Mail/foo.* 399 archive/ % archive/% 400 #news. comp.mail.* #news.comp.mail.* 401 ~smith/Mail/ /usr/doc/foo /usr/doc/foo 402 archive/ ~fred/Mail/* ~fred/Mail/* 403 404 The first three examples demonstrate interpretations in 405 the context of the reference argument. Note that 406 "~smith/Mail" SHOULD NOT be transformed into something 407 like "/u2/users/smith/Mail", or it would be impossible 408 for the client to determine that the interpretation was 409 in the context of the reference. 410 411 The character "*" is a wildcard, and matches zero or more 412 characters at this position. The character "%" is similar to "*", 413 but it does not match a hierarchy delimiter. If the "%" wildcard 414 is the last character of a mailbox name argument, matching levels 415 of hierarchy are also returned. If these levels of hierarchy are 416 not also selectable mailboxes, they are returned with the 417 \Noselect mailbox name attribute (see the description of the LIST 418 response for more details). 419 420 Params: 421 session - current IMAP session 422 referenceName 423 mailboxName 424 `) 425 auto list(ref Session session, string referenceName="", string mailboxName="*") 426 { 427 import std.format: format; 428 auto request = format!`LIST "%s" "%s"`(referenceName,mailboxName); 429 auto id = session.sendRequest(request); 430 return session.responseList(id); 431 } 432 433 434 @SILdoc("List subscribed mailboxes") 435 auto lsub(ref Session session, string refer, string name) 436 { 437 import std.format : format; 438 auto request = format!`LIST "%s" "%s"`(refer,name); 439 auto id = session.imapTry!sendRequest(request); 440 return session.responseList(id); 441 } 442 443 @SILdoc("Search selected mailbox according to the supplied search criteria") 444 auto search(ref Session session, string criteria, string charset = null) 445 { 446 import std.format : format; 447 string s; 448 449 s = (charset.length > 0) ? format!`UID SEARCH CHARSET "%s" %s`(charset, criteria) : 450 format!`UID SEARCH %s`(criteria); 451 452 auto t = session.imapTry!sendRequest(s); 453 auto r = session.responseSearch(t); 454 return r; 455 } 456 457 enum SearchResultType 458 { 459 min, 460 max, 461 count, 462 all, 463 } 464 465 string toString(SearchResultType[] resultTypes) 466 { 467 import std.string : toUpper, join; 468 import std.format : format; 469 import std.algorithm : map; 470 import std.conv : to; 471 import std.array : array; 472 473 if (resultTypes.length == 0) 474 return null; 475 return format!"RETURN (%s) "(resultTypes.map!(t => t.to!string.toUpper).array.join(" ")); 476 } 477 478 private string createSearchMailboxList(string[] mailboxes, string[] subtrees, bool subtreeOne = false) 479 { 480 import std.array : Appender; 481 import std.algorithm : map; 482 Appender!string ret; 483 import std.format : format; 484 import std.algorithm : map; 485 import std.string : join, strip; 486 auto subtreeTerm = subtreeOne ? "subtree-one" : "subtree"; 487 488 // FIXME = should add "subscribed", "inboxes" and maybe "selected" and "selected-delayed" 489 if (mailboxes.length == 0 && subtrees.length == 0) 490 return `IN ("personal") `; 491 if (mailboxes.length > 0) 492 ret.put(format!"mailboxes %s "(mailboxes.map!(m => format!`"%s"`(m)).join(" "))); 493 if (subtrees.length > 0) 494 ret.put(format!"%s %s "(subtreeTerm, subtrees.map!(t => format!`"%s"`(t)).join(" "))); 495 return format!"IN (%s) "(ret.data.strip); 496 } 497 498 499 @SILdoc("Search selected mailbox according to the supplied search criteria.") 500 auto esearch(ref Session session, string criteria, SearchResultType[] resultTypes=[], string charset = null) 501 { 502 import std.format : format; 503 import std.string : strip; 504 string s; 505 506 s = (charset.length > 0) ? format!`UID SEARCH %sCHARSET "%s" %s`(resultTypes.toString(),charset,criteria) : 507 format!`UID SEARCH %s%s`(resultTypes.toString(),criteria); 508 s = s.strip; 509 import std.stdio; 510 stderr.writeln(s); 511 auto t = session.imapTry!sendRequest(s); 512 auto r = session.responseEsearch(t); 513 return r; 514 } 515 516 @SILdoc("Search selected mailboxes and subtrees according to the supplied search criteria.") 517 auto multiSearch(ref Session session, string criteria, SearchResultType[] resultTypes=[], string[] mailboxes=[], string[] subtrees = [],string charset = null, bool subtreeOne = false) 518 { 519 import std.format : format; 520 import std.string : strip; 521 string s; 522 523 s = (charset.length > 0) ? format!`ESEARCH %s%sCHARSET "%s" %s`( 524 createSearchMailboxList(mailboxes,subtrees,subtreeOne), 525 resultTypes.toString(),charset,criteria) : 526 format!`ESEARCH %s%s%s`( 527 createSearchMailboxList(mailboxes,subtrees,subtreeOne), 528 resultTypes.toString(),criteria); 529 s = s.strip; 530 auto t = session.imapTry!sendRequest(s); 531 auto r = session.responseMultiSearch(t); 532 return r; 533 } 534 535 @SILdoc("Fetch the FLAGS, INTERNALDATE and RFC822.SIZE of the messages") 536 auto fetchFast(ref Session session, string mesg) 537 { 538 import std.format : format; 539 auto t = session.imapTry!sendRequest(format!"UID FETCH %s FAST"(mesg)); 540 auto r = session.responseFetchFast(t); 541 return r; 542 } 543 544 @SILdoc("Fetch the FLAGS of the messages") 545 auto fetchFlags(ref Session session, string mesg) 546 { 547 import std.format : format; 548 auto t = session.imapTry!sendRequest(format!"UID FETCH %s FLAGS"(mesg)); 549 return session.responseFetchFlags(t); 550 } 551 552 @SILdoc("Fetch the INTERNALDATE of the messages") 553 auto fetchDate(ref Session session, string mesg) 554 { 555 import std.format : format; 556 auto request = format!"UID FETCH %s INTERNALDATE"(mesg); 557 auto id = session.imapTry!sendRequest(request); 558 return session.responseFetchDate(id); 559 } 560 561 @SILdoc("Fetch the RFC822.SIZE of the messages") 562 auto fetchSize(ref Session session, string mesg) 563 { 564 import std.format : format; 565 auto request = format!"UID FETCH %s RFC822.SIZE"(mesg); 566 auto id = session.imapTry!sendRequest(request); 567 return session.responseFetchSize(id); 568 } 569 570 @SILdoc("Fetch the BODYSTRUCTURE of the messages") 571 auto fetchStructure(ref Session session, string mesg) 572 { 573 import std.format : format; 574 auto request = format!"UID FETCH %s BODYSTRUCTURE"(mesg); 575 auto id = session.imapTry!sendRequest(request); 576 return session.responseFetchStructure(id); 577 } 578 579 580 @SILdoc("Fetch the BODY[HEADER] of the messages") 581 auto fetchHeader(ref Session session, string mesg) 582 { 583 import std.format : format; 584 585 auto id = session.imapTry!sendRequest(format!`UID FETCH %s BODY.PEEK[HEADER]`(mesg)); 586 auto r = session.responseFetchBody(id); 587 return r; 588 } 589 590 591 @SILdoc("Fetch the text, ie. BODY[TEXT], of the messages") 592 auto fetchRFC822(ref Session session, string mesg) 593 { 594 import std.format : format; 595 596 auto id = session.imapTry!sendRequest(format!`UID FETCH %s RFC822`(mesg)); 597 auto r = session.responseFetchBody(id); 598 return r; 599 } 600 601 @SILdoc("Fetch the text, ie. BODY[TEXT], of the messages") 602 auto fetchText(ref Session session, string mesg) 603 { 604 import std.format : format; 605 606 auto id = session.imapTry!sendRequest(format!`UID FETCH %s BODY.PEEK[TEXT]`(mesg)); 607 auto r = session.responseFetchBody(id); 608 return r; 609 } 610 611 612 @SILdoc("Fetch the specified header fields, ie. BODY[HEADER.FIELDS (<fields>)], of the messages.") 613 auto fetchFields(ref Session session, string mesg, string headerFields) 614 { 615 import std.format : format; 616 617 auto id = session.imapTry!sendRequest(format!`UID FETCH %s BODY.PEEK[HEADER.FIELDS (%s)]`(mesg, headerFields)); 618 auto r = session.responseFetchBody(id); 619 return r; 620 } 621 622 623 @SILdoc("Fetch the specified message part, ie. BODY[<part>], of the messages") 624 auto fetchPart(ref Session session, string mesg, string part) 625 { 626 import std.format : format; 627 628 auto id = session.imapTry!sendRequest(format!`UID FETCH %s BODY.PEEK[%s]`(mesg, part)); 629 auto r = session.responseFetchBody(id); 630 return r; 631 } 632 633 enum StoreMode 634 { 635 replace, 636 add, 637 remove, 638 } 639 640 private string modeString(StoreMode mode) 641 { 642 final switch(mode) with(StoreMode) 643 { 644 case replace : return ""; 645 case add : return "+"; 646 case remove: return "-"; 647 } 648 assert(0); 649 } 650 651 @SILdoc("Add, remove or replace the specified flags of the messages.") 652 auto store(ref Session session, string mesg, StoreMode mode, string flags) 653 { 654 import std.format : format; 655 import std.algorithm: canFind; 656 import std.string : toLower, startsWith; 657 import std.format : format; 658 auto t = session.imapTry!sendRequest(format!"UID STORE %s %sFLAGS.SILENT (%s)"(mesg, mode.modeString,flags)); 659 auto r = session.responseGeneric(t); 660 661 if (canFind(flags,`\Deleted`) && mode != StoreMode.remove && session.options.expunge) 662 { 663 if (session.capabilities.has(Capability.uidPlus)) 664 { 665 t = session.imapTry!sendRequest(format!"UID EXPUNGE %s"(mesg)); 666 session.responseGeneric(t); 667 } 668 else 669 { 670 t = session.imapTry!sendRequest("EXPUNGE"); 671 session.responseGeneric(t); 672 } 673 } 674 return r; 675 } 676 677 678 @SILdoc("Copy the specified messages to another mailbox.") 679 auto copy(ref Session session, string mesg, Mailbox mailbox) 680 { 681 import std.format : format; 682 683 auto t = session.imapTry!sendRequest(format!`UID COPY %s "%s"`(mesg, mailbox.toString)); 684 auto r = session.imapTry!responseGeneric(t); 685 if (r.status == ImapStatus.tryCreate) 686 { 687 t = session.imapTry!sendRequest(format!`CREATE "%s"`(mailbox.toString)); 688 session.imapTry!responseGeneric(t); 689 if (session.options.subscribe) 690 { 691 t = session.imapTry!sendRequest(format!`SUBSCRIBE "%s"`(mailbox.toString)); 692 session.imapTry!responseGeneric(t); 693 } 694 t = session.imapTry!sendRequest(format!`UID COPY %s "%s"`(mesg,mailbox.toString)); 695 r = session.imapTry!responseGeneric(t); 696 } 697 return r; 698 } 699 700 @SILdoc("Move the specified message to another mailbox.") 701 auto move(ref Session session, long uid, string mailbox) 702 { 703 import std.conv : text; 704 return multiMove(session,text(uid),Mailbox(mailbox)); 705 } 706 707 @SILdoc("Move the specified messages to another mailbox.") 708 auto moveUIDs(ref Session session, long[] uids, string mailbox) 709 { 710 import std.conv : text; 711 import std.algorithm: map; 712 import std.array : array; 713 import std.string : join; 714 return multiMove(session,uids.map!(uid => text(uid)).array.join(","),Mailbox(mailbox)); 715 } 716 717 @SILdoc("Move the specified messages to another mailbox.") 718 auto multiMove(ref Session session, string mesg, Mailbox mailbox) 719 { 720 import std.exception : enforce; 721 import std.format : format; 722 import std.conv : to; 723 version(MoveSanity) 724 { 725 auto t = session.imapTry!sendRequest(format!`UID MOVE %s %s`(mesg, mailbox.toString)); 726 auto r = session.imapTry!responseMove(t); 727 if (r.status == ImapStatus.tryCreate) 728 { 729 t = session.imapTry!sendRequest(format!`CREATE "%s"`(mailbox.toString)); 730 session.imapTry!responseGeneric(t); 731 if (session.options.subscribe) 732 { 733 t = session.imapTry!sendRequest(format!`SUBSCRIBE "%s"`(mailbox.toString)); 734 session.imapTry!responseGeneric(t); 735 } 736 t = session.imapTry!sendRequest(format!`UID MOVE %s %s`(mesg,mailbox.toString)); 737 r = session.imapTry!responseMove(t); 738 } 739 enforce(r.status == ImapStatus.ok, "imap error when moving : " ~ r.to!string); 740 return r; 741 } 742 else 743 { 744 auto result = copy(session,mesg,mailbox); 745 enforce(result.status == ImapStatus.ok, format!"unable to copy message %s to %s as first stage of move:%s"(mesg,mailbox,result)); 746 result = store(session,mesg,StoreMode.add,`\Deleted`); 747 // enforce(result.status == ImapStatus.ok, format!"unable to set deleted flags for message %s as second stage of move:%s"(mesg,result)); 748 return result; 749 } 750 assert(0); 751 } 752 753 754 /+ 755 // TODO - finish append function 756 @SILdoc(`Append supplied message to the specified mailbox.`) 757 auto append(ref Session session, Mailbox mbox, string mesg, size_t mesglen, string flags, string date) 758 { 759 auto request = format!`CREATE "%s"`(mailbox); 760 auto id = sendRequest(session,request); 761 return responseGeneric(session,id); 762 763 t = session.imapTry!sendRequest(format!"APPEND \"%s\"%s%s%s%s%s%s {%d}"(m, 764 (flags ? " (" : ""), (flags ? flags : ""), 765 (flags ? ")" : ""), (date ? " \"" : ""), 766 (date ? date : ""), (date ? "\"" : ""))); 767 768 r = session.imapTry!responseContinuation(t); 769 if (r == ImapStatus.continue_) { 770 session.imapTry!sendContinuation(mesg, mesglen); 771 r = imaptry!responseGeneric(t); 772 } 773 774 if (r == ImapStatus.tryCreate) { 775 t = session.imapTry!sendRequest(format!`CREATE "%s"`(m)); 776 r = session.imapTry!responseGeneric(t); 777 if (get_option_boolean("subscribe")) { 778 t = session.imapTry!sendRequest(format!`SUBSCRIBE "%s"`(m)); 779 session.imapTry!responseGeneric(t); 780 } 781 TRY(t = sendRequest(session, "APPEND \"%s\"%s%s%s%s%s%s {%d}", m, 782 (flags ? " (" : ""), (flags ? flags : ""), 783 (flags ? ")" : ""), (date ? " \"" : ""), 784 (date ? date : ""), (date ? "\"" : ""), mesglen)); 785 r = session.imapTry!responseContinuation(t); 786 if (r == ImapStatus.continue_) { 787 TRY(send_continuation(session, mesg, mesglen)); 788 TRY(r = responseGeneric(session, t)); 789 } 790 } 791 792 return r; 793 } 794 +/ 795 796 @SILdoc("Create the specified mailbox") 797 auto create(ref Session session, Mailbox mailbox) 798 { 799 import std.format : format; 800 auto request = format!`CREATE "%s"`(mailbox.toString); 801 auto id = session.sendRequest(request); 802 return session.responseGeneric(id); 803 } 804 805 806 @SILdoc("Delete the specified mailbox") 807 auto delete_(ref Session session, Mailbox mailbox) 808 { 809 import std.format : format; 810 auto request = format!`DELETE "%s"`(mailbox.toString); 811 auto id = session.sendRequest(request); 812 return session.responseGeneric(id); 813 } 814 815 @SILdoc("Rename a mailbox") 816 auto rename(ref Session session, Mailbox oldmbox, Mailbox newmbox) 817 { 818 import std.format : format; 819 auto request = format!`RENAME "%s" "%s"`(oldmbox.toString,newmbox.toString); 820 auto id = session.sendRequest(request); 821 return session.responseGeneric(id); 822 } 823 824 @SILdoc("Subscribe to the specified mailbox") 825 auto subscribe(ref Session session, Mailbox mailbox) 826 { 827 import std.format : format; 828 auto request = format!`SUBSCRIBE "%s"`(mailbox.toString); 829 auto id = session.sendRequest(request); 830 return session.responseGeneric(id); 831 } 832 833 834 @SILdoc("Unsubscribe from the specified mailbox.") 835 auto unsubscribe(ref Session session, Mailbox mailbox) 836 { 837 import std.format : format; 838 auto request = format!`UNSUBSCRIBE"%s"`(mailbox.toString); 839 auto id = session.sendRequest(request); 840 return session.responseGeneric(id); 841 } 842 843 @SILdoc(`IMAP idle command`) 844 auto idle(ref Session session) 845 { 846 import std.stdio; 847 Tag t; 848 ImapResult r, ri; 849 850 if (!session.capabilities.has(Capability.idle)) 851 return ImapResult(ImapStatus.bad,""); 852 853 do 854 { 855 version(Trace) stderr.writefln("inner loop for idle"); 856 t = session.sendRequest("IDLE"); 857 ri = session.responseIdle(t); 858 r = session.responseContinuation(t); 859 version(Trace) stderr.writefln("sendRequest - responseContinuation was %s",r); 860 if (r.status == ImapStatus.continue_) 861 { 862 ri = session.responseIdle(t); 863 version(Trace) stderr.writefln("responseIdle result was %s",ri); 864 session.sendContinuation("DONE"); 865 version(Trace) stderr.writefln("continuation result was %s",ri); 866 r = session.responseGeneric(t); 867 version(Trace) stderr.writefln("reponseGenericresult was %s",r); 868 } 869 } while (ri.status != ImapStatus.untagged); 870 stderr.writefln("returning %s",ri); 871 872 return ri; 873 } 874 875 /// 876 enum SearchField 877 { 878 all, 879 and, 880 or, 881 not, 882 old, 883 answered, 884 deleted, 885 draft, 886 flagged, 887 header, 888 body_, 889 bcc, 890 cc, 891 from, 892 to, 893 subject, 894 text, 895 uid, 896 unanswered, 897 undeleted, 898 undraft, 899 unflagged, 900 unkeyword, 901 unseen, 902 larger, 903 smaller, 904 sentBefore, 905 sentOn, 906 sentSince, 907 keyword, 908 messageNumbers, 909 uidNumbers, 910 resultMin, 911 resultMax, 912 resultAll, 913 resultCount, 914 resultRemoveFrom, 915 resultPartial, 916 sourceMailbox, 917 sourceSubtree, 918 sourceTag, 919 sourceUidValidity, 920 contextCount, 921 context 922 } 923 924 struct SearchParameter 925 { 926 string fieldName; 927 //Variable value; 928 } 929