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