/******************************************************************************
 gMailUtils.cc

	Copyright © 1997 by Glenn W. Bach.  All rights reserved.
	

 *****************************************************************************/

#include <gMailUtils.h>

#include <JInPipeStream.h>
#include <JOutPipeStream.h>
#include <JRegex.h>
#include <JUserNotification.h>
#include <JPtrArray-JString.h>
#include <GAddressScanner.h>
#include <GMessageHeader.h>

#include <jXGlobals.h>

#include <jProcessUtil.h>
#include <jStreamUtil.h>
#include <jStrStreamUtil.h>
#include <jFileUtil.h>
#include <jDirUtil.h>
#include <jFStreamUtil.h>
#include <jMissingProto.h>

#include <strstream.h>
#include <iostream.h>
#include <jAssert.h>

/*****************************************************************************
 ParseNameLists

 *****************************************************************************/

void GParseNameLists
	(
	const JString& to,
	const JString& cc,
	const JString& bcc,
	JPtrArray<JString>& tos,
	JPtrArray<JString>& ccs,
	JPtrArray<JString>& bccs
	)
{
	GParseNameList(to, tos);
	GParseNameList(cc, ccs);
	GParseNameList(bcc, bccs);
}

/*****************************************************************************
 ParseNameLists

 *****************************************************************************/

void GParseNameLists
	(
	const JString& to,
	const JString& cc,
	const JString& bcc,
	JPtrArray<JString>& names
	)
{
	GParseNameList(to, names);
	GParseNameList(cc, names);
	GParseNameList(bcc, names);
}


/*****************************************************************************
 GParseNameList

 *****************************************************************************/

void GParseNameList
	(
	const JString& 		list,
	JPtrArray<JString>& names,
	const JBoolean 		forName
	)
{
	JString temp(list);	
	temp.TrimWhitespace();
	if (temp.IsEmpty())
		{
		return;
		}

	GAddressScanner scanner(list, &names);
	scanner.yylex();
	for (JSize i = 1; i <= names.GetElementCount(); i++)
		{
		JString* str = names.NthElement(i);
		if (forName)
			{
			*str = GGetName(*str);
			str->TrimWhitespace();
			}
		else
			{
			*str = GGetAddress(*str);
			str->TrimWhitespace();
			}
		}

/*
	jistrstream(is, temp, temp.GetLength());
	istrstream is(temp);
	names.SetCompareFunction(JCompareStringsCaseInsensitive);
	JString* name;

	JString* buffer = new JString();
	if (list.Contains(","))
		{
		JString parse;
		JCharacter c;
		JBoolean ok = JReadUntil(is, 3, ",(\"", &parse, &c);
		while (ok)
			{
			if (ok && c == '\"')
				{
				buffer->Append(parse);
				buffer->Append("\"");
				GReadQuote(is, buffer);
				}
			else if (ok && c == '(')
				{
				buffer->Append(parse);
				buffer->Append("(");
				GReadComment(is, buffer);
				}
			else if (ok && c == '<')
				{
				buffer->Append(parse);
				buffer->Append("<");
				GReadBracket(is, buffer);
				}
			else
				{
				buffer->Append(parse);
				if (forName)
					{
					name = new JString(GGetName(*buffer));
					}
				else
					{
					name = new JString(GGetAddress(*buffer));
					}
				assert(name != NULL);
				name->TrimWhitespace();
				names.InsertSorted(name, kFalse);
				buffer->Clear();		
				}
			ok = JReadUntil(is, 4, ",(<\"", &parse, &c);
			}
		
		buffer->Append(parse);
		}
	else
		{
		*buffer = list;
		}
	if (forName)
		{
		name = new JString(GGetName(*buffer));
		}
	else
		{
		name = new JString(GGetAddress(*buffer));
		}
	assert(name != NULL);
	name->TrimWhitespace();
	names.InsertSorted(name, kFalse);
	delete buffer;	
*/
}

/*****************************************************************************
 GGetRealNames

 *****************************************************************************/

void GGetRealNames
	(
	const JString& list,
	JPtrArray<JString>& names
	)
{
	GParseNameList(list, names, kTrue);
}

/*****************************************************************************
 GVerifyPGPNames

 *****************************************************************************/

JBoolean GVerifyPGPNames
	(
	const JPtrArray<JString>& names
	)
{
	for (JSize i = 1; i <= names.GetElementCount(); i++)
		{
		JString name = *(names.NthElement(i));
		JString sysCmd = "pgp -kv " + name;
		int inFD;
		JError err = 
			JExecute(sysCmd, NULL,
				kJIgnoreConnection, NULL,
				kJCreatePipe, &inFD,
				kJTossOutput, NULL);
		if (err.OK())
			{
			JInPipeStream is(inFD, kTrue);
			if (is.peek())
				{
				JString buffer;
				JReadAll(is, &buffer);
				JRegex regex;
				JBoolean matched;
				JArray<JIndexRange>* subList = new JArray<JIndexRange>;
				assert(subList != NULL);
				err = regex.SetPattern("([0-9]+) matching key[s]* found");
				assert(err.OK());
				matched = regex.Match(buffer, subList);
				if (matched)
					{
					JIndexRange sRange = subList->GetElement(2);
					JString number = buffer.GetSubstring(sRange);
					if (number == "0")
						{
						JString notice = "No key found for " + name + ".";
						JGetUserNotification()->ReportError(notice);
						return kFalse;
						}
					}
				else
					{
					JGetUserNotification()->ReportError("Error checking pgp key.");
					return kFalse;
					}
				}
			else
				{
				JGetUserNotification()->ReportError("Error checking pgp key.");
				return kFalse;
				}
			}
		else
			{
			JGetUserNotification()->ReportError("Error checking pgp key.");
			return kFalse;
			}
		}
	return kTrue;
}

/*****************************************************************************
 GVerifyPGPNames

 *****************************************************************************/

JBoolean GVerifyGPGNames
	(
	const JPtrArray<JString>& names
	)
{
	for (JSize i = 1; i <= names.GetElementCount(); i++)
		{
		JString name = *(names.NthElement(i));
		JString sysCmd = "gpg --list-key --no-secmem-warning " + name;
		int errFD;
		JError err = 
			JExecute(sysCmd, 		NULL,
				kJIgnoreConnection, NULL,
				kJTossOutput, 		NULL,
				kJCreatePipe, 		&errFD);
		if (err.OK())
			{
			JInPipeStream is(errFD, kTrue);
			JString buffer;
			JReadAll(is, &buffer);
			if (buffer.Contains("public key not found"))
				{
				JString notice = "No key found for " + name + ".";
				JGetUserNotification()->ReportError(notice);
				return kFalse;
				}
			}
		else
			{
			JGetUserNotification()->ReportError("Error checking gog key.");
			return kFalse;
			}
		}
	return kTrue;
}

/******************************************************************************
 GGetAddress

 *****************************************************************************/

JString
GGetAddress
	(
	const JString& fullname
	)
{
	JString address;
	JString name;
	GSplitAddressAndName(fullname, &address, &name);
	return address;
}

/******************************************************************************
 GGetName

 *****************************************************************************/

JString
GGetName
	(
	const JString& fullname
	)
{
	JString address;
	JString name;
	GSplitAddressAndName(fullname, &address, &name);
	return name;
}

/******************************************************************************
 GSplitAddressAndName

 *****************************************************************************/

void
GSplitAddressAndName
	(
	const JString& 	fullname,
	JString* 		address, 
	JString* 		name
	)
{
	JString test = fullname;
	test.TrimWhitespace();
	JRegex regex;
	JBoolean matched;
	JArray<JIndexRange>* subList = new JArray<JIndexRange>;
	assert(subList != NULL);
	JError err = regex.SetPattern("^[\"]?([^\"<>]+)[\"]?[[:space:]]*<([^<>]*)>");
	assert(err.OK());
	matched = regex.Match(test, subList);
	if (matched)
		{
		JIndexRange sRange = subList->GetElement(3);
		if (!sRange.IsEmpty())
			{
			*address = test.GetSubstring(sRange);
			}
		else
			{
			*address = test;
			}
		sRange = subList->GetElement(2);
		if (!sRange.IsEmpty())
			{
			*name = test.GetSubstring(sRange);
			}
		else
			{
			*name = *address;
			}
		delete subList;
		return;
		}

	err = regex.SetPattern("^<(.*@.*)>");
	assert(err.OK());
	matched = regex.Match(test, subList);
	if (matched)
		{
		JIndexRange sRange = subList->GetElement(2);
		if (!sRange.IsEmpty())
			{
			*address = test.GetSubstring(sRange);
			*name = *address;
			}
		else
			{
			*address = test;
			*name = *address;
			}
		delete subList;
		return;
		}

	err = regex.SetPattern("^[\"](.*)[\"][[:space:]]*<(.*)>");
	assert(err.OK());
	matched = regex.Match(test, subList);
	if (matched)
		{
		JIndexRange sRange = subList->GetElement(3);
		if (!sRange.IsEmpty())
			{
			*address = test.GetSubstring(sRange);
			}
		else
			{
			*address = test;
			}
		sRange = subList->GetElement(2);
		if (!sRange.IsEmpty())
			{
			*name = test.GetSubstring(sRange);
			}
		else
			{
			*name = *address;
			}
		delete subList;
		return;
		}

	err = regex.SetPattern("(.*@.*)[[:space:]]*\\((.*)\\)");
	assert(err.OK());
	matched = regex.Match(test, subList);
	if (matched)
		{
		JIndexRange sRange = subList->GetElement(2);
		if (!sRange.IsEmpty())
			{
			*address = test.GetSubstring(sRange);
			}
		else
			{
			*address = test;
			}
		sRange = subList->GetElement(3);
		if (!sRange.IsEmpty())
			{
			*name = test.GetSubstring(sRange);
			}
		else
			{
			*name = *address;
			}
		}
	else
		{
		*address = test;
		*name = test;
		}

	delete subList;
}

/******************************************************************************
 GFixHeaderForReply 


 ******************************************************************************/

void
GFixHeaderForReply
	(
	JString* sub
	)
{
	JRegex regex;
	JError err = regex.SetPattern("^[rR][eE][.:]");
	assert(err.OK());
	JBoolean matched;
	JArray<JIndexRange>* subList = new JArray<JIndexRange>;
	assert(subList != NULL);
	matched = regex.Match(*sub, subList);
	if (!matched)
		{
		sub->Prepend("Re: ");
		}
}

/******************************************************************************
 GCompressWhitespace

 *****************************************************************************/

void
GCompressWhitespace
	(
	JString* str
	)
{
	str->TrimWhitespace();
	if (!str->IsEmpty())
		{
		JIndex findex;
		while (str->LocateSubstring("\n", &findex))
			{
			str->ReplaceSubstring(findex, findex, " ");
			}
		while (str->LocateSubstring("\t", &findex))
			{
			str->ReplaceSubstring(findex, findex, " ");
			}
		findex = 1;
		while(str->LocateNextSubstring(" ",&findex))
			{
			findex++;
			while (  str->IndexValid(findex) && 
					(str->GetCharacter(findex) == ' '))
				{
				str->RemoveSubstring(findex, findex);
				}
			}
		}
}

/******************************************************************************
 GReadQuote 


 ******************************************************************************/

void
GReadQuote
	(
	istream& is,
	JString* str
	)
{
	JCharacter c;
	JString parse;
	JBoolean ok = JReadUntil(is, 2, "\"\\", &parse, &c);
	while (ok)
		{
		if (ok && c == '\"')
			{
			str->Append(parse);
			str->Append("\"");
			return;
			}
		if (ok && c == '\\')
			{
			str->Append(parse);
			str->Append("\\");
			is.get(c);
			if (is.fail())
				{
				return;
				}
			str->AppendCharacter(c);
			}
		ok = JReadUntil(is, 2, "\"\\", &parse, &c);
		}
	str->Append(parse);
}

/******************************************************************************
 GReadComment 


 ******************************************************************************/

void
GReadComment
	(
	istream& is,
	JString* str
	)
{
	JCharacter c;
	JString parse;
	JBoolean ok = JReadUntil(is, 4, ")(\"\\", &parse, &c);
	while (ok)
		{
		if (ok && c == ')')
			{
			str->Append(parse);
			str->Append(")");
			return;
			}
		else if (ok && c == '\\')
			{
			str->Append(parse);
			str->Append("\\");
			is.get(c);
			if (is.fail())
				{
				return;
				}
			str->AppendCharacter(c);
			}
		else if (ok && c == '(')
			{
			str->Append(parse);
			str->Append("(");
			GReadComment(is, str);
			}
		else if (ok && c == '\"')
			{
			str->Append(parse);
			str->Append("\"");
			GReadQuote(is, str);
			}
		ok = JReadUntil(is, 4, ")(\"\\", &parse, &c);
		}
	str->Append(parse);
}

/******************************************************************************
 GReadBracket 


 ******************************************************************************/

void
GReadBracket
	(
	istream& is,
	JString* str
	)
{
	JCharacter c;
	JString parse;
	JBoolean ok = JReadUntil(is, 4, "><\"\\", &parse, &c);
	while (ok)
		{
		if (ok && c == '>')
			{
			str->Append(parse);
			str->Append(">");
			return;
			}
		else if (ok && c == '\\')
			{
			str->Append(parse);
			str->Append("\\");
			is.get(c);
			if (is.fail())
				{
				return;
				}
			str->AppendCharacter(c);
			}
		else if (ok && c == '<')
			{
			str->Append(parse);
			str->Append("<");
			GReadBracket(is, str);
			}
		else if (ok && c == '\"')
			{
			str->Append(parse);
			str->Append("\"");
			GReadQuote(is, str);
			}
		ok = JReadUntil(is, 4, "><\"\\", &parse, &c);
		}
	str->Append(parse);
}

/******************************************************************************
 SaveMessage

 ******************************************************************************/

void
GSaveMessage
	(
	istream& is, 
	ostream& os,
	GMessageHeader* header,
	const JBoolean update,
	const JBoolean flush
	)
{
	JSize headerStart = JTellp(os);
	if ((header->GetMessageStatus() != GMessageHeader::kDelete) || !flush)
		{
		JSeekg(is, header->GetHeaderStart());
		JString line;
		JBoolean foundG = kFalse;
		JBoolean foundS = kFalse;
		while ((line = JReadLine(is)) != "")
			{
			if (line.BeginsWith("Status:"))
				{
				foundS = kTrue;
				line = "Status: ";
				if (header->GetMessageStatus() != GMessageHeader::kNew)
					{
					line += "RO";
					line.Print(os);
					os << "\n";
					}
				}
			else if (line.BeginsWith("GMStatus:"))
				{
				foundG = kTrue;
				line = "GMStatus: ";
				if (header->GetMessageStatus() == GMessageHeader::kUrgent)
					{
					line += "U";
					line.Print(os);
					os << "\n";
					}
				else if (header->GetMessageStatus() == GMessageHeader::kImportant)
					{
					line += "I";
					line.Print(os);
					os << "\n";							
					}
				}
			else
				{
				line.Print(os);
				os << "\n";
				}
			}
		if (!foundG)
			{
			line = "GMStatus: ";
			if (header->GetMessageStatus() == GMessageHeader::kUrgent)
				{
				line += "U";
				line.Print(os);
				os << "\n";
				}
			else if (header->GetMessageStatus() == GMessageHeader::kImportant)
				{
				line += "I";
				line.Print(os);
				os << "\n";
				}
			}
		if (!foundS)
			{
			line = "Status: ";
			if (header->GetMessageStatus() != GMessageHeader::kNew)
				{
				line += "RO";
				line.Print(os);
				os << "\n";
				}
			}
		line = "\n";
		line.Print(os);
		JSize headerEnd = JTellp(os);
		JString body = JRead(is, header->GetMessageEnd() - header->GetHeaderEnd());
		if (!body.EndsWith("\n\n"))
			{
			body.Append("\n\n");
			}
		body.Print(os);
		JSize messageEnd = JTellp(os);
		if (update)
			{
			header->SetHeaderStart(headerStart);
			header->SetHeaderEnd(headerEnd);
			header->SetMessageEnd(messageEnd);
			}
		}
}

/******************************************************************************
 GEncryptPGP2_6_2

 ******************************************************************************/

JBoolean
GEncryptPGP2_6_2
	(
	const JString& 		text, 
	const JString& 		passwd,
	JPtrArray<JString>*	names,
	JString* 			newText
	)
{
	JString file_out = JGetTempFileName("/tmp");
	ofstream os(file_out);
	if (!os.good())
		{
		JGetUserNotification()->ReportError("Error encrypting file.");
		return kFalse;
		}
	text.Print(os);
	os.close();
	JString file_in = JGetTempFileName("/tmp");
	JString sysCmd = "pgp -east " + file_out + " -o " + file_in + " ";
	if (GVerifyPGPNames(*names))
		{
		for (JSize i = 1; i <= names->GetElementCount(); i++)
			{
			sysCmd += *(names->NthElement(i)) + " ";
			}
		setenv("PGPPASS", passwd, 1);
		int errFD;
		JError err =
			JExecute(sysCmd, NULL,
				kJIgnoreConnection, NULL,
				kJTossOutput, NULL,
				kJCreatePipe, &errFD);
		JRemoveFile(file_out);
		if (err.OK())
			{
			file_in += ".asc";
			JReadFile(file_in, newText);
			JRemoveFile(file_in);
			}
		else
			{
			JGetUserNotification()->ReportError("Error encrypting file.");
			return kFalse;
			}
		setenv("PGPPASS", "", 1);
		}
	return kTrue;
}

/******************************************************************************
 GEncryptPGP2_6_2

 ******************************************************************************/

JBoolean
GEncryptGPG1_0
	(
	const JString& 		text, 
	JPtrArray<JString>*	names,
	JString* 			newText
	)
{
	JString sysCmd = "gpg -ea --batch --no-secmem-warning";
	if (GVerifyGPGNames(*names))
		{
		for (JSize i = 1; i <= names->GetElementCount(); i++)
			{
			sysCmd += " -r " + *(names->NthElement(i));
			}
		int errFD, toFD, fromFD;
		pid_t pid;
		JError err =
			JExecute(sysCmd, &pid,
				kJCreatePipe, &toFD,
				kJCreatePipe, &fromFD,
				kJCreatePipe, &errFD);
		if (err.OK())
			{
			JOutPipeStream output(toFD, kTrue);
			JInPipeStream input(fromFD, kTrue);
			text.Print(output);
			output.close();
			JReadAll(input, newText);
			JInPipeStream errors(errFD, kTrue);
			JString errStr;
			JReadAll(errors, &errStr);
			if (!errStr.IsEmpty())
				{
				JGetUserNotification()->ReportError(errStr);
				}
			}
		else
			{
			JGetUserNotification()->ReportError("Error encrypting file.");
			return kFalse;
			}
		}
	return kTrue;
}

/******************************************************************************
 GDecryptPGP2_6_2

 ******************************************************************************/

JBoolean
GDecryptPGP2_6_2
	(
	const JString& 		text, 
	const JString& 		passwd,
	JString* 			newText
	)
{
	JString file_out = JGetTempFileName("/tmp");
	ofstream os(file_out);
	if (!os.good())
		{
		JGetUserNotification()->ReportError("Error decrypting file.");
		return kFalse;
		}
	text.Print(os);
	os.close();
	JString file_in = JGetTempFileName("/tmp");
	JString sysCmd = "pgp " + file_out + " -o " + file_in;
	setenv("PGPPASS", passwd, 1);
	JError err =
		JExecute(sysCmd, 		NULL,
			kJIgnoreConnection, NULL,
			kJTossOutput, 		NULL,
			kJTossOutput, 		NULL);
	setenv("PGPPASS", "", 1);
	JRemoveFile(file_out);
	if (err.OK())
		{
		JReadFile(file_in, newText);
		JRemoveFile(file_in);
		}
	else
		{
		JGetUserNotification()->ReportError("Error decrypting file.");
		}
	return kTrue;
}

/******************************************************************************
 GDecryptGPG1_0

 ******************************************************************************/

JBoolean
GDecryptGPG1_0
	(
	const JString& 		text, 
	const JString& 		passwd,
	JString* 			newText
	)
{
	JString sysCmd = "gpg -d --passphrase-fd 0 --batch --no-secmem-warning";
	int errFD, toFD, fromFD;
	pid_t pid;
	JError err =
		JExecute(sysCmd, &pid,
			kJCreatePipe, &toFD,
			kJCreatePipe, &fromFD,
			kJCreatePipe, &errFD);
	if (err.OK())
		{
		JOutPipeStream output(toFD, kTrue);
		JInPipeStream input(fromFD, kTrue);
		passwd.Print(output);
		output << endl;
		text.Print(output);
		output.close();
		JReadAll(input, newText);
		JInPipeStream errors(errFD, kTrue);
		JString errStr;
		JReadAll(errors, &errStr);
		if (!errStr.IsEmpty())
			{
			JGetUserNotification()->ReportError(errStr);
			}
		}
	else
		{
		JGetUserNotification()->ReportError("Error encrypting file.");
		return kFalse;
		}
	return kTrue;

}