#include <time.h>
#ifdef LINUX
#include <unistd.h>
#endif

#include "lgi/common/Lgi.h"
#include "lgi/common/Mail.h"
#include "lgi/common/Base64.h"
#include "lgi/common/NetTools.h"
#include "lgi/common/DocView.h"
#include "lgi/common/Http.h"
#include "lgi/common/HttpTools.h"
#include "lgi/common/OpenSSLSocket.h"
#include "lgi/common/Json.h"

#define DEBUG_OAUTH2				1
#ifdef _DEBUG
	#define DEBUG_FETCH				0
#else
	#define DEBUG_FETCH				0
#endif
#define OPT_ImapOAuth2AccessToken	"OAuth2AccessTok"


#undef _FL
#define _FL LGetLeaf(__FILE__), __LINE__

////////////////////////////////////////////////////////////////////////////
#if GPL_COMPATIBLE
#include "AuthNtlm/Ntlm.h"
#else
#include "../src/common/Net/libntlm-0.4.2/ntlm.h"
#endif
#if HAS_LIBGSASL
#include "gsasl.h"
#endif

static const char *sRfc822Header	= "RFC822.HEADER";
static const char *sRfc822Size		= "RFC822.SIZE";

struct TraceLog : public LStream
{
	ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { return 0; }
	ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0)
	{
		LgiTrace("%.*s", (int)Size, Buffer);
		return Size;
	}
};

/*
#define SkipWhiteSpace(s)			while (*s && IsWhiteSpace(*s)) s++;
bool JsonDecode(LXmlTag &t, const char *s)
{
	if (*s != '{')
		return false;
	s++;	
	while (*s)
	{
		SkipWhiteSpace(s);
		if (*s != '\"')
			break;
		LAutoString Variable(LTokStr(s));
		SkipWhiteSpace(s);
		if (*s != ':')
			return false;
		s++;
		SkipWhiteSpace(s);
		LAutoString Value(LTokStr(s));
		SkipWhiteSpace(s);
		
		t.SetAttr(Variable, Value);
		if (*s != ',')
			break;
		s++;
	}
		
	if (*s != '}')
		return false;
	s++;
	return true;
}
*/

#define SkipWhite(s)		while (*s && strchr(WhiteSpace, *s)) s++
#define SkipSpaces(s)		while (*s && strchr(" \t", *s)) s++
#define SkipNonWhite(s)		while (*s && !strchr(WhiteSpace, *s)) s++;
#define ExpectChar(ch)		if (*s != ch) return 0; s++

ssize_t MailIMap::ParseImapResponse(char *Buffer, ssize_t BufferLen, LArray<StrRange> &Ranges, int Names)
{
	Ranges.Length(0);

	if (!*Buffer || *Buffer != '*')
		return 0;

	#ifdef _DEBUG
	char *End = Buffer + BufferLen;
	#endif
	char *s = Buffer + 1;
	char *Start;
	for (int n=0; n<Names; n++)
	{
		SkipSpaces(s);
		Start = s;
		SkipNonWhite(s);
		if (s <= Start) return 0;
		Ranges.New().Set(Start - Buffer, s - Buffer); // The msg seq or UID
	}
	
	// Look for start of block
	SkipSpaces(s);
	if (*s != '(')
	{
		char *Eol = Strnstr(Buffer, "\r\n", BufferLen);
		if (Eol)
		{
			s = Eol + 2;
			return s - Buffer;
		}
		return 0;
	}
	s++;

	// Parse fields
	ssize_t MsgSize = 0;
	while (*s)
	{
		LAssert(s < End);
		
		// Field name
		SkipWhite(s);
		if (*s == ')')
		{
			ExpectChar(')');
			ExpectChar('\r');
			ExpectChar('\n');
			
			MsgSize = s - Buffer;
			break;
		}

		Start = s;
		SkipNonWhite(s);
		if (!*s || s <= Start)
			return 0;
		Ranges.New().Set(Start - Buffer, s - Buffer);

		// Field value
		SkipWhite(s);
		if (*s == '{')
		{
			// Parse multiline
			s++;
			int Size = atoi(s);
			while (*s && *s != '}')
				s++;
			ExpectChar('}');
			ExpectChar('\r');
			ExpectChar('\n');
			Start = s;
			ssize_t CurPos = s - Buffer;
			if (CurPos + Size <= BufferLen)
			{
				s += Size;
				Ranges.New().Set(Start - Buffer, s - Buffer);
			}
			else
				return 0;
		}
		else
		{
			// Parse single
			if (*s == '(')
			{
				s++;
				Start = s;
				
				int Depth = 1;
				while (*s)
				{
					if (*s == '\"')
					{
						s++;
						while (*s && *s != '\"')
						{
							s++;
						}
					}
					if (*s == '(')
						Depth++;
					else if (*s == ')')
					{
						Depth--;
						if (Depth == 0)
							break;
					}
					else if (!*s)
						break;
					s++;
				}			
				
				if (*s != ')')
					return 0;
				Ranges.New().Set(Start - Buffer, s - Buffer);
				s++;
			}
			else
			{
				if (*s == '\'' || *s == '\"')
				{
					//char *Begin = s;
					char Delim = *s++;
					Start = s;
					while (*s && *s != Delim)
						s++;
					if (*s == Delim)
					{
						Ranges.New().Set(Start - Buffer, s - Buffer);
						s++;
					}
					else
					{
						// Parse error;
						return 0;
					}
				}
				else
				{
					Start = s;
					while (*s)
					{
						if (strchr(WhiteSpace, *s) || *s == ')')
							break;

						s++;
					}
					Ranges.New().Set(Start - Buffer, s - Buffer);
				}
			}
		}
	}

	LAssert(s <= End);
	return MsgSize;
}

////////////////////////////////////////////////////////////////////////////
bool MailIMap::Http(LSocketI *S,
					LAutoString *OutHeaders,
					LAutoString *OutBody,
					int *StatusCode,
					const char *InMethod,
					const char *InUri,
					const char *InHeaders,
					const char *InBody)
{
	if (!S || !InUri)
		return false;

	LStringPipe p(256);
	LUri u(InUri);

	p.Print("POST %s HTTP/1.1\r\n"
			"Host: %s\r\n"
			"Content-length: %i\r\n"
			"\r\n%s",
			u.sPath.Get(),
			u.sHost.Get(),
			InBody?strlen(InBody):0,
			InBody?InBody:"");

	LAutoString Req(p.NewStr());
	size_t ReqLen = strlen(Req);

	if (!S->Open(u.sHost, u.Port?u.Port:443))
		return false;

	ssize_t w = S->Write(Req, ReqLen);
	if (w != ReqLen)
		return false;

	char Buf[256];
	LArray<char> Res;
	ssize_t r;
	ssize_t ContentLen = 0;
	ssize_t HdrLen = 0;
	while ((r = S->Read(Buf, sizeof(Buf))) > 0)
	{
		ssize_t Old = Res.Length();
		Res.Length(Old + r);
		memcpy(&Res[Old], Buf, r);

		if (ContentLen)
		{
			if ((ssize_t)Res.Length() >= HdrLen + ContentLen)
				break;
		}
		else
		{
			auto Eoh = Strnstr(&Res[0], "\r\n\r\n", Res.Length());
			if (Eoh)
			{
				HdrLen = Eoh - &Res[0];
				LAutoString c(InetGetHeaderField(&Res[0], "Content-Length", HdrLen));
				if (c)
				{
					ContentLen = atoi(c);
				}
			}
		}
	}

	char *Rp = &Res[0];
	auto Eoh = Strnstr(Rp, "\r\n\r\n", Res.Length());
	if (Eoh)
	{
		if (OutHeaders)
			OutHeaders->Reset(NewStr(Rp, Eoh-Rp));
		if (OutBody)
			OutBody->Reset(NewStr(Eoh + 4, Res.Length() - (Eoh-Rp) - 4));
		if (StatusCode)
		{
			*StatusCode = 0;

			char *Eol = strchr(Rp, '\n');
			if (Eol)
			{
				auto t = LString(Rp, Eol-Rp).SplitDelimit(" \t\r\n");
				if (t.Length() > 2)
					*StatusCode = t[1].Int();
			}
		}
	}
	else return false;

	#ifndef _DEBUG
	LFile f;
	if (f.Open("c:\\temp\\http.html", O_WRITE))
	{
		f.SetSize(0);
		f.Write(&Res[0], Res.Length());
		f.Close();
	}
	#endif

	return true;
}

LAutoString ImapBasicTokenize(char *&s)
{
	if (s)
	{
		while (*s && strchr(WhiteSpace, *s)) s++;
		
		char start = 0, end = 0;
		if (*s == '\'' || *s == '\"') start = end = *s;
		else if (*s == '[') { start = '['; end = ']'; }
		else if (*s == '(') { start = '('; end = ')'; }
		else if (*s == '{') { start = '{'; end = '}'; }

		if (start && end)
		{
			s++;
			char *e = strchr(s, end);
			if (e)
			{
				char *n = NewStr(s, e - s);
				s = e + 1;
				return LAutoString(n);
			}
		}
		else
		{
			char *e = s;
			while (*e && !strchr(WhiteSpace, *e)) e++;
			if (e > s)
			{
				char *n = NewStr(s, e - s);
				s = e + (*e != 0);
				return LAutoString(n);
			}
		}
	}

	s += strlen(s);
	return LAutoString();
}

char *Tok(char *&s)
{
	char *Ret = 0;

	while (*s && strchr(WhiteSpace, *s))
		s++;

	if (*s == '=' || *s == ',')
	{
		Ret = NewStr(s++, 1);
	}
	else if (*s == '\'' || *s == '\"')
	{
		char d = *s++;
		char *e = strchr(s, d);
		if (e)
		{
			Ret = NewStr(s, e - s);
			s = e + 1;
		}
	}
	else if (*s)
	{
		char *e;
		for (e=s; *e && (IsDigit(*e) || IsAlpha(*e) || *e == '-'); e++);
		Ret = NewStr(s, e - s);
		s = e;
	}

	return Ret;
}

char *DecodeImapString(const char *s)
{
	LStringPipe p;
	while (s && *s)
	{
		if (*s == '&')
		{
			char Escape = *s++;
			
			const char *e = s;
			while (*e && *e != '-')
			{
				e++;
			}

			ssize_t Len = e - s;
			if (Len)
			{
				char *Base64 = new char[Len + 4];
				if (Base64)
				{
					memcpy(Base64, s, Len);
					char *n = Base64 + Len;
					for (ssize_t i=Len; i%4; i++)
						*n++ = '=';
					*n++ = 0;

					Len = strlen(Base64);
					ssize_t BinLen = BufferLen_64ToBin(Len);
					uint16 *Bin = new uint16[(BinLen/2)+1];
					if (Bin)
					{
						BinLen = ConvertBase64ToBinary((uchar*)Bin, BinLen, Base64, Len);
						if (BinLen)
						{
							ssize_t Chars = BinLen / 2;
							Bin[Chars] = 0;
							for (int i=0; i<Chars; i++)
							{
								Bin[i] = (Bin[i]>>8) | ((Bin[i]&0xff)<<8);
							}

							char *c8 = WideToUtf8((char16*)Bin, BinLen);
							if (c8)
							{
								p.Push(c8);
								DeleteArray(c8);
							}
						}
						DeleteArray(Bin);
					}
					
					DeleteArray(Base64);
				}
			}
			else
			{
				p.Push(&Escape, 1);
			}

			s = e + 1;
		}
		else
		{
			p.Push(s, 1);
			s++;
		}
	}
	return p.NewStr();
}

char *EncodeImapString(const char *s)
{
	LStringPipe p;
	ssize_t Len = s ? strlen(s) : 0;
	
	while (s && *s)
	{
		int c = LgiUtf8To32((uint8*&)s, Len);

		DoNextChar:
		if ((c >= ' ' && c < 0x80) ||
			c == '\n' ||
			c == '\t' ||
			c == '\r')
		{
			// Literal
			char ch = c;
			p.Push(&ch, 1);
		}
		else
		{
			// Encoded
			LArray<uint16> Str;
			Str[0] = c;
			while ((c = LgiUtf8To32((uint8*&)s, Len)))
			{
				if ((c >= ' ' && c < 0x80) ||
					c == '\n' ||
					c == '\t' ||
					c == '\r')
				{
					break;
				}
				else
				{
					Str[Str.Length()] = c;
				}
			}

			for (uint32 i=0; i<Str.Length(); i++)
			{
				Str[i] = (Str[i]>>8) | ((Str[i]&0xff)<<8);
			}
			ssize_t BinLen = Str.Length() << 1;
			ssize_t BaseLen = BufferLen_BinTo64(BinLen);
			char *Base64 = new char[BaseLen+1];
			if (Base64)
			{
				ssize_t Bytes = ConvertBinaryToBase64(Base64, BaseLen, (uchar*)&Str[0], BinLen);
				while (Bytes > 0 && Base64[Bytes-1] == '=')
				{
					Base64[Bytes-1] = 0;
					Bytes--;
				}
				Base64[Bytes] = 0;

				p.Print("&%s-", Base64);
				DeleteArray(Base64);
			}

			goto DoNextChar;
		}
	}

	return p.NewStr();
}

void ChopNewLine(char *Str)
{
	char *End = Str+strlen(Str)-1;
	if (*End == '\n')
	{
		*End-- = 0;
	}
	if (*End == '\r')
	{
		*End-- = 0;
	}
}

MailImapFolder::MailImapFolder()
{
	Sep = '/';
	Path = 0;
	NoSelect = false;
	NoInferiors = false;
	Marked = false;
	Exists = -1;
	Recent = -1;
	// UnseenIndex = -1;
	Deleted = 0;
}

MailImapFolder::~MailImapFolder()
{
	DeleteArray(Path);
}

void MailImapFolder::operator =(LHashTbl<ConstStrKey<char>,int> &v)
{
	int o = v.Find("exists");
	if (o >= 0) Exists = o;
	o = v.Find("recent");
	if (o >= 0) Recent = o;
}

char *MailImapFolder::GetPath()
{
	return Path;
}

void MailImapFolder::SetPath(const char *s)
{
	char *NewPath = DecodeImapString(s);
	DeleteArray(Path);
	Path = NewPath;
}

char *MailImapFolder::GetName()
{
	if (Path)
	{
		char *s = strrchr(Path, Sep);
		if (s)
		{
			return s + 1;
		}
		else
		{
			return Path;
		}
	}

	return 0;
}

void MailImapFolder::SetName(const char *s)
{
	if (s)
	{
		char Buf[256];
		strcpy_s(Buf, sizeof(Buf), Path?Path:(char*)"");
		DeleteArray(Path);

		char *Last = strrchr(Buf, Sep);
		if (Last)
		{
			Last++;
			strcpy_s(Last, sizeof(Buf)-(Last-Buf), s);
			Path = NewStr(Buf);
		}
		else
		{
			Path = NewStr(s);
		}
	}
}

/////////////////////////////////////////////
class MailIMapPrivate : public LMutex
{
public:
	int NextCmd, IdleCmd;
	bool Logging;
	bool ExpungeOnExit;
	bool ReadDebug;
	char FolderSep;
	char *Current;
	char *Flags;
	LHashTbl<StrKey<char,false>,bool> Capability;
	LString WebLoginUri;
	LViewI *ParentWnd;
	LCancel *Cancel;
	OsThread InCommand;
	LString LastWrite;

	MailIMapPrivate() : LMutex("MailImapSem")
	{
		ParentWnd = NULL;
		FolderSep = '/';
		NextCmd = 1;
		IdleCmd = -1;
		Logging = true;
		ExpungeOnExit = true;
		Current = 0;
		Flags = 0;
		InCommand = 0;
		Cancel = NULL;
		ReadDebug = false;
	}

	~MailIMapPrivate()
	{
		DeleteArray(Current);
		DeleteArray(Flags);
	}
};

MailIMap::MailIMap()
{
	d = new MailIMapPrivate;
	Buffer[0] = 0;
}

MailIMap::~MailIMap()
{
	if (Lock(_FL))
	{
		ClearDialog();
		ClearUid();
		DeleteObj(d);
	}
}

bool MailIMap::Lock(const char *file, int line)
{
	if (!d->Lock(file, line))
		return false;
	return true;
}

bool MailIMap::LockWithTimeout(int Timeout, const char *file, int line)
{
	if (!d->LockWithTimeout(Timeout, file, line))
		return false;
	return true;
}

void MailIMap::Unlock()
{
	d->Unlock();
	d->InCommand = 0;
}

void MailIMap::SetCancel(LCancel *Cancel)
{
	d->Cancel = Cancel;
}

void MailIMap::SetParentWindow(LViewI *wnd)
{
	d->ParentWnd = wnd;
}

const char *MailIMap::GetWebLoginUri()
{
	return d->WebLoginUri;
}

bool MailIMap::IsOnline()
{
	return Socket ? Socket->IsOpen() : false;
}

char MailIMap::GetFolderSep()
{
	return d->FolderSep;
}

char *MailIMap::GetCurrentPath()
{
	return d->Current;
}

bool MailIMap::GetExpungeOnExit()
{
	return d->ExpungeOnExit;
}

void MailIMap::SetExpungeOnExit(bool b)
{
	d->ExpungeOnExit = b; 
}

void MailIMap::ClearUid()
{
	if (Lock(_FL))
	{
		Uid.DeleteArrays();
		Unlock();
	}
}

void MailIMap::ClearDialog()
{
	if (Lock(_FL))
	{
		Dialog.DeleteArrays();
		Unlock();
	}
}

bool MailIMap::WriteBuf(bool ObsurePass, const char *Buffer, bool Continuation)
{
	if (Socket)
	{
		if (!Buffer)
			Buffer = Buf;

		ssize_t Len = strlen(Buffer);
		d->LastWrite = Buffer;
		if (!Continuation && d->InCommand)
		{
			LString Msg;
			Msg.Printf("%s:%i - WriteBuf failed(%s)\n", LGetLeaf(__FILE__), __LINE__, d->LastWrite.Strip().Get());
			Socket->OnInformation(Msg);

			LAssert(!"Can't be issuing new commands while others are still running.");
			return false;
		}
		/*
		else
		{
			LString Msg;
			Msg.Printf("%s:%i - WriteBuf ok(%s)\n", LGetLeaf(__FILE__), __LINE__, d->LastWrite.Strip().Get());
			Socket->OnInformation(Msg);
		}
		*/

		if (Socket->Write((void*)Buffer, Len, 0) == Len)
		{
			if (ObsurePass)
			{
				char *Sp = (char*)strrchr(Buffer, ' ');
				if (Sp)
				{
					Sp++;
					LString s;
					s.Printf("%.*s********\r\n", Sp - Buffer, Buffer);
					Log(s.Get(), LSocketI::SocketMsgSend);
				}
			}
			else Log(Buffer, LSocketI::SocketMsgSend);
			
			d->InCommand = LGetCurrentThread();

			return true;
		}
		// else Log("Failed to write data to socket.", LSocketI::SocketMsgError);
	}
	else Log("Not connected.", LSocketI::SocketMsgError);

	return false;
}

bool MailIMap::Read(LStreamI *Out, int Timeout)
{
	int Lines = 0;

	while (!Lines && Socket)
	{
		ssize_t r = Socket->Read(Buffer, sizeof(Buffer));

		if (Timeout > 0 && Socket->IsOpen() && r <= 0)
		{
			auto St = LCurrentTime();
			auto Rd = Socket->IsReadable(Timeout);
			auto End = LCurrentTime();

			if (Rd)
			{
				r = Socket->Read(Buffer, sizeof(Buffer));
				if (r < 0)
				{
					Socket->Close();
					LgiTrace("%s:%i - Wut? IsReadable/Read mismatch.\n", _FL);
					return false;
				}
				#if 0
				else if (d->ReadDebug)
				{
					LgiTrace("%s:%i - Idle Read '%.*s'\n", _FL, (int)r, Buffer);
				}
				#endif
			}
			else
			{
				if (End - St < Timeout - 20)
					LgiTrace("%s:%i - IsReadable broken (again)\n", _FL);
				return false;
			}
		}


		if (r > 0)
		{
			ReadBuf.Push(Buffer, r);
			while (ReadBuf.Pop(Buffer, sizeof(Buffer)))
			{
				// Trim trailing whitespace
				char *e = Buffer + strlen(Buffer) - 1;
				while (e > Buffer && strchr(WhiteSpace, *e))
					*e-- = 0;

				Lines++;

				if (Out)
				{
					Out->Write(Buffer, strlen(Buffer));
					Out->Write((char*)"\r\n", 2);
				}
				else
				{
					Dialog.Add(NewStr(Buffer));
				}
			}
		}
		else break;
	}

	return Lines > 0;
}

bool MailIMap::IsResponse(const char *Buf, int Cmd, bool &Ok)
{
	char Num[8];
	int Ch = sprintf_s(Num, sizeof(Num), "A%4.4i ", Cmd);
	if (!Buf || _strnicmp(Buf, Num, Ch) != 0)
		return false;

	Ok = _strnicmp(Buf+Ch, "OK", 2) == 0;
	if (!Ok)
		SetError(L_ERROR_GENERIC, "Error: %s", Buf+Ch);
	
	return true;
}

bool MailIMap::ReadResponse(int Cmd, bool Plus)
{
	bool Done = false;
	bool Status = false;
	if (Socket)
	{
		ssize_t Pos = Dialog.Length();
		while (!Done)
		{
			if (Read(NULL))
			{
				for (auto It = Dialog.begin(Pos); !Done && It != Dialog.end(); It++)
				{
					auto Dlg = *It;
					Pos++;
					if (Cmd < 0 || (Plus && *Dlg == '+'))
					{
						Status = Done = true;
					}

					if (IsResponse(Dlg, Cmd, Status))
						Done = true;
					
					if (d->Logging)
					{
						bool Good = strchr("*+", *Dlg) != NULL || Status;
						Log(Dlg, Good ? LSocketI::SocketMsgReceive : LSocketI::SocketMsgError);
					}
				}
			}
			else
			{
				// LgiTrace("%s:%i - 'Read' failed.\n", _FL);
				break;
			}
		}
	}

	return Status;
}

void Hex(char *Out, int OutLen, uchar *In, ssize_t InLen = -1)
{
	if (Out && In)
	{
		if (InLen < 0)
			InLen = strlen((char*)In);

		for (int i=0; i<InLen; i++)
		{
			int ch = sprintf_s(Out, OutLen, "%2.2x", *In++);
			if (ch > 0)
			{
				Out += ch;
				OutLen -= ch;
			}
			else break;
		}
	}
}

void _unpack(void *ptr, int ptrsize, char *b64)
{
	ConvertBase64ToBinary((uchar*) ptr, ptrsize, b64, strlen(b64));
}

bool MailIMap::ReadLine()
{
	int Len = 0;
	Buf[0] = 0;
	do
	{
		ssize_t r = Socket->Read(Buf+Len, sizeof(Buf)-Len);
		if (r < 1)
			return false;
		Len += r;
	}
	while (!stristr(Buf, "\r\n"));

	Log(Buf, LSocketI::SocketMsgReceive);
	return true;
}

#if HAS_LIBGSASL
int GsaslCallback(Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop)
{
	return 0;
}
#endif

class OAuthWebServer : public LThread, public LMutex
{
	bool Loop;
	int Port;
	LSocket Listen;
	LAutoString Req;
	LString Resp;
	bool Finished;

public:
	OAuthWebServer(int DesiredPort = 0) :
		LThread("OAuthWebServerThread"),
		LMutex("OAuthWebServerMutex")
	{
		Loop = false;
		if (Listen.Listen(DesiredPort))
		{
			Port = Listen.GetLocalPort();
			Run();
		}
		else Port = 0;
		Finished = false;
	}
	
	~OAuthWebServer()
	{
		if (Loop)
		{
			Loop = false;
			while (!IsExited())
				LSleep(10);
		}
	}
	
	int GetPort()
	{
		return Port;
	}

	LString GetRequest(LCancel *Loop, uint64 TimeoutMs = 0)
	{
		LString r;
		
		uint64 Start = LCurrentTime();
		while (!r && (!Loop || !Loop->IsCancelled()))
		{
			if (Lock(_FL))
			{
				if (Req)
					r = Req;
				Unlock();
			}
			
			if (TimeoutMs)
			{
				uint64 Now = LCurrentTime();
				if (Now - Start >= TimeoutMs)
					break;
			}

			if (!r)
				LSleep(50);
		}
		
		return r;
	}
	
	void SetResponse(const char *r)
	{
		if (Lock(_FL))
		{
			Resp = r;
			Unlock();
		}
	}
	
	bool IsFinished()
	{
		return Finished;
	}
	
	int Main()
	{
		LAutoPtr<LSocket> s;
		Loop = true;
		while (Loop)
		{
			if (Listen.CanAccept(100))
			{
				s.Reset(new LSocket);
				
				if (!Listen.Accept(s))
					s.Reset();
				else
				{
					LArray<char> Mem;
					ssize_t r;
					char buf[512];
					do 
					{
						r = s->Read(buf, sizeof(buf));
						if (r > 0)
						{
							Mem.Add(buf, r);
							bool End = Strnstr(&Mem[0], "\r\n\r\n", Mem.Length()) != NULL;
							if (End)
								break;
						}	
					}
					while (r > 0);
					
					if (Lock(_FL))
					{
						Mem.Add(0);
						Req.Reset(Mem.Release());
						Unlock();
					}
					
					// Wait for the response...
					LString Response;
					do
					{
						if (Lock(_FL))
						{
							if (Resp)
								Response = Resp;
							Unlock();
						}
						if (!Response)
							LSleep(10);
					}
					while (Loop && !Response);
					
					if (Response)
						s->Write(Response, Response.Length());
					Loop = false;
				}
			}
			else LSleep(10);
		}

		Finished = true;
		return 0;
	}
};

static void AddIfMissing(LArray<LString> &Auths, const char *a, LString *DefaultAuthType = NULL)
{
	for (unsigned i=0; i<Auths.Length(); i++)
	{
		if (!_stricmp(Auths[i], a))
		{
			if (DefaultAuthType) *DefaultAuthType = Auths[i];
			return;
		}
	}
	Auths.Add(a);
	if (DefaultAuthType) *DefaultAuthType = Auths.Last();
}

void MailIMap::CommandFinished()
{
	/*
	LString Msg;
	Msg.Printf("%s:%i - CommandFinished(%s)\n", LGetLeaf(__FILE__), __LINE__, d->LastWrite.Strip().Get());
	Socket->OnInformation(Msg);
	*/

	d->InCommand = 0;
	d->LastWrite.Empty();
}

bool MailIMap::Open(LSocketI *s, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *settingStore, int Flags)
{
	bool Status = false;
	
	if (settingStore)
		SettingStore = settingStore;

	if (SocketLock.Lock(_FL))
	{
		Socket.Reset(s);
		SocketLock.Unlock();
	}
	if (Socket &&
		ValidStr(RemoteHost) &&
		ValidStr(User) &&
		(
			ValidStr(Password)
			||
			OAuth2.IsValid()
		)
		&&
		Lock(_FL))
	{
		// prepare address
		if (Port < 1)
		{
			if (Flags & MAIL_SSL)
				Port = IMAP_SSL_PORT;
			else
				Port = IMAP_PORT;
		}

		char Remote[256];
		strcpy_s(Remote, sizeof(Remote), RemoteHost);
		char *Colon = strchr(Remote, ':');
		if (Colon)
		{
			*Colon++ = 0;
			Port = atoi(Colon);
		}

		// Set SSL mode
		LVariant v;
		if (Flags == MAIL_SSL)
			v = "SSL";
		Socket->SetValue(LSocket_Protocol, v);

		// connect
		if (Socket->Open(Remote, Port))
		{
			bool IMAP4Server = false;
			LArray<LString> Auths;

			// check capability
			int CapCmd = d->NextCmd++;
			sprintf_s(Buf, sizeof(Buf), "A%4.4i CAPABILITY\r\n", CapCmd);
			if (WriteBuf())
			{
				bool Rd = ReadResponse(CapCmd);
				CommandFinished();
				if (Rd)
				{
					for (auto r: Dialog)
					{
						auto T = LString(r).SplitDelimit(" ");
						if (T.Length() > 1 &&
							_stricmp(T[1], "CAPABILITY") == 0)
						{
							for (unsigned i=2; i<T.Length(); i++)
							{
								if (_stricmp(T[i], "IMAP4") == 0 ||
									_stricmp(T[i], "IMAP4rev1") == 0)
								{
									IMAP4Server = true;
								}
								if (_strnicmp(T[i], "AUTH=", 5) == 0)
								{
									char *Type = T[i].Get() + 5;
									AddIfMissing(Auths, Type);
								}

								d->Capability.Add(T[i], true);
							}
						}
					}

					ClearDialog();
				}
				else Log("Read CAPABILITY response failed", LSocketI::SocketMsgError);
			}
			else Log("Write CAPABILITY cmd failed", LSocketI::SocketMsgError);

			if (!IMAP4Server)
				Log("CAPABILITY says not an IMAP4Server", LSocketI::SocketMsgError);
			else
			{
				LString DefaultAuthType;
				bool TryAllAuths =	Auths.Length() == 0
									&&
									!(
										Flags &
										(
											MAIL_USE_PLAIN |
											MAIL_USE_LOGIN |
											MAIL_USE_NTLM | 
											MAIL_USE_OAUTH2
										)
									);

				if (TryAllAuths)
					AddIfMissing(Auths, "DIGEST-MD5");
				if (TestFlag(Flags, MAIL_USE_PLAIN) || TryAllAuths)
					AddIfMissing(Auths, "PLAIN", &DefaultAuthType);
				if (TestFlag(Flags, MAIL_USE_LOGIN) || TryAllAuths)
					AddIfMissing(Auths, "LOGIN", &DefaultAuthType);
				if (TestFlag(Flags, MAIL_USE_NTLM) || TryAllAuths)
					AddIfMissing(Auths, "NTLM", &DefaultAuthType);
				if (TestFlag(Flags, MAIL_USE_OAUTH2) || TryAllAuths)
					AddIfMissing(Auths, "XOAUTH2", &DefaultAuthType);
					
				// Move the default to the start of the list
				for (unsigned n=0; n<Auths.Length(); n++)
				{
					if (Auths[n] == DefaultAuthType && n > 0)
					{
						Auths.DeleteAt(n);
						Auths.AddAt(0, DefaultAuthType);
						break;
					}
				}

				// SSL
				bool TlsError = false;
				if (TestFlag(Flags, MAIL_USE_STARTTLS))
				{
					int CapCmd = d->NextCmd++;
					sprintf_s(Buf, sizeof(Buf), "A%4.4i STARTTLS\r\n", CapCmd);					
					if (WriteBuf())
					{
						bool Rd = ReadResponse(CapCmd);
						CommandFinished();
						if (Rd)
						{
							LVariant v;
							TlsError = !Socket->SetValue(LSocket_Protocol, v="SSL");
						}
						else
						{
							TlsError = true;
						}
					}
					else LAssert(0);
					if (TlsError)
					{
						Log("STARTTLS failed", LSocketI::SocketMsgError);
					}
				}

				// login
				bool LoggedIn = false;
				char AuthTypeStr[256] = "";
				for (unsigned i=0; i<Auths.Length() && !TlsError && !LoggedIn; i++)
				{
					const char *AuthType = Auths[i];

					if (DefaultAuthType != AuthType)
					{
						// put a string of auth types together
						if (strlen(AuthTypeStr) > 0)
						{
							strconcat(AuthTypeStr, ", ");
						}
						strconcat(AuthTypeStr, AuthType);
					}

					// Do auth
					#if HAS_LIBGSASL
					if (!_stricmp(AuthType, "GSSAPI"))
					{
						int AuthCmd = d->NextCmd++;
						sprintf_s(Buf, sizeof(Buf), "A%04.4i AUTHENTICATE GSSAPI\r\n", AuthCmd);
						if (WriteBuf() &&
							ReadLine() &&
							Buf[0] == '+')
						{
							// Start GSSAPI
							Gsasl *ctx = NULL;
							Gsasl_session *sess = NULL;
							int rc = gsasl_init(&ctx);
							if (rc == GSASL_OK)
							{
								char *mechs;
								rc = gsasl_client_mechlist(ctx, &mechs);
  								gsasl_callback_set(ctx, GsaslCallback);
								rc = gsasl_client_start(ctx, AuthType, &sess);
								if (rc != GSASL_OK)
								{
									Log("gsasl_client_start failed", LSocketI::SocketMsgError);
								}

								// gsasl_step(ctx, 
								gsasl_done(ctx);
							}
							else Log("gsasl_init failed", LSocketI::SocketMsgError);
						}						
						else Log("AUTHENTICATE GSSAPI failed", LSocketI::SocketMsgError);
					}
					else
					#endif
					if (_stricmp(AuthType, "LOGIN") == 0 ||
						_stricmp(AuthType, "OTP") == 0)
					{
						// clear text authentication
						int AuthCmd = d->NextCmd++;
						sprintf_s(Buf, sizeof(Buf), "A%4.4i LOGIN %s %s\r\n", AuthCmd, User, Password);
						if (WriteBuf(true))
						{
							LoggedIn = ReadResponse(AuthCmd);
							CommandFinished();
						}
					}
					else if (_stricmp(AuthType, "PLAIN") == 0)
					{
						// plain auth type
						char s[256];
						char *e = s;
						*e++ = 0;
						strcpy_s(e, sizeof(s)-(e-s), User);
						e += strlen(e);
						e++;
						strcpy_s(e, sizeof(s)-(e-s), Password);
						e += strlen(e);
						*e++ = '\r';
						*e++ = '\n';
						ssize_t Len = e - s - 2;

						int AuthCmd = d->NextCmd++;
						sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE PLAIN\r\n", AuthCmd);
						if (WriteBuf())
						{
							if (ReadResponse(AuthCmd, true))
							{
								ssize_t b = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*)s, Len);
								strcpy_s(Buf+b, sizeof(Buf)-b, "\r\n");
								if (WriteBuf(false, NULL, true))
								{
									bool Rd = ReadResponse(AuthCmd);
									CommandFinished();
									if (Rd)
									{
										LoggedIn = true;
									}
									else
									{
										// Look for WEBALERT from Google
										for (auto s: Dialog)
										{
											char *start = strchr(s, '[');
											char *end = start ? strrchr(start, ']') : NULL;
											if (start && end)
											{
												start++;
												if (_strnicmp(start, "WEBALERT", 8) == 0)
												{
													start += 8;
													while (*start && strchr(WhiteSpace, *start))
														start++;
													
													d->WebLoginUri.Set(start, end - start);
												}
											}
										}
									}
								}
							}
						}						
					}
					#if (GPL_COMPATIBLE || defined(_LIBNTLM_H)) && defined(WINNATIVE)
					else if (_stricmp(AuthType, "NTLM") == 0)
					{
						// NT Lan Man authentication
						OSVERSIONINFO ver;
						ZeroObj(ver);
						ver.dwOSVersionInfoSize = sizeof(ver);
						if (!GetVersionEx(&ver))
						{
							DWORD err = GetLastError();
							Log("Couldn't get OS version", LSocketI::SocketMsgError);
						}
						else
						{
							// Username is in the format: User[@Domain]
							char UserDom[256];
							strcpy_s(UserDom, sizeof(UserDom), User);
							char *Domain = strchr(UserDom, '@');
							if (Domain)
								*Domain++ = 0;

							int AuthCmd = d->NextCmd++;
							sprintf_s(Buf, sizeof(Buf), "A%04.4i AUTHENTICATE NTLM\r\n", AuthCmd);
							if (WriteBuf())
							{
								if (ReadResponse(AuthCmd, true))
								{
									tSmbNtlmAuthNegotiate	negotiate;              
									tSmbNtlmAuthChallenge	challenge;
									tSmbNtlmAuthResponse	response;

									buildSmbNtlmAuthNegotiate(&negotiate, 0, 0);
									if (NTLM_VER(&negotiate) == 2)
									{
										negotiate.v2.version.major = (uint8) ver.dwMajorVersion;
										negotiate.v2.version.minor = (uint8) ver.dwMinorVersion;
										negotiate.v2.version.buildNumber = (uint16) ver.dwBuildNumber;
										negotiate.v2.version.ntlmRevisionCurrent = 0x0f;
									}

									ZeroObj(Buf);
									int negotiateLen = SmbLength(&negotiate);
									int c = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*)&negotiate, negotiateLen);
									strcpy_s(Buf+c, sizeof(Buf)-c, "\r\n");
									WriteBuf(false, NULL, true);

									/* read challange data from server, convert from base64 */

									Buf[0] = 0;
									ClearDialog();
									if (ReadResponse())
									{
										/* buffer should contain the string "+ [base 64 data]" */

										#if 1
											ZeroObj(challenge);
											char *Line = Dialog[0];
											LAssert(Line != NULL);
											ChopNewLine(Line);
											int LineLen = strlen(Line);
											int challengeLen = sizeof(challenge);
											c = ConvertBase64ToBinary((uchar*) &challenge, sizeof(challenge), Line+2, LineLen-2);
											if (NTLM_VER(&challenge) == 2)
												challenge.v2.bufIndex = c - (challenge.v2.buffer-(uint8*)&challenge);
											else
												challenge.v1.bufIndex = c - (challenge.v1.buffer-(uint8*)&challenge);

										#endif


										/* prepare response, convert to base64, send to server */
										ZeroObj(response);
										FILETIME time = {0, 0};
										SYSTEMTIME stNow;
										GetSystemTime(&stNow);
										SystemTimeToFileTime(&stNow, &time);

									    char HostName[256] = "";
										gethostname(HostName, sizeof(HostName));

										buildSmbNtlmAuthResponse(&challenge,
																&response,
																UserDom,
																HostName,
																Domain,
																Password,
																(uint8*)&time);
										if (NTLM_VER(&response) == 2)
										{
											response.v2.version.major = (uint8) ver.dwMajorVersion;
											response.v2.version.minor = (uint8) ver.dwMinorVersion;
											response.v2.version.buildNumber = (uint16) ver.dwBuildNumber;
											response.v2.version.ntlmRevisionCurrent = 0x0f;
										}
										
										#if 0
										{
											uint8 *r1 = (uint8*)&response;
											uint8 *r2 = (uint8*)&response_good;
											for (int i=0; i<sizeof(response); i++)
											{
												if (r1[i] != r2[i])
												{
													int asd=0;
												}
											}
										}
										#endif
										
										ZeroObj(Buf);
										c = ConvertBinaryToBase64(Buf, sizeof(Buf), (uchar*) &response, SmbLength(&response));
										strcat(Buf, "\r\n");
										WriteBuf(false, NULL, true);

										/* read line from server, it should be "[seq] OK blah blah blah" */
										LoggedIn = ReadResponse(AuthCmd);
									}
								}

								CommandFinished();
							}
						}

						ClearDialog();
					}
					#endif
					else if (_stricmp(AuthType, "DIGEST-MD5") == 0)
					{
						int AuthCmd = d->NextCmd++;
						sprintf_s(Buf, sizeof(Buf), "A%4.4i AUTHENTICATE DIGEST-MD5\r\n", AuthCmd);
						if (WriteBuf())
						{
							if (ReadResponse(AuthCmd))
							{
								char *TestCnonce = 0;
								
								#if 0
								// Test case
								strcpy(Buf, "+ cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hhcnNldD11dGYtOA==");
								RemoteHost = "elwood.innosoft.com";
								User = "chris";
								Password = "secret";
								TestCnonce = "OA6MHXh6VqTrRk";
								#endif

								char *In = (char*)Buf;
								if (In[0] == '+' && In[1] == ' ')
								{
									In += 2;

									uchar Out[2048];
									ssize_t b = ConvertBase64ToBinary(Out, sizeof(Out), In, strlen(In));
									Out[b] = 0;
									
									LHashTbl<ConstStrKey<char,false>, char*> Map;
									char *s = (char*)Out;
									while (s && *s)
									{
										char *Var = Tok(s);
										char *Eq = Tok(s);
										char *Val = Tok(s);
										char *Comma = Tok(s);

										if (Var && Eq && Val && strcmp(Eq, "=") == 0)
										{
											Map.Add(Var, Val);
											Val = 0;
										}

										DeleteArray(Var);
										DeleteArray(Eq);
										DeleteArray(Val);
										DeleteArray(Comma);
									}

									int32 CnonceI[2] = { (int32)LRand(), (int32)LRand() };
									char Cnonce[32];
									if (TestCnonce)
										strcpy_s(Cnonce, sizeof(Cnonce), TestCnonce);
									else
										Cnonce[ConvertBinaryToBase64(Cnonce, sizeof(Cnonce), (uchar*)&CnonceI, sizeof(CnonceI))] = 0;
									s = strchr(Cnonce, '=');
									if (s) *s = 0;

									int Nc = 1;
									char *Realm = Map.Find("realm");
									char DigestUri[256];
									sprintf_s(DigestUri, sizeof(DigestUri), "imap/%s", Realm ? Realm : RemoteHost);

									LStringPipe p;
									p.Print("username=\"%s\"", User);
									p.Print(",nc=%08.8i", Nc);
									p.Print(",digest-uri=\"%s\"", DigestUri);
									p.Print(",cnonce=\"%s\"", Cnonce);
									char *Nonce = Map.Find("nonce");
									if (Nonce)
									{
										p.Print(",nonce=\"%s\"", Nonce);
									}
									if (Realm)
									{
										p.Print(",realm=\"%s\"", Realm);
									}
									char *Charset = Map.Find("charset");
									if (Charset)
									{
										p.Print(",charset=%s", Charset);
									}
									char *Qop = Map.Find("qop");
									if (Qop)
									{
										p.Print(",qop=%s", Qop);
									}

									// Calculate A1
									char a1[256];
									uchar md5[16];
									sprintf_s(Buf, sizeof(Buf), "%s:%s:%s", User, Realm ? Realm : (char*)"", Password);
									MDStringToDigest((uchar*)a1, Buf);
									char *Authzid = Map.Find("authzid");
									int ch = 16;
									if (Authzid)
										ch += sprintf_s(a1+ch, sizeof(a1)-ch, ":%s:%s:%s", Nonce, Cnonce, Authzid);
									else
										ch += sprintf_s(a1+ch, sizeof(a1)-ch, ":%s:%s", Nonce, Cnonce);
									MDStringToDigest(md5, a1, ch);
									char a1hex[256];
									Hex(a1hex, sizeof(a1hex), (uchar*)md5, sizeof(md5));

									// Calculate 
									char a2[256];
									if (Qop && (_stricmp(Qop, "auth-int") == 0 || _stricmp(Qop, "auth-conf") == 0))
										sprintf_s(a2, sizeof(a2), "AUTHENTICATE:%s:00000000000000000000000000000000", DigestUri);
									else
										sprintf_s(a2, sizeof(a2), "AUTHENTICATE:%s", DigestUri);
									MDStringToDigest(md5, a2);
									char a2hex[256];
									Hex(a2hex, sizeof(a2hex), (uchar*)md5, sizeof(md5));

									// Calculate the final response
									sprintf_s(Buf, sizeof(Buf), "%s:%s:%8.8i:%s:%s:%s", a1hex, Nonce, Nc, Cnonce, Qop, a2hex);
									MDStringToDigest(md5, Buf);
									Hex(Buf, sizeof(Buf), (uchar*)md5, sizeof(md5));
									p.Print(",response=%s", Buf);
									if ((s = p.NewStr()))
									{
										ssize_t Chars = ConvertBinaryToBase64(Buf, sizeof(Buf) - 4, (uchar*)s, strlen(s));
										LAssert(Chars < sizeof(Buf));
										strcpy_s(Buf+Chars, sizeof(Buf)-Chars, "\r\n");
										
										if (WriteBuf(false, NULL, true) && Read())
										{
											for (auto Dlg: Dialog)
											{
												if (Dlg[0] == '+' && Dlg[1] == ' ')
												{
													Log(Dlg, LSocketI::SocketMsgReceive);
													strcpy_s(Buf, sizeof(Buf), "\r\n");
													if (WriteBuf(false, NULL, true))
													{
														LoggedIn = ReadResponse(AuthCmd);
													}
												}
												else
												{
													Log(Dlg, LSocketI::SocketMsgError);
													break;
												}
											}
										}
										
										DeleteArray(s);
									}
								}
							}
							
							CommandFinished();
						}
					}
					else if (!_stricmp(AuthType, "XOAUTH2"))
					{
						if (stristr(RemoteHost, "office365.com"))
						{
							Log("office365.com doesn't support OAUTH2:", LSocketI::SocketMsgInfo);
							Log("\thttps://stackoverflow.com/questions/29747477/imap-auth-in-office-365-using-oauth2", LSocketI::SocketMsgInfo);
							Log("\tSo why does it report support in the CAPABILITY response? Don't ask me - fret", LSocketI::SocketMsgInfo);
							continue;
						}
						else if (!OAuth2.IsValid())
						{
							sprintf_s(Buf, sizeof(Buf), "Error: Unknown OAUTH2 server '%s' (ask fret@memecode.com to add)", RemoteHost);
							Log(Buf, LSocketI::SocketMsgError);
							continue;
						}

						TraceLog TLog;
						LOAuth2 Auth(OAuth2, User, SettingStore, Socket->GetCancel(), &TLog);

						auto AccessToken = Auth.GetAccessToken();
						if (!AccessToken)
						{
							sprintf_s(Buf, sizeof(Buf), "Warning: No OAUTH2 Access Token.");
							#if DEBUG_OAUTH2
							LgiTrace("%s:%i - %s.\n", _FL, Buf);
							#endif
							Log(Buf, LSocketI::SocketMsgWarning);
							break;
						}

						// Construct the XOAUTH2 parameter
						LString s;
						s.Printf("user=%s\001auth=Bearer %s\001\001", User, AccessToken.Get());
						#if DEBUG_OAUTH2
						LgiTrace("%s:%i - s=%s.\n", _FL, s.Replace("\001", "%01").Get());
						#endif
						Base64Str(s);						

						// Issue the IMAP command
						int AuthCmd = d->NextCmd++;
						LString AuthStr;
						AuthStr.Printf("A%4.4i AUTHENTICATE XOAUTH2 %s\r\n", AuthCmd, s.Get());
						if (WriteBuf(false, AuthStr))
						{
							Dialog.DeleteArrays();
							if (Read(NULL))
							{
								for (auto l: Dialog)
								{
									if (*l == '+')
									{
										l++;
										while (*l && strchr(WhiteSpace, *l))
											l++;
										s = l;
										UnBase64Str(s);
										Log(s.Strip(), LSocketI::SocketMsgError);
				
										LJson t(s);
										auto StatusCode = t.Get("status").Int();
										LgiTrace("%s:%i - HTTP status: %" PRIi64 "\n%s\n", _FL, StatusCode, s.Get());

										sprintf_s(Buf, sizeof(Buf), "\r\n");
										WriteBuf(false, NULL, true);
				
										if (StatusCode == 400)
										{
											// Refresh the token...?
											if (Auth.Refresh())
											{
												CommandFinished();
												
												// We need to restart the connection to use the refreshed token
												// Seems we can't just re-try the authentication command.
												return false;
											}
										}
									}
									else if (*l == '*')
									{
										Log(l, LSocketI::SocketMsgReceive);
									}
									else
									{
										if (IsResponse(l, AuthCmd, LoggedIn) &&
											LoggedIn)
										{
											Log(l, LSocketI::SocketMsgReceive);

											if (SettingStore)
											{
												// Login successful, so persist the AuthCode for next time
												LVariant v = AccessToken.Get();
												bool b = SettingStore->SetValue(OPT_ImapOAuth2AccessToken, v);
												if (!b)
												{
													Log("Couldn't store access token.", LSocketI::SocketMsgWarning);
												}
											}
											break;
										}
										else
										{
											Log(l, LSocketI::SocketMsgError);
										}
									}
								}
							}
							CommandFinished();
						}
					}
					else
					{
						char s[256];
						sprintf_s(s, sizeof(s), "Warning: Unsupported authentication type '%s'", AuthType);
						Log(s, LSocketI::SocketMsgWarning);
					}
				}

				if (LoggedIn)
				{
					Status = true;

					// Ask server for it's heirarchy (folder) separator.
					int Cmd = d->NextCmd++;
					sprintf_s(Buf, sizeof(Buf), "A%4.4i LIST \"\" \"\"\r\n", Cmd);
					if (WriteBuf())
					{
						ClearDialog();
						Buf[0] = 0;
						if (ReadResponse(Cmd))
						{
							for (auto Dlg: Dialog)
							{
								LArray<LAutoString> t;
								char *s = Dlg;
								while (*s)
								{
									LAutoString a = ImapBasicTokenize(s);
									if (a)
										t.New() = a;
									else
										break;
								}								

								if (t.Length() >= 5 &&
									strcmp(t[0], "*") == 0 &&
									_stricmp(t[1], "list") == 0)
								{
									for (unsigned i=2; i<t.Length(); i++)
									{
										s = t[i];
										if (strlen(s) == 1)
										{
											d->FolderSep = *s;
											break;
										}
									}
									break;
								}
							}
						}
						CommandFinished();
					}
				}
				else
				{
					SetError(L_ERROR_UNSUPPORTED_AUTH,
							"Authentication failed, types available:\n\t%s",
							ValidStr(AuthTypeStr) ? AuthTypeStr : "(none)");
				}
			}
		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::Close()
{
	bool Status = false;
	if (Socket && Lock(_FL))
	{
		if (d->ExpungeOnExit)
		{
			ExpungeFolder();
		}
		
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i LOGOUT\r\n", Cmd);
		if (WriteBuf())
			Status = true;
		
		CommandFinished();
		
		Socket->Close();
		
		Unlock();
	}

	return Status;
}

bool MailIMap::GetCapabilities(LArray<char*> &s)
{
	// char *k = 0;
	// for (bool p=d->Capability.First(&k); p; p=d->Capability.Next(&k))
	for (auto i : d->Capability)
	{
		s.Add(i.key);
	}

	return s.Length() > 0;
}

bool MailIMap::ServerOption(char *Opt)
{
	return d->Capability.Find(Opt) != 0;
}

char *MailIMap::GetSelectedFolder()
{
	return d->Current;
}

bool MailIMap::SelectFolder(const char *Path, StrMap *Values)
{
	bool Status = false;

	if (!Path)
	{
		LAssert(!"Path is missing.");
		return false;
	}

	if (Socket && Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		char *Enc = EncodePath(Path);
		sprintf_s(Buf, sizeof(Buf), "A%4.4i SELECT \"%s\"\r\n", Cmd, Enc);
		DeleteArray(Enc);
		if (WriteBuf())
		{
			DeleteArray(d->Current);
			ClearDialog();
			if (ReadResponse(Cmd))
			{
				Uid.DeleteArrays();

				if (Values)
				{
					for (LString Dlg: Dialog)
					{
						LString::Array t = Dlg.SplitDelimit(" []");
						if (t.Length() > 0 &&
							t[0].Equals("*"))
						{
							for (unsigned i=1; i<t.Length(); i++)
							{
								//char *var = t[i];

								if (t[i].Equals("exists") ||
									t[i].Equals("recent"))
								{
									//char *val = t[i-1];
									if (t[i-1].IsNumeric())
										Values->Add(t[i], t[i-1]);
								}
								else if (t[i].Equals("unseen"))
								{
									//char *val = t[i+1];
									if (t[i+1].IsNumeric())
										Values->Add(t[i], t[i+1]);
								}
								else if (t[i].Equals("flags"))
								{
									ssize_t s = Dlg.Find("(");
									ssize_t e = Dlg.Find(")", s + 1);
									if (e >= 0)
									{
										LString Val = Dlg(s+1, e);
										Values->Add(t[i], Val);
									}
								}
							}
						}
					}
				}

				Status = true;
				d->Current = NewStr(Path);
				ClearDialog();
			}
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

int MailIMap::GetMessages(const char *Path)
{
	int Status = 0;

	if (!Path)
	{
		LAssert(!"No path.");
		return 0;
	}

	if (Socket && Lock(_FL))
	{
		StrMap f;
		if (SelectFolder(Path, &f))
		{
			LString Exists = f.Find("exists");
			if (Exists && Exists.Int() >= 0)
				Status = (int)Exists.Int();
			else
				LgiTrace("%s:%i - Failed to get 'exists' value.\n", _FL);
		}
		
		Unlock();
	}
	
	return Status;
}

int MailIMap::GetMessages()
{
	return GetMessages("INBOX");
}

char *MailIMap::SequenceToString(LArray<int> *Seq)
{
	if (!Seq)
		return NewStr("1:*");

	LStringPipe p;			
	// int Last = 0;
	for (unsigned s=0; s<Seq->Length(); )
	{
		unsigned e = s;
		while (e<Seq->Length()-1 && (*Seq)[e] == (*Seq)[e+1]-1)
			e++;

		if (s) p.Print(",");
		if (e == s)
			p.Print("%i", (*Seq)[s]);
		else
			p.Print("%i:%i", (*Seq)[s], (*Seq)[e]);

		s = e + 1;			
	}

	return p.NewStr();
}

static void RemoveBytes(LArray<char> &a, ssize_t &Used, ssize_t Bytes)
{
	if (Used >= Bytes)
	{
		ssize_t Remain = Used - Bytes;
		if (Remain > 0)
			memmove(&a[0], &a[Bytes], Remain);
		Used -= Bytes;
	}
	else LAssert(0);
}

static bool PopLine(LArray<char> &a, ssize_t &Used, LAutoString &Line)
{
	for (ssize_t i=0; i<Used; i++)
	{
		if (a[i] == '\n')
		{
			i++;
			Line.Reset(NewStr(&a[0], i));
			RemoveBytes(a, Used, i);
			return true;
		}
	}

	return false;
}


void NullCheck(char *Ptr, unsigned Len)
{
	// Check for NULLs
	for (unsigned i=0; i<Len; i++)
	{
		LAssert(Ptr[i] != 0);
	}
}

extern void DeNullText(char *in, int &len);

int MailIMap::Fetch(bool ByUid,
					const char *Seq,
					const char *Parts,
					FetchCallback Callback,
					void *UserData,
					LStreamI *RawCopy,
					int64 SizeHint,
					LError *Error)
{
	LError Local;
	if (!Error) Error = &Local;
		
	if (!Parts || !Callback || !Seq)
	{
		LgiTrace("%s:%i - Invalid FETCH argument.\n", _FL);
		Error->Set(EINVAL);
		Error->AddNote(_FL, "Invalid arg: %p,%p,%p.", Parts, Callback, Seq);
		return false;
	}

	if (!Lock(_FL))
	{
		LgiTrace("%s:%i - Failed to get lock.\n", _FL);
		Error->Set(ENOLCK);
		Error->AddNote(_FL, "Failed to get lock.");
		return false;
	}
	
	int Status = 0;
	int Cmd = d->NextCmd++;
	LStringPipe p(256);
	p.Print("A%4.4i %sFETCH ", Cmd, ByUid ? "UID " : "");
	p.Write(Seq, strlen(Seq));
	p.Print(" (%s)\r\n", Parts);
	LAutoString WrBuf(p.NewStr());
	if (!WriteBuf(false, WrBuf))
	{
		Error->Set(EIO);
		Error->AddNote(_FL, "Write failed.");
	}
	else
	{
		ClearDialog();

		LArray<char> Buf;
		Buf.Length(1024 + (SizeHint>0?(uint32)SizeHint:0));
		ssize_t Used = 0;
		ssize_t MsgSize;
		// int64 Start = LCurrentTime();
		int64 Bytes = 0;
		bool Done = false;
		
		// uint64 TotalTs = 0;

		bool Blocking = Socket->IsBlocking();
		Socket->IsBlocking(false);

		#if DEBUG_FETCH
		LgiTrace("%s:%i - Fetch: Starting loop\n", _FL);
		#endif

		uint64 LastActivity = LCurrentTime();
		bool Debug = false;
		while (!Done)
		{
			ssize_t r;

			if (!Socket->IsOpen())
			{
				Error->Set(ENOTSOCK);
				Error->AddNote(_FL, "Socket closed.");
				break;
			}

			// We don't wait for 'readable' with select here because
			// a) The socket is in non-blocking mode and
			// b) For OpenSSL connections 'readable' on the socket != can get bytes from 'read'.
			//    Just try the read first and see if it gives you bytes, if not then 'select' on the socket.
			while (true)
			{
				// Extend the buffer if getting used up
				if (Buf.Length()-Used <= 256)
				{
					Buf.Length(Buf.Length() + (64<<10));
					#if DEBUG_FETCH
					LgiTrace("%s:%i - Fetch: Ext buf: %i\n", _FL, Buf.Length());
					#endif
				}

				// Try and read bytes from server.
				r = Socket->Read(Buf.AddressOf(Used), Buf.Length()-Used-1); // -1 for NULL terminator
				#if DEBUG_FETCH
				LgiTrace("%s:%i - Fetch: r=%i, used=%i, buf=%i\n", _FL, r, Used, Buf.Length());
				#endif
				if (r > 0)
				{
					if (RawCopy)
						RawCopy->Write(&Buf[Used], r);

					Used += r;
					Bytes += r;
					
					LastActivity = LCurrentTime();
				}
				else
				{
					LSleep(1); // Don't eat the whole CPU...
					break;
				}
				
				if (Debug)
					LgiTrace("%s:%i - Recv=%i\n", _FL, r);
			}
			
			// See if we can parse out a single response
			LArray<StrRange> Ranges;
			LAssert(Used < Buf.Length());
			Buf[Used] = 0; // NULL terminate before we parse

			while (true)
			{
				MsgSize = ParseImapResponse(&Buf[0], Used, Ranges, 2);
				#if DEBUG_FETCH
				LgiTrace("%s:%i - Fetch: MsgSize=%i\n", _FL, MsgSize);
				#endif
				
				if (Debug)
					LgiTrace("%s:%i - ParseImapResponse=%i\n", _FL, MsgSize);
				
				if (!MsgSize)
					break;

				if (!Debug)
					LastActivity = LCurrentTime();
				
				char *b = &Buf[0];
				if (MsgSize > Used)
				{
					// This is an error... ParseImapResponse should always return <= Used.
					// If this triggers, ParseImapResponse is skipping a NULL that it shouldn't.
					#if DEBUG_FETCH
					LgiTrace("%s:%i - Fetch: Wrong size %i, %i\n", _FL, MsgSize, Used);
					#endif
					Ranges.Length(0);
					LAssert(0);
					#if _DEBUG
					ParseImapResponse(&Buf[0], Used, Ranges, 2);
					#endif
					Done = true;
					
					Error->Set(ENODATA);
					Error->AddNote(_FL, "Wrong data size.");
					break;
				}
				
				LAssert(Ranges.Length() >= 2);
				
				// Setup strings for callback
				char *Param = b + Ranges[0].Start;
				Param[Ranges[0].Len()] = 0;
				char *Name = b + Ranges[1].Start;
				Name[Ranges[1].Len()] = 0;

				if (Stricmp(Name, "FETCH"))
				{
					// Not the response we're looking for.
					#if DEBUG_FETCH
					LgiTrace("%s:%i - Fetch: Wrong response: %s\n", _FL, Name);
					#endif
				}
				else if (Stricmp(Param, "BYE"))
				{
					// Process ranges into a hash table
					StrMap Parts;
					for (unsigned i=2; i<Ranges.Length()-1; i += 2)
					{
						char *Name = b + Ranges[i].Start;
						Name[Ranges[i].Len()] = 0;							
						LString Value(b + Ranges[i+1].Start, Ranges[i+1].Len());
						Parts.Add(Name, Value);
					}
					
					// Call the callback function
					if (Callback(this, atoi(Param), Parts, UserData))
					{
						#if DEBUG_FETCH
						LgiTrace("%s:%i - Fetch: Callback OK\n", _FL);
						#endif
						Status++;
					}
					else
					{
						Error->Set(ECANCELED);
						Error->AddNote(_FL, "Callback failed.");
						#if DEBUG_FETCH
						LgiTrace("%s:%i - Fetch: Callback return FALSE?\n", _FL);
						#endif
					}
				}

				// Remove this msg from buffer
				RemoveBytes(Buf, Used, MsgSize);
				Buf[Used] = 0; // 'Used' changed... so NULL terminate before we parse
			}

			// Look for the end marker
			#if DEBUG_FETCH
			LgiTrace("%s:%i - Fetch: End, Used=%i, Buf=%.12s\n", _FL, Used, Buf.AddressOf());
			#endif
			if (Used > 0 && Buf[0] != '*')
			{
				LAutoString Line;
				
				while (PopLine(Buf, Used, Line))
				{
					#if DEBUG_FETCH
					LgiTrace("%s:%i - Fetch: Line='%s'\n", _FL, Line.Get());
					#endif
					auto t = LString(Line).SplitDelimit(" \r\n");
					if (t.Length() >= 2)
					{
						char *r = t[0];
						if (*r == 'A')
						{
							bool IsOk = !_stricmp(t[1], "Ok");
							int Response = atoi(r + 1);
							Log(Line, IsOk ? LSocketI::SocketMsgReceive : LSocketI::SocketMsgError);
							if (Response == Cmd)
							{
								Done = true;
								break;
							}
						}
						else
						{
							Error->Set(EPROTO);
							Error->AddNote(_FL, "ImapErr: %s", &Buf[0]);
						
							Log(&Buf[0], LSocketI::SocketMsgError);
						}
					}
					else
					{
						// This is normal behaviour... just don't have the end marker yet.
						Done = true;
						break;
					}
				}
			}
		}

		Socket->IsBlocking(Blocking);			
		CommandFinished();
		#if DEBUG_FETCH
		LgiTrace("%s:%i - Fetch finished, status=%i\n", _FL, Status);
		#endif
	}

	Unlock();

	if (!Status && !Error->GetCode())
	{
		Error->Set(ENODATA);
		Error->AddNote(_FL, "No records received.");
	}
	return Status;
}

bool IMapHeadersCallback(MailIMap *Imap, uint32_t Msg, MailIMap::StrMap &Parts, void *UserData)
{
	char *s = Parts.Find(sRfc822Header);
	if (s)
	{
		Parts.Delete(sRfc822Header);

		LAutoString *Hdrs = (LAutoString*)UserData;
		Hdrs->Reset(s);
	}
	
	return true;
}

char *MailIMap::GetHeaders(int Message)
{
	LAutoString Text;
	
	if (Lock(_FL))
	{
		char Seq[64];
		sprintf_s(Seq, sizeof(Seq), "%i", Message + 1);

		Fetch(	false,
				Seq,
				sRfc822Header,
				IMapHeadersCallback,
				&Text,
				NULL);
		
		Unlock();
	}
	
	return Text.Release();
}

struct ReceiveCallbackState
{
	MailTransaction *Trans;
	MailCallbacks *Callbacks;
};

static bool IMapReceiveCallback(MailIMap *Imap, uint32_t Msg, MailIMap::StrMap &Parts, void *UserData)
{
	ReceiveCallbackState *State = (ReceiveCallbackState*) UserData;
	char *Flags = Parts.Find("FLAGS");
	if (Flags)
	{
		State->Trans->Imap.Set(Flags);
	}
	
	char *Hdrs = Parts.Find(sRfc822Header);
	if (Hdrs)
	{
		ssize_t Len = strlen(Hdrs);
		State->Trans->Stream->Write(Hdrs, Len);
	}

	char *Body = Parts.Find("BODY[TEXT]");
	if (Body)
	{
		ssize_t Len = strlen(Body);
		State->Trans->Stream->Write(Body, Len);
	}
	
	State->Trans->Status = Hdrs != NULL || Body != NULL;
	if (Imap->Items)
		Imap->Items->Value++;
	
	Parts.Empty();

	if (State->Callbacks)
	{
		bool Ret = State->Callbacks->OnReceive(State->Trans, State->Callbacks->CallbackData);
		if (!Ret)
			return false;
	}

	return true;
}

bool MailIMap::Receive(LArray<MailTransaction*> &Trans, MailCallbacks *Callbacks)
{
	bool Status = false;

	if (Lock(_FL))
	{
		int Errors = 0;
		ReceiveCallbackState State;
		State.Callbacks = Callbacks;
		
		for (unsigned i=0; i<Trans.Length(); i++)
		{
			State.Trans = Trans[i];			
			State.Trans->Status = false;

			char Seq[64];
			sprintf_s(Seq, sizeof(Seq), "%i", State.Trans->Index + 1);
			
			Fetch
			(
				false,
				Seq,
				"FLAGS RFC822.HEADER BODY[TEXT]",
				IMapReceiveCallback,
				&State,
				NULL
			);

			if (State.Trans->Status)
			{
				Status = true;
			}
			else if (Errors++ > 5)
			{
				// Yeah... not feelin' it
				Status = false;
				break;
			}
		}
		Unlock();
	}

	return Status;
}

bool MailIMap::Append(const char *Folder, ImapMailFlags *Flags, const char *Msg, LString &NewUid)
{
	bool Status = false;

	if (Folder && Msg && Lock(_FL))
	{
		LString Flag;
		if (Flags)
			Flag = Flags->Get();
		LAutoString Path(EncodePath(Folder));

		int Cmd = d->NextCmd++;
		int Len = 0;
		for (const char *m = Msg; *m; m++)
		{
			if (*m == '\n')
			{
				Len += 2;
			}
			else if (*m != '\r')
			{
				Len++;
			}
		}

		// Append on the end of the mailbox
		int c = sprintf_s(Buf, sizeof(Buf), "A%4.4i APPEND \"%s\"", Cmd, Path.Get());
		if (Flag)
			c += sprintf_s(Buf+c, sizeof(Buf)-c, " (%s)", Flag.Get());
		c += sprintf_s(Buf+c, sizeof(Buf)-c, " {%i}\r\n", Len);
		
		if (WriteBuf())
		{
			if (Read())
			{
				bool GotPlus = false;
				for (auto Dlg: Dialog)
				{
					if (Dlg[0] == '+')
					{
    					Dialog.Delete(Dlg);
    					DeleteArray(Dlg);
						GotPlus = true;
						break;
					}
				}
				
				if (GotPlus)
				{
					int Wrote = 0;
					for (const char *m = Msg; *m; )
					{
						while (*m == '\r' || *m == '\n')
						{
							if (*m == '\n')
							{
								Wrote += Socket->Write((char*)"\r\n", 2);
							}
							m++;
						}

						const char *e = m;
						while (*e && *e != '\r' && *e != '\n')
							e++;
						if (e > m)
						{
							Wrote += Socket->Write(m, e-m);
							m = e;
						}
						else break;
					}

					LAssert(Wrote == Len);
					Wrote += Socket->Write((char*)"\r\n", 2);

					// Read response..
					ClearDialog();
					if ((Status = ReadResponse(Cmd)))
					{
						char Tmp[16];
						sprintf_s(Tmp, sizeof(Tmp), "A%4.4i", Cmd);
						for (auto Line: Dialog)
						{
							LAutoString c = ImapBasicTokenize(Line);
							if (!c) break;
							if (!strcmp(Tmp, c))
							{
								LAutoString a;

								while ((a = ImapBasicTokenize(Line)).Get())
								{
									auto t = LString(a).SplitDelimit(" ");
									if (t.Length() > 2 && !_stricmp(t[0], "APPENDUID"))
									{
										NewUid = t[2];
										break;
									}
								}
							}
						}
					}
				}
			}
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::Delete(int Message)
{
	bool Status = false;

	if (Socket && Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i STORE %i FLAGS (\\deleted)\r\n", Cmd, Message+1);
		if (WriteBuf())
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
			CommandFinished();
 		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::Delete(bool ByUid, const char *Seq)
{
	bool Status = false;

	if (Socket && Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i %sSTORE %s FLAGS (\\deleted)\r\n", Cmd, ByUid?"UID ":"", Seq);
		if (WriteBuf())
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
			CommandFinished();
 		}
		
		Unlock();
	}

	return Status;
}

int MailIMap::Sizeof(int Message)
{
	int Status = 0;

	if (Socket && Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i FETCH %i (%s)\r\n", Cmd, Message+1, sRfc822Size);
		if (WriteBuf())
		{
			ClearDialog();
			Buf[0] = 0;
			if (ReadResponse(Cmd))
			{
				char *d = Dialog[0];
				if (d)
				{
					char *t = strstr(d, sRfc822Size);
					if (t)
					{
						t += strlen(sRfc822Size) + 1;
						Status = atoi(t);
					}
				}
			}
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

bool ImapSizeCallback(MailIMap *Imap, uint32_t Msg, MailIMap::StrMap &Parts, void *UserData)
{
	LArray<int> *Sizes = (LArray<int>*) UserData;

	char *Sz = Parts.Find(sRfc822Size);
	if (!Sz)
		return false;

	(*Sizes)[Msg - 1] = atoi(Sz);
	return true;
}

bool MailIMap::GetSizes(LArray<int> &Sizes)
{
	return Fetch(false, "1:*", sRfc822Size, ImapSizeCallback, &Sizes) != 0;	
}

bool MailIMap::GetUid(int Message, char *Id, int IdLen)
{
	bool Status = false;
	
	if (Lock(_FL))
	{
		if (FillUidList())
		{
			char *s = Uid.ItemAt(Message);
			if (s && Id)
			{
				strcpy_s(Id, IdLen, s);
				Status = true;
			}
		}
	
		Unlock();
	}

	return Status;
}

bool MailIMap::FillUidList()
{
	bool Status = false;

	if (Socket && Lock(_FL))
	{
		if (Uid.Length() == 0)
		{
			int Cmd = d->NextCmd++;
			sprintf_s(Buf, sizeof(Buf), "A%4.4i UID SEARCH ALL\r\n", Cmd);
			if (WriteBuf())
			{
				ClearDialog();
				if (ReadResponse(Cmd))
				{
					for (auto d: Dialog)
					{
						auto T = LString(d).SplitDelimit(" ");
						if (T[1] && strcmp(T[1], "SEARCH") == 0)
						{
							for (unsigned i=2; i<T.Length(); i++)
								Uid.Insert(NewStr(T[i]));
						}
					}

					Status = true;
				}
				CommandFinished();
			}
		}
		else
		{
			Status = true;
		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::GetUidList(List<char> &Id)
{
	bool Status = false;

	if (Lock(_FL))
	{
		if (FillUidList())
		{
			for (auto s: Uid)
			{
				Id.Insert(NewStr(s));
			}

			Status = true;
		}
		Unlock();
	}

	return Status;
}

bool MailIMap::GetFolders(LArray<MailImapFolder*> &Folders)
{
	bool Status = false;

	if (Socket && Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i LIST \"\" \"*\"\r\n", Cmd);
		if (WriteBuf())
		{
			ClearDialog();
			Buf[0] = 0;
			if (ReadResponse(Cmd))
			{
				char Sep[] = { GetFolderSep(), 0 };

				for (auto d: Dialog)
				{
					LArray<LAutoString> t;
					char *s;
					while ((s = LTokStr((const char*&)d)))
					{
						t[t.Length()].Reset(s);
					}
					
					if (t.Length() >= 5)
					{
						if (strcmp(t[0], "*") == 0 &&
							_stricmp(t[1], "LIST") == 0)
						{
							char *Folder = t[t.Length()-1];

							MailImapFolder *f = new MailImapFolder();
							if (f)
							{
								Folders.Add(f);
								f->Sep = Sep[0];

								// Check flags
								f->NoSelect = stristr(t[2], "NoSelect") != 0;
								f->NoInferiors = stristr(t[2], "NoInferiors") != 0;

								// LgiTrace("Imap folder '%s' %s\n", Folder, t[2].Get());

								// Alloc name
								if (Folder[0] == '\"')
								{
									char *p = TrimStr(Folder, "\"");
									f->Path = DecodeImapString(p);
									DeleteArray(p);
								}
								else
								{
									f->Path = DecodeImapString(Folder);
								}
							}
						}						
					}
				}

				Status = true;
				ClearDialog();
			}
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::CreateFolder(MailImapFolder *f)
{
	bool Status = false;
	// char Dir[2] = { d->FolderSep, 0 };

	if (f && f->GetPath() && Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		char *Enc = EncodePath(f->GetPath());
		sprintf_s(Buf, sizeof(Buf), "A%4.4i CREATE \"%s\"\r\n", Cmd, Enc);
		DeleteArray(Enc);
		if (WriteBuf())
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
			if (Status)
			{
				char *End = f->Path + strlen(f->Path) - 1;
				if (*End == GetFolderSep())
				{
					f->NoSelect = true;
					*End = 0;
				}
				else
				{
					f->NoInferiors = true;
				}
			}
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

char *MailIMap::EncodePath(const char *Path)
{
	if (!Path)
		return 0;

	char Sep = GetFolderSep();
	char Native[MAX_PATH], *o = Native, *e = Native + sizeof(Native) - 1;
	for (const char *i =
		Path[0] == '/' && Path[1] ?
		Path + 1 :
		Path;
		*i && o < e;
		i++)
	{
		if (*i == '/')
			*o++ = Sep;
		else
			*o++ = *i;
	}
	*o++ = 0;

	return EncodeImapString(Native);
}

bool MailIMap::DeleteFolder(const char *Path)
{
	bool Status = false;

	if (Path && Lock(_FL))
	{
		// Close the current folder if required.
		if (d->Current &&
			_stricmp(Path, d->Current) == 0)
		{
			int Cmd = d->NextCmd++;
			sprintf_s(Buf, sizeof(Buf), "A%4.4i CLOSE\r\n", Cmd);
			if (WriteBuf())
			{
				ClearDialog();
				ReadResponse(Cmd);
				DeleteArray(d->Current);
				CommandFinished();
			}
		}

		// Delete the folder
		int Cmd = d->NextCmd++;
		char *NativePath = EncodePath(Path);
		sprintf_s(Buf, sizeof(Buf), "A%4.4i DELETE \"%s\"\r\n", Cmd, NativePath);
		DeleteArray(NativePath);
		if (WriteBuf())
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::RenameFolder(const char *From, const char *To)
{
	bool Status = false;

	if (From && To && Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		LAutoString f(EncodePath(From));
		LAutoString t(EncodePath(To));
		sprintf_s(Buf, sizeof(Buf), "A%4.4i RENAME \"%s\" \"%s\"\r\n", Cmd, f.Get(), t.Get());
		if (WriteBuf())
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::SetFolderFlags(MailImapFolder *f)
{
	bool Status = false;

	if (f && Lock(_FL))
	{
		/*
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i RENAME \"%s\" \"%s\"\r\n", Cmd);
		if (WriteBuf())
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
		}
		*/
		
		Unlock();
	}

	return Status;
}

bool MailIMap::SetFlagsByUid(LArray<uint32_t> &Uids, const char *Flags)
{
	bool Status = false;

	if (Lock(_FL))
	{
		int Cmd = d->NextCmd++;

		LStringPipe p;
		p.Print("A%04.4i UID STORE ", Cmd);
		if (Uids.Length())
		{
			for (unsigned i=0; i<Uids.Length(); i++)
			{
				p.Print("%s%i", i?",":"", Uids[i]);
			}
		}
		else
			p.Print("1:*");
		p.Print(" FLAGS (%s)\r\n", Flags?Flags:"");

		auto Buffer = p.NewGStr();
		if (Buffer && WriteBuf(false, Buffer))
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
			CommandFinished();
		}

		Unlock();
	}

	return Status;
}

bool MailIMap::CopyByUid(LArray<uint32_t> &InUids, const char *DestFolder)
{
	bool Status = false;

	if (Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		LAutoString Dest(EncodePath(DestFolder));

		LStringPipe p(1024);
		p.Print("A%04.4i UID COPY ", Cmd);
		for (unsigned i=0; i<InUids.Length(); i++)
		{
			if (i) p.Write((char*)",", 1);
			p.Print("%i", InUids[i]);
		}
		p.Print(" \"%s\"\r\n", Dest.Get());

		LAutoString Buffer(p.NewStr());
		if (Buffer && WriteBuf(false, Buffer))
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
			CommandFinished();
		}

		Unlock();
	}

	return Status;
}

bool MailIMap::ExpungeFolder()
{
	bool Status = false;

	if (Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i EXPUNGE\r\n", Cmd);

		if (WriteBuf())
		{
			ClearDialog();
			Status = ReadResponse(Cmd);
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::Search(bool Uids, LArray<LString> &SeqNumbers, const char *Filter)
{
	bool Status = false;

	if (ValidStr(Filter) && Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i %sSEARCH %s\r\n", Cmd, Uids?"UID ":"", Filter);
		if (WriteBuf())
		{
			ClearDialog();
			if (ReadResponse(Cmd))
			{
				for (auto d: Dialog)
				{
					if (*d != '*')
						continue;

					d++;
					LAutoString s(Tok(d));
					if (!s || _stricmp(s, "Search"))
						continue;

					while (s.Reset(Tok(d)))
					{
						SeqNumbers.New() = s.Get();
						Status = true;
					}
				}
			}
			CommandFinished();
		}

		Unlock();
	}

	return Status;
}

bool MailIMap::Status(char *Path, int *Recent)
{
	bool Status = false;

	if (Path && Recent && Lock(_FL))
	{
		LAutoString Dest(EncodePath(Path));

		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i STATUS %s (RECENT)\r\n", Cmd, Dest.Get());
		if (WriteBuf())
		{
			ClearDialog();
			if (ReadResponse(Cmd))
			{
				for (auto d: Dialog)
				{
					if (*d != '*')
						continue;

					d++;
					LAutoString Cmd = ImapBasicTokenize(d);
					LAutoString Folder = ImapBasicTokenize(d);
					LAutoString Fields = ImapBasicTokenize(d);
					if (Cmd &&
						Folder &&
						Fields &&
						!_stricmp(Cmd, "status") &&
						!_stricmp(Folder, Dest))
					{
						char *f = Fields;
						LAutoString Field = ImapBasicTokenize(f);
						LAutoString Value = ImapBasicTokenize(f);
						if (Field &&
							Value &&
							!_stricmp(Field, "recent"))
						{
							*Recent = atoi(Value);
							Status = true;
							break;
						}
					}
				}
			}
			else LgiTrace("%s:%i - STATUS cmd failed.\n", _FL);
			CommandFinished();
		}
		
		Unlock();
	}

	return Status;
}

bool MailIMap::Poll(int *Recent, LArray<LString> *New)
{
	bool Status = true;

	if (Lock(_FL))
	{
		int Cmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i NOOP\r\n", Cmd);
		if (WriteBuf())
		{
			ClearDialog();
			if ((Status = ReadResponse(Cmd)))
			{
				int LocalRecent;
				if (!Recent)
					Recent = &LocalRecent;

				*Recent = 0;
				for (auto Dlg: Dialog)
				{
					if (Recent && stristr(Dlg, " RECENT"))
					{
						*Recent = atoi(Dlg + 2);
					}
				}

				if (*Recent && New)
				{
					Search(false, *New, "new");
				}
			}
			CommandFinished();
		}
		Unlock();
	}

	return Status;
}

bool MailIMap::StartIdle()
{
	bool Status = false;

	if (Lock(_FL))
	{
		d->IdleCmd = d->NextCmd++;
		sprintf_s(Buf, sizeof(Buf), "A%4.4i IDLE\r\n", d->IdleCmd);
		Status = WriteBuf();
		CommandFinished();
		Unlock();
	}


	return Status;
}

bool MailIMap::OnIdle(int Timeout, LString::Array &Resp)
{
	bool Status = false;

	if (Lock(_FL))
	{
		auto Blk = Socket->IsBlocking();
		Socket->IsBlocking(false);
		
		d->ReadDebug = true;
		Read(NULL, Timeout);
		d->ReadDebug = false;
		
		Socket->IsBlocking(Blk);
		Resp.SetFixedLength(false);

		char *Dlg;
		while ((Dlg = Dialog[0]))
		{
			Dialog.Delete(Dlg);
			Log(Dlg, LSocketI::SocketMsgReceive);

			if (Dlg[0] == '*' &&
				Dlg[1] == ' ')
			{
				Resp.New() = Dlg;
				Status = true;
			}

			DeleteArray(Dlg);
		}

		Unlock();
	}

	return Status;
}

bool MailIMap::FinishIdle()
{
	bool Status = false;

	if (Lock(_FL))
	{
		if (WriteBuf(false, "DONE\r\n"))
		{
			Status = ReadResponse(d->IdleCmd);
			CommandFinished();
			d->IdleCmd = -1;
		}
		Unlock();
	}

	return Status;
}

