#include "FileZilla.h"
#include "directorylistingparser.h"
#include "ControlSocket.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

std::map<wxString, int> CDirectoryListingParser::m_MonthNamesMap;

//#define LISTDEBUG_MVS
//#define LISTDEBUG
#ifdef LISTDEBUG
static char data[][110]={

	/* IBM MVS listings */
	// Volume Unit    Referred Ext Used Recfm Lrecl BlkSz Dsorg Dsname
	"  WYOSPT 3420   2003/05/21  1  200  FB      80  8053  PS  48-MVS.FILE",
	"  WPTA01 3290   2004/03/04  1    3  FB      80  3125  PO  49-MVS.DATASET",
	"  TSO004 3390   VSAM 50-mvs-file",
	"  TSO005 3390   2005/06/06 213000 U 0 27998 PO 51-mvs-dir",
	"  NRP004 3390   **NONE**    1   15  NONE     0     0  PO  52-MVS-NONEDATE.DATASET",

	/* Dataset members */
	// Name         VV.MM   Created      Changed       Size  Init  Mod Id
	// ADATAB /* filenames without data, only check for those on MVS servers */
	"  53-MVSPDSMEMBER 01.01 2004/06/22 2004/06/22 16:32   128   128    0 BOBY12",

	"54-MVSPDSMEMBER2 00B308 000411  00 FO                31    ANY",
	"55-MVSPDSMEMBER3 00B308 000411  00 FO        RU      ANY    24",

	// Migrated MVS file
	"Migrated				SOME.FILE",

	""};

#endif

class CToken
{
protected:
	enum TokenInformation
	{
		Unknown,
		Yes,
		No
	};

public:
	CToken()
	{
		m_pToken = 0;
		m_len = 0;
	}

	enum t_numberBase
	{
		decimal,
		hex
	};

	CToken(const wxChar* p, unsigned int len)
	{
		m_pToken = p;
		m_len = len;
		m_numeric = Unknown;
		m_leftNumeric = Unknown;
		m_rightNumeric = Unknown;
		m_number = -1;
	}

	const wxChar* GetToken() const
	{
		return m_pToken;
	}

	unsigned int GetLength() const
	{
		return m_len;
	}

	wxString GetString(unsigned int type = 0)
	{
		if (!m_pToken)
			return _T("");

		if (type > 2)
			return _T("");

		if (m_str[type] != _T(""))
			return m_str[type];

		if (!type)
		{
			wxString str(m_pToken, m_len);
			m_str[type] = str;
			return str;
		}
		else if (type == 1)
		{
			if (!IsRightNumeric() || IsNumeric())
				return _T("");

			int pos = m_len - 1;
			while (m_pToken[pos] >= '0' && m_pToken[pos] <= '9')
				pos--;

			wxString str(m_pToken, pos + 1);
			m_str[type] = str;
			return str;
		}
		else if (type == 2)
		{
			if (!IsLeftNumeric() || IsNumeric())
				return _T("");

			int pos = 0;
			while (m_pToken[pos] >= '0' && m_pToken[pos] <= '9')
				pos++;

			wxString str(m_pToken + pos, m_len - pos);
			m_str[type] = str;
			return str;
		}

		return _T("");
	}

	bool IsNumeric(t_numberBase base = decimal)
	{
		switch (base)
		{
		case decimal:
		default:
			if (m_numeric == Unknown)
			{
				m_numeric = Yes;
				for (unsigned int i = 0; i < m_len; i++)
					if (m_pToken[i] < '0' || m_pToken[i] > '9')
					{
						m_numeric = No;
						break;
					}
			}
			return m_numeric == Yes;
		case hex:
			for (unsigned int i = 0; i < m_len; i++)
			{
				const char c = m_pToken[i];
				if ((c < '0' || c > '9') && (c < 'A' || c > 'F') && (c < 'a' || c > 'f'))
					return false;
			}
			return true;
		}
	}

	bool IsNumeric(unsigned int start, unsigned int len)
	{
		for (unsigned int i = start; i < wxMin(start + len, m_len); i++)
			if (m_pToken[i] < '0' || m_pToken[i] > '9')
				return false;
		return true;
	}

	bool IsLeftNumeric()
	{
		if (m_leftNumeric == Unknown)
		{
			if (m_len < 2)
				m_leftNumeric = No;
			else if (m_pToken[0] < '0' || m_pToken[0] > '9')
				m_leftNumeric = No;
			else
				m_leftNumeric = Yes;
		}
		return m_leftNumeric == Yes;
	}

	bool IsRightNumeric()
	{
		if (m_rightNumeric == Unknown)
		{
			if (m_len < 2)
				m_rightNumeric = No;
			else if (m_pToken[m_len - 1] < '0' || m_pToken[m_len - 1] > '9')
				m_rightNumeric = No;
			else
				m_rightNumeric = Yes;
		}
		return m_rightNumeric == Yes;
	}

	int Find(const wxChar* chr, int start = 0) const
	{
		if (!chr)
			return -1;

		for (unsigned int i = start; i < m_len; i++)
		{
			for (int c = 0; chr[c]; c++)
			{
				if (m_pToken[i] == chr[c])
					return i;
			}
		}
		return -1;
	}

	int Find(wxChar chr, int start = 0) const
	{
		if (!m_pToken)
			return -1;

		for (unsigned int i = start; i < m_len; i++)
			if (m_pToken[i] == chr)
				return i;

		return -1;
	}

	wxLongLong GetNumber(unsigned int start, int len)
	{
		if (len == -1)
			len = m_len - start;
		if (len < 1)
			return -1;

		if (start + static_cast<unsigned int>(len) > m_len)
			return -1;

		if (m_pToken[start] < '0' || m_pToken[start] > '9')
			return -1;

		wxLongLong number = 0;
		for (unsigned int i = start; i < (start + len); i++)
		{
			if (m_pToken[i] < '0' || m_pToken[i] > '9')
				break;
			number *= 10;
			number += m_pToken[i] - '0';
		}
		return number;
	}

	wxLongLong GetNumber(t_numberBase base = decimal)
	{
		switch (base)
		{
		default:
		case decimal:
			if (m_number == -1)
			{
				if (IsNumeric() || IsLeftNumeric())
				{
					m_number = 0;
					for (unsigned int i = 0; i < m_len; i++)
					{
						if (m_pToken[i] < '0' || m_pToken[i] > '9')
							break;
						m_number *= 10;
						m_number += m_pToken[i] - '0';
					}
				}
				else if (IsRightNumeric())
				{
					m_number = 0;
					int start = m_len - 1;
					while (m_pToken[start - 1] >= '0' && m_pToken[start - 1] <= '9')
						start--;
					for (unsigned int i = start; i < m_len; i++)
					{
						m_number *= 10;
						m_number += m_pToken[i] - '0';
					}
				}
			}
			return m_number;
		case hex:
			{
				wxLongLong number = 0;
				for (unsigned int i = 0; i < m_len; i++)
				{
					const wxChar& c = m_pToken[i];
					if (c >= '0' && c <= '9')
					{
						number *= 16;
						number += c - '0';
					}
					else if (c >= 'a' && c <= 'f')
					{
						number *= 16;
						number += c - '0' + 10;
					}
					else if (c >= 'A' && c <= 'F')
					{
						number *= 16;
						number += c - 'A' + 10;
					}
					else
						return -1;
				}
				return number;
			}
		}
	}

	wxChar operator[](unsigned int n) const
	{
		if (n >= m_len)
			return 0;

		return m_pToken[n];
	}

protected:
	const wxChar* m_pToken;
	unsigned int m_len;

	TokenInformation m_numeric;
	TokenInformation m_leftNumeric;
	TokenInformation m_rightNumeric;
	wxLongLong m_number;
	wxString m_str[3];
};

class CLine
{
public:
	CLine(wxChar* p, int len = -1)
	{
		m_pLine = p;
		if (len != -1)
			m_len = len;
		else
			m_len = wxStrlen(p);

		m_parsePos = 0;
	}

	~CLine()
	{
		delete [] m_pLine;

		std::vector<CToken *>::iterator iter;
		for (iter = m_Tokens.begin(); iter != m_Tokens.end(); iter++)
			delete *iter;
		for (iter = m_LineEndTokens.begin(); iter != m_LineEndTokens.end(); iter++)
			delete *iter;
	}

	bool GetToken(unsigned int n, CToken &token, bool toEnd = false)
	{
		if (!toEnd)
		{
			if (m_Tokens.size() > n)
			{
				token = *(m_Tokens[n]);
				return true;
			}

			int start = m_parsePos;
			while (m_parsePos < m_len)
			{
				if (m_pLine[m_parsePos] == ' ')
				{
					CToken *pToken = new CToken(m_pLine + start, m_parsePos - start);
					m_Tokens.push_back(pToken);

					while (m_pLine[m_parsePos] == ' ' && m_parsePos < m_len)
						m_parsePos++;

					if (m_Tokens.size() > n)
					{
						token = *(m_Tokens[n]);
						return true;
					}

					start = m_parsePos;
				}
				m_parsePos++;
			}
			if (m_parsePos != start)
			{
				CToken *pToken = new CToken(m_pLine + start, m_parsePos - start);
					m_Tokens.push_back(pToken);
			}

			if (m_Tokens.size() > n)
			{
				token = *(m_Tokens[n]);
				return true;
			}

			return false;
		}
		else
		{
			if (m_LineEndTokens.size() > n)
			{
				token = *(m_LineEndTokens[n]);
				return true;
			}

			if (m_Tokens.size() <= n)
				if (!GetToken(n, token))
					return false;

			for (unsigned int i = static_cast<unsigned int>(m_LineEndTokens.size()); i <= n; i++)
			{
				const CToken *refToken = m_Tokens[i];
				const wxChar* p = refToken->GetToken();
				CToken *pToken = new CToken(p, m_len - (p - m_pLine));
				m_LineEndTokens.push_back(pToken);
			}
			token = *(m_LineEndTokens[n]);
			return true;
		}
	};

	CLine *Concat(const CLine *pLine) const
	{
		int newLen = m_len + pLine->m_len + 1;
		wxChar* p = new wxChar[newLen];
		memcpy(p, m_pLine, m_len * sizeof(wxChar));
		p[m_len] = ' ';
		memcpy(p + m_len + 1, pLine->m_pLine, pLine->m_len * sizeof(wxChar));

		return new CLine(p, m_len + pLine->m_len + 1);
	}

protected:
	std::vector<CToken *> m_Tokens;
	std::vector<CToken *> m_LineEndTokens;
	int m_parsePos;
	int m_len;
	wxChar* m_pLine;
};

CDirectoryListingParser::CDirectoryListingParser(CControlSocket* pControlSocket, const CServer& server)
	: m_pControlSocket(pControlSocket), m_server(server)
{
	m_currentOffset = 0;
	m_prevLine = 0;
	m_fileListOnly = true;
	m_maybeMultilineVms = false;

	if (m_MonthNamesMap.empty())
	{
		//Fill the month names map

		//English month names
		m_MonthNamesMap[_T("jan")] = 1;
		m_MonthNamesMap[_T("feb")] = 2;
		m_MonthNamesMap[_T("mar")] = 3;
		m_MonthNamesMap[_T("apr")] = 4;
		m_MonthNamesMap[_T("may")] = 5;
		m_MonthNamesMap[_T("jun")] = 6;
		m_MonthNamesMap[_T("june")] = 6;
		m_MonthNamesMap[_T("jul")] = 7;
		m_MonthNamesMap[_T("july")] = 7;
		m_MonthNamesMap[_T("aug")] = 8;
		m_MonthNamesMap[_T("sep")] = 9;
		m_MonthNamesMap[_T("sept")] = 9;
		m_MonthNamesMap[_T("oct")] = 10;
		m_MonthNamesMap[_T("nov")] = 11;
		m_MonthNamesMap[_T("dec")] = 12;

		//Numerical values for the month
		m_MonthNamesMap[_T("1")] = 1;
		m_MonthNamesMap[_T("01")] = 1;
		m_MonthNamesMap[_T("2")] = 2;
		m_MonthNamesMap[_T("02")] = 2;
		m_MonthNamesMap[_T("3")] = 3;
		m_MonthNamesMap[_T("03")] = 3;
		m_MonthNamesMap[_T("4")] = 4;
		m_MonthNamesMap[_T("04")] = 4;
		m_MonthNamesMap[_T("5")] = 5;
		m_MonthNamesMap[_T("05")] = 5;
		m_MonthNamesMap[_T("6")] = 6;
		m_MonthNamesMap[_T("06")] = 6;
		m_MonthNamesMap[_T("7")] = 7;
		m_MonthNamesMap[_T("07")] = 7;
		m_MonthNamesMap[_T("8")] = 8;
		m_MonthNamesMap[_T("08")] = 8;
		m_MonthNamesMap[_T("9")] = 9;
		m_MonthNamesMap[_T("09")] = 9;
		m_MonthNamesMap[_T("10")] = 10;
		m_MonthNamesMap[_T("11")] = 11;
		m_MonthNamesMap[_T("12")] = 12;

		//German month names
		m_MonthNamesMap[_T("mrz")] = 3;
		m_MonthNamesMap[_T("m\xe4r")] = 3;
		m_MonthNamesMap[_T("m\xe4rz")] = 3;
		m_MonthNamesMap[_T("mai")] = 5;
		m_MonthNamesMap[_T("juni")] = 6;
		m_MonthNamesMap[_T("juli")] = 7;
		m_MonthNamesMap[_T("okt")] = 10;
		m_MonthNamesMap[_T("dez")] = 12;

		//Austrian month names
		m_MonthNamesMap[_T("j\xe4n")] = 1;

		//French month names
		m_MonthNamesMap[_T("janv")] = 1;
		m_MonthNamesMap[_T("f\xe9")_T("b")] = 1;
		m_MonthNamesMap[_T("f\xe9v")] = 2;
		m_MonthNamesMap[_T("fev")] = 2;
		m_MonthNamesMap[_T("f\xe9vr")] = 2;
		m_MonthNamesMap[_T("fevr")] = 2;
		m_MonthNamesMap[_T("mars")] = 3;
		m_MonthNamesMap[_T("mrs")] = 3;
		m_MonthNamesMap[_T("avr")] = 4;
		m_MonthNamesMap[_T("juin")] = 6;
		m_MonthNamesMap[_T("juil")] = 7;
		m_MonthNamesMap[_T("jui")] = 7;
		m_MonthNamesMap[_T("ao\xfb")] = 8;
		m_MonthNamesMap[_T("ao\xfbt")] = 8;
		m_MonthNamesMap[_T("aout")] = 8;
		m_MonthNamesMap[_T("d\xe9")_T("c")] = 12;
		m_MonthNamesMap[_T("dec")] = 12;

		//Italian month names
		m_MonthNamesMap[_T("gen")] = 1;
		m_MonthNamesMap[_T("mag")] = 5;
		m_MonthNamesMap[_T("giu")] = 6;
		m_MonthNamesMap[_T("lug")] = 7;
		m_MonthNamesMap[_T("ago")] = 8;
		m_MonthNamesMap[_T("set")] = 9;
		m_MonthNamesMap[_T("ott")] = 10;
		m_MonthNamesMap[_T("dic")] = 12;

		//Spanish month names
		m_MonthNamesMap[_T("ene")] = 1;
		m_MonthNamesMap[_T("fbro")] = 2;
		m_MonthNamesMap[_T("mzo")] = 3;
		m_MonthNamesMap[_T("ab")] = 4;
		m_MonthNamesMap[_T("abr")] = 4;
		m_MonthNamesMap[_T("agto")] = 8;
		m_MonthNamesMap[_T("sbre")] = 9;
		m_MonthNamesMap[_T("obre")] = 9;
		m_MonthNamesMap[_T("nbre")] = 9;
		m_MonthNamesMap[_T("dbre")] = 9;

		//Polish month names
		m_MonthNamesMap[_T("sty")] = 1;
		m_MonthNamesMap[_T("lut")] = 2;
		m_MonthNamesMap[_T("kwi")] = 4;
		m_MonthNamesMap[_T("maj")] = 5;
		m_MonthNamesMap[_T("cze")] = 6;
		m_MonthNamesMap[_T("lip")] = 7;
		m_MonthNamesMap[_T("sie")] = 8;
		m_MonthNamesMap[_T("wrz")] = 9;
		m_MonthNamesMap[_T("pa\x9f")] = 10;
		m_MonthNamesMap[_T("lis")] = 11;
		m_MonthNamesMap[_T("gru")] = 12;

		//Russian month names
		m_MonthNamesMap[_T("\xff\xed\xe2")] = 1;
		m_MonthNamesMap[_T("\xf4\xe5\xe2")] = 2;
		m_MonthNamesMap[_T("\xec\xe0\xf0")] = 3;
		m_MonthNamesMap[_T("\xe0\xef\xf0")] = 4;
		m_MonthNamesMap[_T("\xec\xe0\xe9")] = 5;
		m_MonthNamesMap[_T("\xe8\xfe\xed")] = 6;
		m_MonthNamesMap[_T("\xe8\xfe\xeb")] = 7;
		m_MonthNamesMap[_T("\xe0\xe2\xe3")] = 8;
		m_MonthNamesMap[_T("\xf1\xe5\xed")] = 9;
		m_MonthNamesMap[_T("\xee\xea\xf2")] = 10;
		m_MonthNamesMap[_T("\xed\xee\xff")] = 11;
		m_MonthNamesMap[_T("\xe4\xe5\xea")] = 12;

		//Dutch month names
		m_MonthNamesMap[_T("mrt")] = 3;
		m_MonthNamesMap[_T("mei")] = 5;

		//Portuguese month names
		m_MonthNamesMap[_T("out")] = 10;

		//Finnish month names
		m_MonthNamesMap[_T("tammi")] = 1;
		m_MonthNamesMap[_T("helmi")] = 2;
		m_MonthNamesMap[_T("maalis")] = 3;
		m_MonthNamesMap[_T("huhti")] = 4;
		m_MonthNamesMap[_T("touko")] = 5;
		m_MonthNamesMap[_T("kes\xe4")] = 6;
		m_MonthNamesMap[_T("hein\xe4")] = 7;
		m_MonthNamesMap[_T("elo")] = 8;
		m_MonthNamesMap[_T("syys")] = 9;
		m_MonthNamesMap[_T("loka")] = 10;
		m_MonthNamesMap[_T("marras")] = 11;
		m_MonthNamesMap[_T("joulu")] = 12;

		//Slovenian month names
		m_MonthNamesMap[_T("avg")] = 8;

		//There are more languages and thus month
		//names, but as long as knowbody reports a
		//problem, I won't add them, there are way
		//too many languages

		// Some servers send a combination of month name and number,
		// Add corresponding numbers to the month names.
		std::map<wxString, int> combo;
		for (std::map<wxString, int>::iterator iter = m_MonthNamesMap.begin(); iter != m_MonthNamesMap.end(); iter++)
		{
			// January could be 1 or 0, depends how the server counts
			combo[wxString::Format(_T("%s%02d"), iter->first.c_str(), iter->second)] = iter->second;
			combo[wxString::Format(_T("%s%02d"), iter->first.c_str(), iter->second - 1)] = iter->second;
			if (iter->second < 10)
				combo[wxString::Format(_T("%s%d"), iter->first.c_str(), iter->second)] = iter->second;
			else
				combo[wxString::Format(_T("%s%d"), iter->first.c_str(), iter->second % 10)] = iter->second;
			if (iter->second <= 10)
				combo[wxString::Format(_T("%s%d"), iter->first.c_str(), iter->second - 1)] = iter->second;
			else
				combo[wxString::Format(_T("%s%d"), iter->first.c_str(), (iter->second - 1) % 10)] = iter->second;
		}
		m_MonthNamesMap.insert(combo.begin(), combo.end());

		m_MonthNamesMap[_T("1")] = 1;
		m_MonthNamesMap[_T("2")] = 2;
		m_MonthNamesMap[_T("3")] = 3;
		m_MonthNamesMap[_T("4")] = 4;
		m_MonthNamesMap[_T("5")] = 5;
		m_MonthNamesMap[_T("6")] = 6;
		m_MonthNamesMap[_T("7")] = 7;
		m_MonthNamesMap[_T("8")] = 8;
		m_MonthNamesMap[_T("9")] = 9;
		m_MonthNamesMap[_T("10")] = 10;
		m_MonthNamesMap[_T("11")] = 11;
		m_MonthNamesMap[_T("12")] = 12;
	}

#ifdef LISTDEBUG
	for (unsigned int i = 0; data[i][0]; i++)
	{
		unsigned int len = (unsigned int)strlen(data[i]);
		char *pData = new char[len + 3];
		strcpy(pData, data[i]);
		strcat(pData, "\r\n");
		AddData(pData, len + 2);
	}
#endif
}

CDirectoryListingParser::~CDirectoryListingParser()
{
	for (std::list<t_list>::iterator iter = m_DataList.begin(); iter != m_DataList.end(); iter++)
		delete [] iter->p;

	delete m_prevLine;
}

void CDirectoryListingParser::ParseData(bool partial)
{
	CLine *pLine = GetLine(partial);
	while (pLine)
	{
		bool res = ParseLine(pLine, m_server.GetType(), false);
		if (!res)
		{
			if (m_prevLine)
			{
				CLine* pConcatenatedLine = m_prevLine->Concat(pLine);
				bool res = ParseLine(pConcatenatedLine, m_server.GetType(), true);
				delete pConcatenatedLine;
				delete m_prevLine;

				if (res)
				{
					delete pLine;
					m_prevLine = 0;
				}
				else
					m_prevLine = pLine;
			}
			else
				m_prevLine = pLine;
		}
		else
		{
			delete m_prevLine;
			m_prevLine = 0;
			delete pLine;
		}
		pLine = GetLine(partial);
	};
}

CDirectoryListing CDirectoryListingParser::Parse(const CServerPath &path)
{
	ParseData(false);

	CDirectoryListing listing;
	listing.path = path;
	listing.m_firstListTime = CTimeEx::Now();

	if (!m_fileList.empty())
	{
		wxASSERT(m_entryList.empty());

		listing.SetCount(m_fileList.size());
		unsigned int i = 0;
		for (std::list<wxString>::const_iterator iter = m_fileList.begin(); iter != m_fileList.end(); iter++, i++)
		{
			CDirentry entry;
			entry.name = *iter;
			entry.dir = false;
			entry.hasDate = false;
			entry.hasTime = false;
			entry.link = false;
			entry.size = -1;
			entry.unsure = false;
			listing[i] = entry;
		}
	}
	else
	{
		listing.SetCount(m_entryList.size());
		listing.Assign(m_entryList);
	}

	return listing;
}

bool CDirectoryListingParser::ParseLine(CLine *pLine, const enum ServerType serverType, bool concatenated)
{
	CDirentry entry;
	bool res = ParseAsUnix(pLine, entry);
	if (res)
		goto done;
	res = ParseAsDos(pLine, entry);
	if (res)
		goto done;
	res = ParseAsMlsd(pLine, entry);
	if (res)
		goto done;
	res = ParseAsEplf(pLine, entry);
	if (res)
		goto done;
	res = ParseAsVms(pLine, entry);
	if (res)
		goto done;
	res = ParseOther(pLine, entry);
	if (res)
		goto done;
	res = ParseAsIbm(pLine, entry);
	if (res)
		goto done;
	res = ParseAsWfFtp(pLine, entry);
	if (res)
		goto done;
	res = ParseAsIBM_MVS(pLine, entry);
	if (res)
		goto done;
	res = ParseAsIBM_MVS_PDS(pLine, entry);
	if (res)
		goto done;
	res = ParseAsOS9(pLine, entry);
	if (res)
		goto done;
#ifndef LISTDEBUG_MVS
	if (serverType == MVS)
#endif //LISTDEBUG_MVS
	{
		res = ParseAsIBM_MVS_PDS2(pLine, entry);
		if (res)
			goto done;
		res = ParseAsIBM_MVS_Migrated(pLine, entry);
		if (res)
			goto done;
	}

	// Some servers just send a list of filenames. If a line could not be parsed,
	// check if it's a filename. If that's the case, store it for later, else clear
	// list of stored files.
	// If parsing finishes and no entries could be parsed and none of the lines
	// contained a space, assume it's a raw filelisting.

	if (!concatenated)
	{
		CToken token;
		if (!pLine->GetToken(0, token, true) || token.Find(' ') != -1)
		{
			m_maybeMultilineVms = false;
			m_fileList.clear();
			m_fileListOnly = false;
		}
		else
		{
			m_maybeMultilineVms = token.Find(';') != -1;
			if (m_fileListOnly)
				m_fileList.push_back(token.GetString());
		}
	}
	else
		m_maybeMultilineVms = false;

	return false;
done:

	m_maybeMultilineVms = false;
	m_fileList.clear();
	m_fileListOnly = false;

	// Don't add . or ..
	if (entry.name == _T(".") || entry.name == _T(".."))
		return true;

	if (serverType == VMS && entry.dir)
	{
		// Trim version information from directories
		int pos = entry.name.Find(';', true);
		if (pos > 0)
			entry.name = entry.name.Left(pos);
	}

	int offset = m_server.GetTimezoneOffset();
	if (offset && entry.hasTime)
	{
		// Apply timezone offset
		wxTimeSpan span(0, offset, 0, 0);
		entry.time.Add(span);
	}

	entry.unsure = false;
	m_entryList.push_back(entry);

	return true;
}

bool CDirectoryListingParser::ParseAsUnix(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken token;
	if (!pLine->GetToken(index, token))
		return false;

	wxChar chr = token[0];
	if (chr != 'b' &&
		chr != 'c' &&
		chr != 'd' &&
		chr != 'l' &&
		chr != 'p' &&
		chr != 's' &&
		chr != '-')
		return false;

	entry.permissions = token.GetString();

	if (chr == 'd' || chr == 'l')
		entry.dir = true;
	else
		entry.dir = false;

	if (chr == 'l')
		entry.link = true;
	else
		entry.link = false;

	// Check for netware servers, which split the permissions into two parts
	bool netware = false;
	if (token.GetLength() == 1)
	{
		if (!pLine->GetToken(++index, token))
			return false;
		entry.permissions += _T(" ") + token.GetString();
		netware = true;
	}

	int numOwnerGroup = 3;
	if (!netware)
	{
		// Filter out link count, we don't need it
		if (!pLine->GetToken(++index, token))
			return false;

		if (!token.IsNumeric())
			index--;
	}

	// Repeat until numOwnerGroup is 0 since not all servers send every possible field
	int startindex = index;
	do
	{
		// Reset index
		index = startindex;

		entry.ownerGroup.clear();
		for (int i = 0; i < numOwnerGroup; i++)
		{
			if (!pLine->GetToken(++index, token))
				return false;
			if (i)
				entry.ownerGroup += _T(" ");
			entry.ownerGroup += token.GetString();
		}

		if (!pLine->GetToken(++index, token))
			return false;

		// Check for concatenated groupname and size fields
		if (!ParseComplexFileSize(token, entry.size))
		{
			if (!token.IsRightNumeric())
				continue;
			entry.size = token.GetNumber();
		}

		// Append missing group to ownerGroup
		if (!token.IsNumeric() && token.IsRightNumeric())
		{
			if (!entry.ownerGroup.IsEmpty())
				entry.ownerGroup += _T(" ");
			entry.ownerGroup += token.GetString(1);
		}

		if (!ParseUnixDateTime(pLine, index, entry))
			continue;

		// Get the filename
		if (!pLine->GetToken(++index, token, 1))
			continue;

		entry.name = token.GetString();

		// Filter out cpecial chars at the end of the filenames
		chr = token[token.GetLength() - 1];
		if (chr == '/' ||
			chr == '|' ||
			chr == '*')
			entry.name.RemoveLast();

		if (entry.link)
		{
			int pos;
			if ((pos = entry.name.Find(_T(" -> "))) != -1)
			{
				entry.target = entry.name.Mid(pos + 4);
				entry.name = entry.name.Left(pos);
			}
		}

		return true;
	}
	while (numOwnerGroup--);

	return false;
}

bool CDirectoryListingParser::ParseUnixDateTime(CLine *pLine, int &index, CDirentry &entry)
{
	bool mayHaveTime = true;
	bool bHasYearAndTime = false;

	CToken token;

	// Get the month date field
	CToken dateMonth;
	if (!pLine->GetToken(++index, token))
		return false;

	int year = 0;
	int month = 0;
	int day = 0;
	long hour = 0;
	long minute = 0;

	// Some servers use the following date formats:
	// 26-05 2002, 2002-10-14, 01-jun-99 or 2004.07.15
	// slashes instead of dashes are also possible
	int pos = token.Find(_T("-/."));
	if (pos != -1)
	{
		int pos2 = token.Find(_T("-/."), pos + 1);
		if (pos2 == -1)
		{
			if (token[pos] != '.')
			{
				// something like 26-05 2002
				day = token.GetNumber(pos + 1, token.GetLength() - pos - 1).GetLo();
				if (day < 1 || day > 31)
					return false;
				dateMonth = CToken(token.GetToken(), pos);
			}
			else
				dateMonth = token;
		}
		else if (token[pos] != token[pos2])
			return false;
		else
		{
			if (!ParseShortDate(token, entry))
				return false;

			if (token[pos] == '.')
			{
				entry.hasTime = false;
				return true;
			}

			year = entry.time.GetYear();
			month = entry.time.GetMonth() - wxDateTime::Jan + 1;
			day = entry.time.GetDay();
		}
	}
	else if (token.IsNumeric())
	{
		if (token.GetNumber() > 1000 && token.GetNumber() < 10000)
		{
			// Two possible variants:
			// 1) 2005 3 13
			// 2) 2005 13 3
			// assume first one.
			year = token.GetNumber().GetLo();
			if (!pLine->GetToken(++index, dateMonth))
				return false;
			mayHaveTime = false;
		}
		else
			dateMonth = token;
	}
	else
	{
		if (token.IsLeftNumeric() && (unsigned int)token[token.GetLength() - 1] > 127 &&
			token.GetNumber() > 1000)
		{
			if (token.GetNumber() > 10000)
				return false;

			// Asian date format: 2005xxx 5xx 20xxx with some non-ascii characters following
			year = token.GetNumber().GetLo();
			if (!pLine->GetToken(++index, dateMonth))
				return false;
			mayHaveTime = false;
		}
		else
			dateMonth = token;
	}

	if (!day)
	{
		// Get day field
		if (!pLine->GetToken(++index, token))
			return false;

		int dateDay;

		// Check for non-numeric day
		if (!token.IsNumeric() && !token.IsLeftNumeric())
		{
			int offset = 0;
			if (dateMonth.GetString().Right(1) == _T("."))
				offset++;
			if (!dateMonth.IsNumeric(0, dateMonth.GetLength() - offset))
				return false;
			dateDay = dateMonth.GetNumber(0, dateMonth.GetLength() - offset).GetLo();
			dateMonth = token;
		}
		else
		{
			dateDay = token.GetNumber().GetLo();
			if (token[token.GetLength() - 1] == ',')
				bHasYearAndTime = true;
		}

		if (dateDay < 1 || dateDay > 31)
			return false;
		day = dateDay;
	}

	if (!month)
	{
		wxString strMonth = dateMonth.GetString();
		if (dateMonth.IsLeftNumeric() && (unsigned int)strMonth[strMonth.Length() - 1] > 127)
		{
			// Most likely an Asian server sending some unknown language specific
			// suffix at the end of the monthname. Filter it out.
			int i;
			for (i = strMonth.Length() - 1; i > 0; i--)
			{
				if (strMonth[i] >= '0' && strMonth[i] <= '9')
					break;
			}
			strMonth = strMonth.Left(i + 1);
		}
		// Check month name
		if (strMonth.Right(1) == _T(",") || strMonth.Right(1) == _T("."))
			strMonth.RemoveLast();
		strMonth.MakeLower();
		std::map<wxString, int>::iterator iter = m_MonthNamesMap.find(strMonth);
		if (iter == m_MonthNamesMap.end())
			return false;
		month = iter->second;
	}

	// Get time/year field
	if (!pLine->GetToken(++index, token))
		return false;

	pos = token.Find(_T(":.-"));
	if (pos != -1 && mayHaveTime)
	{
		// token is a time
		if (!pos || static_cast<size_t>(pos) == (token.GetLength() - 1))
			return false;

		wxString str = token.GetString();
		if (!str.Left(pos).ToLong(&hour))
			return false;
		if (!str.Mid(pos + 1).ToLong(&minute))
			return false;

		if (hour < 0 || hour > 23)
			return false;
		if (minute < 0 || minute > 59)
			return false;

		entry.hasTime = true;

		// Some servers use times only for files newer than 6 months
		if (!year)
		{
			year = wxDateTime::Now().GetYear();
			int currentDayOfYear = wxDateTime::Now().GetDay() + 31 * (wxDateTime::Now().GetMonth() - wxDateTime::Jan);
			int fileDayOfYear = (month - 1) * 31 + (day - 1);
			if (currentDayOfYear <= fileDayOfYear)
				year -= 1;
		}
	}
	else if (!year)
	{
		// token is a year
		if (!token.IsNumeric() && !token.IsLeftNumeric())
			return false;

		year = token.GetNumber().GetLo();

		if (year > 3000)
			return false;
		if (year < 1000)
			year += 1900;

		if (bHasYearAndTime)
		{
			if (!pLine->GetToken(++index, token))
				return false;

			if (token.Find(':') == 2 && token.GetLength() == 5 && token.IsLeftNumeric() && token.IsRightNumeric())
			{
				int pos = token.Find(':');
				// token is a time
				if (!pos || static_cast<size_t>(pos) == (token.GetLength() - 1))
					return false;

				wxString str = token.GetString();

				if (!str.Left(pos).ToLong(&hour))
					return false;
				if (!str.Mid(pos + 1).ToLong(&minute))
					return false;

				if (hour < 0 || hour > 23)
					return false;
				if (minute < 0 || minute > 59)
					return false;

				entry.hasTime = true;
			}
			else
			{
				entry.hasTime = false;
				index--;
			}
		}
		else
			entry.hasTime = false;
	}
	else
	{
		entry.hasTime = false;
		index--;
	}

	entry.time = wxDateTime();
	if (!VerifySetDate(entry.time, year, (wxDateTime::Month)(wxDateTime::Jan + month - 1), day, hour, minute))
		return false;
	entry.hasDate = true;

	return true;
}

bool CDirectoryListingParser::ParseShortDate(CToken &token, CDirentry &entry, bool saneFieldOrder /*=false*/)
{
	if (token.GetLength() < 1)
		return false;

	bool gotYear = false;
	bool gotMonth = false;
	bool gotDay = false;
	bool gotMonthName = false;

	int year = 0;
	int month = 0;
	int day = 0;

	int value = 0;

	int pos = token.Find(_T("-./"));
	if (pos < 1)
		return false;

	if (!token.IsNumeric(0, pos))
	{
		// Seems to be monthname-dd-yy

		// Check month name
		wxString dateMonth = token.GetString().Mid(0, pos);
		dateMonth.MakeLower();
		std::map<wxString, int>::iterator iter = m_MonthNamesMap.find(dateMonth);
		if (iter == m_MonthNamesMap.end())
			return false;
		month = iter->second;
		gotMonth = true;
		gotMonthName = true;
	}
	else if (pos == 4)
	{
		// Seems to be yyyy-mm-dd
		year = token.GetNumber(0, pos).GetLo();
		if (year < 1900 || year > 3000)
			return false;
		gotYear = true;
	}
	else if (pos <= 2)
	{
		wxLongLong value = token.GetNumber(0, pos);
		if (token[pos] == '.')
		{
			// Maybe dd.mm.yyyy
			if (value < 1900 || value > 3000)
				return false;
			day = value.GetLo();
			gotDay = true;
		}
		else
		{
			if (saneFieldOrder)
			{
				year = value.GetLo();
				if (year < 50)
					year += 2000;
				else
					year += 1900;
				gotYear = true;
			}
			else
			{
				// Detect mm-dd-yyyy or mm/dd/yyyy and
				// dd-mm-yyyy or dd/mm/yyyy
				if (value < 1)
					return false;
				if (value > 12)
				{
					if (value > 31)
						return false;

					day = value.GetLo();
					gotDay = true;
				}
				else
				{
					month = value.GetLo();
					gotMonth = true;
				}
			}
		}
	}
	else
		return false;

	int pos2 = token.Find(_T("-./"), pos + 1);
	if (pos2 == -1 || (pos2 - pos) == 1)
		return false;
	if (static_cast<size_t>(pos2) == (token.GetLength() - 1))
		return false;

	// If we already got the month and the second field is not numeric,
	// change old month into day and use new token as month
	if (!token.IsNumeric(pos + 1, pos2 - pos - 1) && gotMonth)
	{
		if (gotMonthName)
			return false;

		if (gotDay)
			return false;

		gotDay = true;
		gotMonth = false;
		day = month;
	}

	if (gotYear || gotDay)
	{
		// Month field in yyyy-mm-dd or dd-mm-yyyy
		// Check month name
		wxString dateMonth = token.GetString().Mid(pos + 1, pos2 - pos - 1);
		dateMonth.MakeLower();
		std::map<wxString, int>::iterator iter = m_MonthNamesMap.find(dateMonth);
		if (iter == m_MonthNamesMap.end())
			return false;
		month = iter->second;
		gotMonth = true;
	}
	else
	{
		wxLongLong value = token.GetNumber(pos + 1, pos2 - pos - 1);
		// Day field in mm-dd-yyyy
		if (value < 1 || value > 31)
			return false;
		day = value.GetLo();
		gotDay = true;
	}

	value = token.GetNumber(pos2 + 1, token.GetLength() - pos2 - 1).GetLo();
	if (gotYear)
	{
		// Day field in yyy-mm-dd
		if (!value || value > 31)
			return false;
		day = value;
		gotDay = true;
	}
	else
	{
		if (value < 0)
			return false;

		if (value < 50)
			value += 2000;
		else if (value < 1000)
			value += 1900;
		year = value;

		gotYear = true;
	}

	entry.hasDate = true;

	if (!gotMonth || !gotDay || !gotYear)
		return false;

	entry.time = wxDateTime();
	if (!VerifySetDate(entry.time, year, (wxDateTime::Month)(wxDateTime::Jan + month - 1), day))
		return false;
	entry.hasDate = true;

	return true;
}

bool CDirectoryListingParser::ParseAsDos(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken token;

	// Get first token, has to be a valid date
	if (!pLine->GetToken(index, token))
		return false;

	if (!ParseShortDate(token, entry))
		return false;

	// Extract time
	if (!pLine->GetToken(++index, token))
		return false;

	if (!ParseTime(token, entry))
		return false;

	// If next token is <DIR>, entry is a directory
	// else, it should be the filesize.
	if (!pLine->GetToken(++index, token))
		return false;

	if (token.GetString() == _T("<DIR>"))
	{
		entry.dir = true;
		entry.size = -1;
	}
	else if (token.IsNumeric() || token.IsLeftNumeric())
	{
		// Convert size, filter out separators
		wxLongLong size = 0;
		int len = token.GetLength();
		for (int i = 0; i < len; i++)
		{
			char chr = token[i];
			if (chr == ',' || chr == '.')
				continue;
			if (chr < '0' || chr > '9')
				return false;

			size *= 10;
			size += chr - '0';
		}
		entry.size = size;
		entry.dir = false;
	}
	else
		return false;

	// Extract filename
	if (!pLine->GetToken(++index, token, true))
		return false;
	entry.name = token.GetString();

	entry.link = false;
	entry.target = _T("");
	entry.ownerGroup = _T("");
	entry.permissions = _T("");

	return true;
}

bool CDirectoryListingParser::ParseTime(CToken &token, CDirentry &entry)
{
	if (!entry.hasDate)
		return false;

	int pos = token.Find(':');
	if (pos < 1 || static_cast<unsigned int>(pos) >= (token.GetLength() - 1))
		return false;

	wxLongLong hour = token.GetNumber(0, pos);
	if (hour < 0 || hour > 23)
		return false;

	wxLongLong minute = token.GetNumber(pos + 1, -1);
	if (minute < 0 || minute > 59)
		return false;

	// Convert to 24h format
	if (!token.IsRightNumeric())
	{
		if (token[token.GetLength() - 2] == 'P')
		{
			if (hour < 12)
				hour += 12;
		}
		else
			if (hour == 12)
				hour = 0;
	}

	wxTimeSpan span(hour.GetLo(), minute.GetLo());
	entry.time.Add(span);
	entry.hasTime = true;

	return true;
}

bool CDirectoryListingParser::ParseAsEplf(CLine *pLine, CDirentry &entry)
{
	CToken token;
	if (!pLine->GetToken(0, token, true))
		return false;

	if (token[0] != '+')
		return false;

	int pos = token.Find('\t');
	if (pos == -1 || static_cast<size_t>(pos) == (token.GetLength() - 1))
		return false;

	entry.name = token.GetString().Mid(pos + 1);

	entry.ownerGroup = _T("");
	entry.permissions = _T("");
	entry.dir = false;
	entry.link = false;
	entry.hasDate = false;
	entry.hasTime = false;
	entry.size = -1;

	int fact = 1;
	while (fact < pos)
	{
		int separator = token.Find(',', fact);
		int len;
		if (separator == -1)
			len = pos - fact;
		else
			len = separator - fact;

		if (!len)
		{
			fact++;
			continue;
		}

		char type = token[fact];

		if (type == '/')
			entry.dir = true;
		else if (type == 's')
			entry.size = token.GetNumber(fact + 1, len - 1);
		else if (type == 'm')
		{
			wxLongLong number = token.GetNumber(fact + 1, len - 1);
			if (number < 0)
				return false;
			entry.time = wxDateTime((time_t)number.GetValue());

			entry.hasTime = true;
			entry.hasDate = true;
		}
		else if (type == 'u' && len > 2 && token[fact + 1] == 'p')
			entry.permissions = token.GetString().Mid(fact + 2, len - 2);

		fact += len + 1;
	}

	return true;
}

bool CDirectoryListingParser::ParseAsVms(CLine *pLine, CDirentry &entry)
{
	CToken token;
	int index = 0;

	if (!pLine->GetToken(index, token))
		return false;

	int pos = token.Find(';');
	if (pos == -1)
		return false;

	if (pos > 4 && token.GetString().Mid(pos - 4, 4) == _T(".DIR"))
	{
		entry.dir = true;
		entry.name = token.GetString().Left(pos - 4) + token.GetString().Mid(pos);
	}
	else
	{
		entry.dir = false;
		entry.name = token.GetString();
	}

	if (!pLine->GetToken(++index, token))
		return false;

	entry.ownerGroup = _T("");

	bool gotSize = false;
	// This field can either be the filesize, a username (at least that's what I think) enclosed in [] or a date.
	if (!token.IsNumeric() && !token.IsLeftNumeric())
	{
		// Must be username

		const int len = token.GetLength();
		if (len < 3 || token[0] != '[' || token[len - 1] != ']')
			return false;
		entry.ownerGroup = token.GetString().Mid(1, len - 2);

		if (!pLine->GetToken(++index, token))
			return false;
		if (!token.IsNumeric() && !token.IsLeftNumeric())
			return false;
	}

	// Current token is either size or date
	pos = token.Find('/');
	if (token.IsNumeric() || (pos != -1 && token.Find('/', pos + 1) == -1))
	{
		// Definitely size
		gotSize = true;
		entry.size = token.GetNumber();

		if (!pLine->GetToken(++index, token))
			return false;
	}

	// Get date
	if (!ParseShortDate(token, entry))
		return false;

	// Get time
	if (!pLine->GetToken(++index, token))
		return true;

	if (!ParseTime(token, entry))
	{
		int len = token.GetLength();
		if (token[0] == '[' && token[len - 1] != ']')
			return false;
		if (token[0] == '(' && token[len - 1] != ')')
			return false;
		if (token[0] != '[' && token[len - 1] == ']')
			return false;
		if (token[0] != '(' && token[len - 1] == ')')
			return false;
		entry.hasTime = false;
		index--;
	}

	if (!gotSize)
	{
		// Get size
		if (!pLine->GetToken(++index, token))
			return false;

		if (!token.IsNumeric() && !token.IsLeftNumeric())
			return false;

		entry.size = token.GetNumber();
	}

	// Owner / group and permissions
	entry.permissions = _T("");
	while (pLine->GetToken(++index, token))
	{
		const int len = token.GetLength();
		if (len > 2 && token[0] == '(' && token[len - 1] == ')')
		{
			if (entry.permissions != _T(""))
				entry.permissions += _T(" ");
			entry.permissions += token.GetString().Mid(1, len - 2);
		}
		else if (len > 2 && token[0] == '[' && token[len - 1] == ']')
		{
			if (entry.ownerGroup != _T(""))
				entry.ownerGroup += _T(" ");
			entry.ownerGroup += token.GetString().Mid(1, len - 2);
		}
		else
		{
			if (entry.permissions != _T(""))
				entry.permissions += _T(" ");
			entry.ownerGroup += token.GetString();
		}
	}

	return true;
}

bool CDirectoryListingParser::ParseAsIbm(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken token;

	// Get owner
	if (!pLine->GetToken(index, token))
		return false;

	entry.ownerGroup = token.GetString();

	// Get size
	if (!pLine->GetToken(++index, token))
		return false;

	if (!token.IsNumeric())
		return false;

	entry.size = token.GetNumber();

	// Get date
	if (!pLine->GetToken(++index, token))
		return false;

	if (!ParseShortDate(token, entry))
		return false;

	// Get time
	if (!pLine->GetToken(++index, token))
		return false;

	if (!ParseTime(token, entry))
		return false;

	// Get filename
	if (!pLine->GetToken(index + 2, token, 1))
		return false;

	entry.name = token.GetString();
	if (token[token.GetLength() - 1] == '/')
	{
		entry.name.RemoveLast();
		entry.dir = true;
	}
	else
		entry.dir = false;

	return true;
}

bool CDirectoryListingParser::ParseOther(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken firstToken;

	if (!pLine->GetToken(index, firstToken))
		return false;

	if (!firstToken.IsNumeric())
		return false;

	// Possible formats: Numerical unix, VShell or OS/2

	CToken token;
	if (!pLine->GetToken(++index, token))
		return false;

	// If token is a number, than it's the numerical Unix style format,
	// else it's the VShell, OS/2 or nortel.VxWorks format
	if (token.IsNumeric())
	{
		entry.permissions = firstToken.GetString();
		if (firstToken.GetLength() >= 2 && firstToken[1] == '4')
			entry.dir = true;
		else
			entry.dir = false;

		entry.ownerGroup += token.GetString();

		if (!pLine->GetToken(++index, token))
			return false;

		entry.ownerGroup += _T(" ") + token.GetString();

		// Get size
		if (!pLine->GetToken(++index, token))
			return false;

		if (!token.IsNumeric())
			return false;

		entry.size = token.GetNumber();

		// Get date/time
		if (!pLine->GetToken(++index, token))
			return false;

		wxLongLong number = token.GetNumber();
		if (number < 0)
			return false;
		entry.time = wxDateTime((time_t)number.GetValue());

		entry.hasTime = true;
		entry.hasDate = true;

		// Get filename
		if (!pLine->GetToken(++index, token, true))
			return false;

		entry.name = token.GetString();

		entry.link = false;
		entry.target = _T("");
	}
	else
	{
		// Possible conflict with multiline VMS listings
		if (m_maybeMultilineVms)
			return false;

		// VShell, OS/2 or nortel.VxWorks style format
		entry.size = firstToken.GetNumber();

		// Get date
		wxString dateMonth = token.GetString();
		dateMonth.MakeLower();
		std::map<wxString, int>::const_iterator iter = m_MonthNamesMap.find(dateMonth);
		if (iter == m_MonthNamesMap.end())
		{
			// OS/2 or nortel.VxWorks
			entry.dir = false;
			int skippedCount = 0;
			do
			{
				if (token.GetString() == _T("DIR"))
					entry.dir = true;
				else if (token.Find(_T("-/.")) != -1)
					break;

				skippedCount++;

				if (!pLine->GetToken(++index, token))
					return false;
			} while (true);

			if (!ParseShortDate(token, entry))
				return false;

			// Get time
			if (!pLine->GetToken(++index, token))
				return false;

			if (!ParseTime(token, entry))
				return false;

			// Get filename
			if (!pLine->GetToken(++index, token, true))
				return false;

			entry.name = token.GetString();
			if (!skippedCount && entry.name.Right(5).MakeUpper() == _T("<DIR>"))
			{
				entry.dir = true;
				entry.name = entry.name.Left(entry.name.Length() - 5);
				while (entry.name.Last() == ' ')
					entry.name.RemoveLast();
			}
		}
		else
		{
			int month = iter->second;

			// Get day
			if (!pLine->GetToken(++index, token))
				return false;

			if (!token.IsNumeric() && !token.IsLeftNumeric())
				return false;

			wxLongLong day = token.GetNumber();
			if (day < 0 || day > 31)
				return false;

			// Get Year
			if (!pLine->GetToken(++index, token))
				return false;

			if (!token.IsNumeric())
				return false;

			wxLongLong year = token.GetNumber();
			if (year < 50)
				year += 2000;
			else if (year < 1000)
				year += 1900;

			entry.time = wxDateTime();
			if (!VerifySetDate(entry.time, year.GetLo(), (wxDateTime::Month)(month - 1), day.GetLo()))
				return false;

			entry.hasDate = true;

			// Get time
			if (!pLine->GetToken(++index, token))
				return false;

			if (!ParseTime(token, entry))
				return false;

			// Get filename
			if (!pLine->GetToken(++index, token, 1))
				return false;

			entry.name = token.GetString();
			char chr = token[token.GetLength() - 1];
			if (chr == '/' || chr == '\\')
			{
				entry.dir = true;
				entry.name.RemoveLast();
			}
			else
				entry.dir = false;
		}
		entry.link = false;
		entry.target = _T("");
		entry.ownerGroup = _T("");
		entry.permissions = _T("");
	}

	return true;
}

void CDirectoryListingParser::AddData(char *pData, int len)
{
	t_list item;
	item.p = pData;
	item.len = len;

	m_DataList.push_back(item);

	ParseData(true);
}

void CDirectoryListingParser::AddLine(const wxChar* pLine)
{
	if (m_pControlSocket)
		m_pControlSocket->LogMessageRaw(RawList, pLine);

	while (*pLine == ' ' || *pLine == '\t')
		pLine++;

	if (!*pLine)
		return;

	const int len = wxStrlen(pLine);

	wxChar* p = new wxChar[len + 1];

	wxStrcpy(p, pLine);

	CLine line(p, len);

	ParseLine(&line, m_server.GetType(), false);
}

CLine *CDirectoryListingParser::GetLine(bool breakAtEnd /*=false*/)
{
	while (!m_DataList.empty())
	{
		// Trim empty lines and spaces
		std::list<t_list>::iterator iter = m_DataList.begin();
		int len = iter->len;
		while (iter->p[m_currentOffset]=='\r' || iter->p[m_currentOffset]=='\n' || iter->p[m_currentOffset]==' ' || iter->p[m_currentOffset]=='\t')
		{
			m_currentOffset++;
			if (m_currentOffset >= len)
			{
				delete [] iter->p;
				iter++;
				m_currentOffset = 0;
				if (iter == m_DataList.end())
				{
					m_DataList.clear();
					return 0;
				}
				len = iter->len;
			}
		}
		m_DataList.erase(m_DataList.begin(), iter);
		iter = m_DataList.begin();

		// Remember start offset and find next linebreak
		int startpos = m_currentOffset;
		int reslen = 0;

		int emptylen = 0;

		int currentOffset = m_currentOffset;
		while ((iter->p[currentOffset] != '\n') && (iter->p[currentOffset] != '\r'))
		{
			if (iter->p[currentOffset] != ' ' && iter->p[currentOffset] != '\t')
			{
				reslen += emptylen + 1;
				emptylen = 0;
			}
			else
				emptylen++;

			currentOffset++;
			if (currentOffset >= len)
			{
				iter++;
				if (iter == m_DataList.end())
				{
					if (breakAtEnd)
						return 0;
					break;
				}
				len = iter->len;
				currentOffset = 0;
			}
		}
		m_currentOffset = currentOffset;

		// Reslen is now the length of the line, excluding any terminating whitespace
		char *res = new char[reslen + 1];
		res[reslen] = 0;

		int	respos = 0;

		// Copy line data
		std::list<t_list>::iterator i = m_DataList.begin();
		while (i != iter && reslen)
		{
			int copylen = i->len - startpos;
			if (copylen > reslen)
				copylen = reslen;
			memcpy(&res[respos], &i->p[startpos], copylen);
			reslen -= copylen;
			respos += i->len - startpos;
			startpos = 0;

			delete [] i->p;
			i++;
		}
		// Delete all extra whitespace
		while (i != iter)
		{
			delete [] i->p;
			i++;
		}

		// Copy last chunk
		if (iter != m_DataList.end() && reslen)
		{
			int copylen = m_currentOffset-startpos;
			if (copylen>reslen)
				copylen=reslen;
			memcpy(&res[respos], &iter->p[startpos], copylen);
			if (reslen >= iter->len)
			{
				delete [] iter->p;
				m_DataList.erase(m_DataList.begin(), ++iter);
			}
			else
				m_DataList.erase(m_DataList.begin(), iter);
		}
		else
			m_DataList.erase(m_DataList.begin(), iter);

		wxChar* buffer;
		if (m_pControlSocket)
		{
			buffer = m_pControlSocket->ConvToLocalBuffer(res);
			m_pControlSocket->LogMessageRaw(RawList, buffer);
		}
		else
		{
			wxString str(res, wxConvUTF8);
			if (str == _T(""))
			{
				str = wxString(res, wxConvLocal);
				if (str == _T(""))
					str = wxString(res, wxConvISO8859_1);
			}
			buffer = new wxChar[str.Len() + 1];
			wxStrcpy(buffer, str.c_str());
		}
		delete [] res;

		if (!buffer)
		{
			// Line contained no usable data, start over
			continue;
		}

		return new CLine(buffer);
	}

	return 0;
}

bool CDirectoryListingParser::ParseAsWfFtp(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken token;

	// Get filename
	if (!pLine->GetToken(index++, token))
		return false;

	entry.name = token.GetString();

	// Get filesize
	if (!pLine->GetToken(index++, token))
		return false;

	if (!token.IsNumeric())
		return false;

	entry.size = token.GetNumber();

	// Parse daste
	if (!pLine->GetToken(index++, token))
		return false;

	if (!ParseShortDate(token, entry))
		return false;

	// Unused token
	if (!pLine->GetToken(index++, token))
		return false;

	if (token.GetString().Right(1) != _T("."))
		return false;

	// Parse time
	if (!pLine->GetToken(index++, token, true))
		return false;

	if (!ParseTime(token, entry))
		return false;

	entry.dir = false;
	entry.link = false;
	entry.ownerGroup = _T("");
	entry.permissions = _T("");

	return true;
}

bool CDirectoryListingParser::ParseAsIBM_MVS(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken token;

	// volume
	if (!pLine->GetToken(index++, token))
		return false;

	// unit
	if (!pLine->GetToken(index++, token))
		return false;

	// Referred date
	if (!pLine->GetToken(index++, token))
		return false;

	if (token.GetString() == _T("**NONE**"))
		entry.hasDate = false;
	else if (!ParseShortDate(token, entry))
	{
		// Perhaps of the following type:
		// TSO004 3390 VSAM FOO.BAR
		if (token.GetString() != _T("VSAM"))
			return false;

		if (!pLine->GetToken(index++, token))
			return false;

		entry.name = token.GetString();
		if (entry.name.Find(' ') != -1)
			return false;

		entry.size = -1;
		entry.dir = false;
		entry.ownerGroup = _T("");
		entry.permissions = _T("");
		entry.hasDate = false;
		entry.hasTime = false;
		entry.link = false;

		return true;
	}

	// ext
	if (!pLine->GetToken(index++, token))
		return false;
	if (!token.IsNumeric())
		return false;

	int prevLen = token.GetLength();

	// used
	if (!pLine->GetToken(index++, token))
		return false;
	if (token.IsNumeric())
	{
		// recfm
		if (!pLine->GetToken(index++, token))
			return false;
		if (token.IsNumeric())
			return false;
	}
	else
	{
		if (prevLen < 6)
			return false;
	}

	// lrecl
	if (!pLine->GetToken(index++, token))
		return false;
	if (!token.IsNumeric())
		return false;

	// blksize
	if (!pLine->GetToken(index++, token))
		return false;
	if (!token.IsNumeric())
		return false;

	// dsorg
	if (!pLine->GetToken(index++, token))
		return false;

	if (token.GetString() == _T("PO"))
	{
		entry.dir = true;
		entry.size = -1;
	}
	else
	{
		entry.dir = false;
		entry.size = 100;
	}

	// name of dataset or sequential file
	if (!pLine->GetToken(index++, token, true))
		return false;

	entry.name = token.GetString();

	entry.ownerGroup = _T("");
	entry.permissions = _T("");
	entry.hasTime = false;
	entry.link = false;

	return true;
}

bool CDirectoryListingParser::ParseAsIBM_MVS_PDS(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken token;

	// pds member name
	if (!pLine->GetToken(index++, token))
		return false;
	entry.name = token.GetString();

	// vv.mm
	if (!pLine->GetToken(index++, token))
		return false;

	// creation date
	if (!pLine->GetToken(index++, token))
		return false;
	if (!ParseShortDate(token, entry))
		return false;

	// modification date
	if (!pLine->GetToken(index++, token))
		return false;
	if (!ParseShortDate(token, entry))
		return false;

	// modification time
	if (!pLine->GetToken(index++, token))
		return false;
	if (!ParseTime(token, entry))
		return false;

	// size
	if (!pLine->GetToken(index++, token))
		return false;
	if (!token.IsNumeric())
		return false;
	entry.size = token.GetNumber();

	// init
	if (!pLine->GetToken(index++, token))
		return false;
	if (!token.IsNumeric())
		return false;

	// mod
	if (!pLine->GetToken(index++, token))
		return false;
	if (!token.IsNumeric())
		return false;

	// id
	if (!pLine->GetToken(index++, token, true))
		return false;

	entry.dir = false;
	entry.ownerGroup = _T("");
	entry.permissions = _T("");
	entry.hasTime = false;
	entry.link = false;

	return true;
}

bool CDirectoryListingParser::ParseAsIBM_MVS_Migrated(CLine *pLine, CDirentry &entry)
{
	// Migrated MVS file
	// "Migrated				SOME.NAME"

	int index = 0;
	CToken token;
	if (!pLine->GetToken(index, token))
		return false;

	if (token.GetString().CmpNoCase(_T("Migrated")))
		return false;

	if (!pLine->GetToken(++index, token))
		return false;

	entry.name = token.GetString();

	entry.dir = false;
	entry.link = false;
	entry.ownerGroup = _T("");
	entry.permissions = _T("");
	entry.size = -1;
	entry.hasDate = false;
	entry.hasTime = false;

	if (pLine->GetToken(++index, token))
		return false;

	return true;
}

bool CDirectoryListingParser::ParseAsIBM_MVS_PDS2(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken token;
	if (!pLine->GetToken(index, token))
		return false;

	entry.name = token.GetString();

	entry.dir = false;
	entry.link = false;
	entry.ownerGroup = _T("");
	entry.permissions = _T("");
	entry.size = -1;
	entry.hasDate = false;
	entry.hasTime = false;

	if (!pLine->GetToken(++index, token))
		return true;

	entry.size = token.GetNumber(CToken::hex);
	if (entry.size == -1)
		return false;

	// Unused hexadecimal token
	if (!pLine->GetToken(++index, token))
		return false;
	if (!token.IsNumeric(CToken::hex))
		return false;

	// Unused numeric token
	if (!pLine->GetToken(++index, token))
		return false;
	if (!token.IsNumeric())
		return false;

	int start = ++index;
	while (pLine->GetToken(index, token))
	{
		index++;
	}
	if ((index - start < 2))
		return false;
	index--;

	pLine->GetToken(index, token);
	if (!token.IsNumeric() && (token.GetString() != _T("ANY")))
		return false;

	pLine->GetToken(index - 1, token);
	if (!token.IsNumeric() && (token.GetString() != _T("ANY")))
		return false;

	for (int i = start; i < index - 1; i++)
	{
		pLine->GetToken(i, token);
		int len = token.GetLength();
		for (int j = 0; j < len; j++)
			if (token[j] < 'A' || token[j] > 'Z')
				return false;
	}

	return true;
}

bool CDirectoryListingParser::ParseComplexFileSize(CToken& token, wxLongLong& size)
{
	if (token.IsNumeric())
	{
		size = token.GetNumber();
		return true;
	}

	int len = token.GetLength() - 1;

	char last = token[len];
	if (last == 'B' || last == 'b')
	{
		char c = token[len];
		if (c < '0' || c > '9')
		{
			last = token[len];
			len--;
		}
	}

	size = 0;

	int dot = -1;
	for (int i = 0; i < len; i++)
	{
		char c = token[i];
		if (c >= '0' && c <= '9')
		{
			size *= 10;
			size += c - '0';
		}
		else if (c == '.')
		{
			if (dot != -1)
				return false;
			dot = len - i - 1;
		}
		else
			return false;
	}
	switch (last)
	{
	case 'k':
	case 'K':
		size *= 1024;
		break;
	case 'm':
	case 'M':
		size *= 1024 * 1024;
		break;
	case 'g':
	case 'G':
		size *= 1024 * 1024 * 1024;
		break;
	case 't':
	case 'T':
		size *= 1024 * 1024;
		size *= 1024 * 1024;
		break;
	case 'b':
	case 'B':
		break;
	default:
		return false;
	}
	while (dot-- > 0)
		size /= 10;

	return true;
}

bool CDirectoryListingParser::ParseAsMlsd(CLine *pLine, CDirentry &entry)
{
	// MLSD format as described here: http://www.ietf.org/internet-drafts/draft-ietf-ftpext-mlst-16.txt

	// Parsing is done strict, abort on slightest error.

	CToken token;

	if (!pLine->GetToken(0, token))
		return false;

	wxString facts = token.GetString();
	if (facts == _T(""))
		return false;

	entry.dir = false;
	entry.link = false;
	entry.size = -1;
	entry.hasDate = false;
	entry.hasTime = false;
	entry.ownerGroup = _T("");
	entry.permissions = _T("");

	while (facts != _T(""))
	{
		int delim = facts.Find(';');
		if (delim < 3)
			return false;

		int pos = facts.Find('=');
		if (pos < 1 || (pos + 2) >= delim)
			return false;

		wxString factname = facts.Left(pos).Lower();
		wxString value = facts.Mid(pos + 1, delim - pos - 1);
		if (factname == _T("type"))
		{
			if (!value.CmpNoCase(_T("dir")) ||
				!value.CmpNoCase(_T("cdir")) ||
				!value.CmpNoCase(_T("pdir")))
				entry.dir = true;
		}
		else if (factname == _T("size"))
		{
			entry.size = 0;

			for (unsigned int i = 0; i < value.Len(); i++)
			{
				if (value[i] < '0' || value[i] > '9')
					return false;
				entry.size *= 10;
				entry.size += value[i] - '0';
			}
		}
		else if (factname == _T("modify") ||
			(!entry.hasDate && factname == _T("create")))
		{
			wxDateTime dateTime;
			const wxChar* time = dateTime.ParseFormat(value, _T("%Y%m%d"));

			if (!time)
				return false;

			if (*time)
			{
				if (!dateTime.ParseFormat(time, _T("%H%M"), dateTime))
					return false;
				entry.hasTime = true;
			}
			else
				entry.hasTime = false;

			entry.time = dateTime.FromTimezone(wxDateTime::GMT0);

			entry.hasDate = true;
		}
		else if (factname == _T("perm"))
			entry.permissions = value;

		facts = facts.Mid(delim + 1);
	}

	if (!pLine->GetToken(1, token, true))
		return false;

	entry.name = token.GetString();

	entry.unsure = false;

	return true;
}

bool CDirectoryListingParser::ParseAsOS9(CLine *pLine, CDirentry &entry)
{
	int index = 0;
	CToken token;

	// Get owner
	if (!pLine->GetToken(index++, token))
		return false;

	// Make sure it's number.number
	int pos = token.Find('.');
	if (pos == -1 || !pos || pos == ((int)token.GetLength() - 1))
		return false;

	if (!token.IsNumeric(0, pos))
		return false;

	if (!token.IsNumeric(pos + 1, token.GetLength() - pos - 1))
		return false;

	entry.ownerGroup = token.GetString();

	// Get date
	if (!pLine->GetToken(index++, token))
		return false;

	if (!ParseShortDate(token, entry, true))
		return false;

	// Unused token
	if (!pLine->GetToken(index++, token))
		return false;

	// Get perms
	if (!pLine->GetToken(index++, token))
		return false;

	entry.permissions = token.GetString();

	entry.dir = token[0] == 'd';

	// Unused token
	if (!pLine->GetToken(index++, token))
		return false;

	// Get Size
	if (!pLine->GetToken(index++, token))
		return false;

	if (!token.IsNumeric())
		return false;

	entry.size = token.GetNumber();

	// Filename
	if (!pLine->GetToken(index++, token, true))
		return false;

	entry.name = token.GetString();

	entry.hasTime = false;
	entry.link = false;
	entry.unsure = false;

	return true;
}

void CDirectoryListingParser::Reset()
{
	for (std::list<t_list>::iterator iter = m_DataList.begin(); iter != m_DataList.end(); iter++)
		delete [] iter->p;
	m_DataList.clear();

	delete m_prevLine;
	m_prevLine = 0;

	m_entryList.clear();
	m_fileList.clear();
	m_currentOffset = 0;
	m_fileListOnly = true;
	m_maybeMultilineVms = false;
}


syntax highlighted by Code2HTML, v. 0.9.1