#include "FileZilla.h"
#include "externalipresolver.h"
#include "asynchostresolver.h"
#include "wx/regex.h"

const wxEventType fzEVT_EXTERNALIPRESOLVE = wxNewEventType();

fzExternalIPResolveEvent::fzExternalIPResolveEvent(int id)
	: wxEvent(id, fzEVT_EXTERNALIPRESOLVE)
{
}

wxEvent* fzExternalIPResolveEvent::Clone() const
{
	return new fzExternalIPResolveEvent(GetId());
}

wxString CExternalIPResolver::m_ip;
bool CExternalIPResolver::m_checked = false;

BEGIN_EVENT_TABLE(CExternalIPResolver, wxEvtHandler)
	EVT_FZ_ASYNCHOSTRESOLVE(wxID_ANY, CExternalIPResolver::OnAsyncHostResolver)
	EVT_SOCKET(0, CExternalIPResolver::OnSocketEvent)
END_EVENT_TABLE();

CExternalIPResolver::CExternalIPResolver(wxEvtHandler* handler, int id /*=wxID_ANY*/)
	: m_handler(handler), m_id(id), m_pHostResolver(0)
{
	m_pSocket = 0;
	m_done = false;
	m_pSendBuffer = 0;
	m_sendBufferPos = 0;
	m_pRecvBuffer = 0;
	m_recvBufferPos = 0;

	ResetHttpData(true);
}

CExternalIPResolver::~CExternalIPResolver()
{
	delete [] m_pSendBuffer;
	m_pSendBuffer = 0;
	delete [] m_pRecvBuffer;
	m_pRecvBuffer = 0;

	delete m_pSocket;
	m_pSocket = 0;
	if (m_pHostResolver)
	{
		m_pHostResolver->SetObsolete();
		m_pHostResolver->Wait();
		delete m_pHostResolver;
	}
}

void CExternalIPResolver::GetExternalIP(const wxString& address /*=_T("")*/, bool force /*=false*/)
{
	if (m_checked)
	{
		if (force)
			m_checked = false;
		else
		{
			m_done = true;
			return;
		}
	}

	m_address = address;

	wxString host;
	int pos;
	if ((pos = address.Find(_T("://"))) != -1)
		host = address.Mid(pos + 3);
	else
		host = address;

	if ((pos = host.Find(_T("/"))) != -1)
		host = host.Left(pos);

	wxString hostWithPort = host;

	if ((pos = host.Find(':', true)) != -1)
	{
		wxString port = host.Mid(pos + 1);
		if (!port.ToULong(&m_port) || m_port < 1 || m_port > 65535)
			m_port = 80;
		host = host.Left(pos);
	}
	else
		m_port = 80;

	if (host == _T(""))
	{
		m_done = true;
		return;
	}

	m_pHostResolver = new CAsyncHostResolver(this, host);
	m_pHostResolver->Create();
	m_pHostResolver->Run();

	wxString buffer = wxString::Format(_T("GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s\r\nConnection: close\r\n\r\n"), address.c_str(), hostWithPort.c_str(), wxString(PACKAGE_STRING, wxConvLocal).c_str());
	m_pSendBuffer = new char[strlen(buffer.mb_str()) + 1];
	strcpy(m_pSendBuffer, buffer.mb_str());
}

void CExternalIPResolver::OnAsyncHostResolver(fzAsyncHostResolveEvent& event)
{
	if (!m_pHostResolver)
		return;

	if (!m_pHostResolver->Done())
		return;

	if (!m_pHostResolver->Successful())
	{
		Close(false);
		return;
	}

	m_pHostResolver->Wait();

	if (m_pSocket)
	{
		delete m_pHostResolver;
		m_pHostResolver = 0;
		return;
	}

	m_pSocket = new wxSocketClient(wxSOCKET_NOWAIT);
	m_pSocket->SetEventHandler(*this, 0);
	m_pSocket->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_OUTPUT_FLAG | wxSOCKET_CONNECTION_FLAG | wxSOCKET_LOST_FLAG);
	m_pSocket->Notify(true);

	m_pHostResolver->m_Address.Service(m_port);
	m_pSocket->Connect(m_pHostResolver->m_Address, false);

	delete m_pHostResolver;
	m_pHostResolver = 0;
}

void CExternalIPResolver::OnSocketEvent(wxSocketEvent& event)
{
	if (!m_pSocket)
		return;

	switch (event.GetSocketEvent())
	{
	case wxSOCKET_INPUT:
		OnReceive();
		break;
	case wxSOCKET_CONNECTION:
		OnConnect();
		break;
	case wxSOCKET_LOST:
		OnClose();
		break;
	case wxSOCKET_OUTPUT:
		OnSend();
		break;
	}

}

void CExternalIPResolver::OnConnect()
{
}

void CExternalIPResolver::OnClose()
{
	if (m_data != _T(""))
		OnData(0, 0);
	else
		Close(false);
}

void CExternalIPResolver::OnReceive()
{
	if (!m_pRecvBuffer)
	{
		m_pRecvBuffer = new char[m_recvBufferLen];
		m_recvBufferPos = 0;
	}

	if (m_pSendBuffer)
		return;

	unsigned int len = m_recvBufferLen - m_recvBufferPos;
	m_pSocket->Read(m_pRecvBuffer + m_recvBufferPos, len);
	if (m_pSocket->Error())
	{
		if (m_pSocket->LastError() != wxSOCKET_WOULDBLOCK)
		{
			Close(false);
		}
		return;
	}

	int read;
	if (!(read = m_pSocket->LastCount()))
	{
		Close(false);
		return;
	}

	if (m_finished)
	{
		// Just ignore all further data
		m_recvBufferPos = 0;
		return;
	}

	m_recvBufferPos += read;

	if (!m_gotHeader)
		OnHeader();
	else
	{
		if (m_transferEncoding == chunked)
			OnChunkedData();
		else
			OnData(m_pRecvBuffer, m_recvBufferPos);
	}
}

void CExternalIPResolver::OnSend()
{
	if (!m_pSendBuffer)
		return;

	unsigned int len = strlen(m_pSendBuffer + m_sendBufferPos);
	m_pSocket->Write(m_pSendBuffer + m_sendBufferPos, len);
	if (m_pSocket->Error())
	{
		if (m_pSocket->LastError() != wxSOCKET_WOULDBLOCK)
		{
			Close(false);
		}
		return;
	}

	unsigned int written;
	if (!(written = m_pSocket->LastCount()))
	{
		Close(false);
		return;
	}

	if (written == len)
	{
		delete [] m_pSendBuffer;
		m_pSendBuffer = 0;

		OnReceive();
	}
	else
		m_sendBufferPos += written;
}

void CExternalIPResolver::Close(bool successful)
{
	delete [] m_pSendBuffer;
	m_pSendBuffer = 0;

	delete [] m_pRecvBuffer;
	m_pRecvBuffer = 0;

	delete m_pSocket;
	m_pSocket = 0;

	if (m_done)
		return;

	m_done = true;

	if (!successful)
		m_ip = _T("");
	m_checked = true;

	if (m_handler)
	{
		fzExternalIPResolveEvent event;
		wxPostEvent(m_handler, event);
		m_handler = 0;
	}
}

void CExternalIPResolver::OnHeader()
{
	// Parse the HTTP header.
	// We do just the neccessary parsing and silently ignore most header fields
	// Redirects are supported though if the server sends the Location field.

	while (true)
	{
		// Find line ending
		unsigned int i = 0;
		for (i = 0; (i + 1) < m_recvBufferPos; i++)
		{
			if (m_pRecvBuffer[i] == '\r')
			{
				if (m_pRecvBuffer[i + 1] != '\n')
				{
					Close(false);
					return;
				}
				break;
			}
		}
		if ((i + 1) >= m_recvBufferPos)
		{
			if (m_recvBufferPos == m_recvBufferLen)
			{
				// We don't support header lines larger than 4096
				Close(false);
				return;
			}
			return;
		}

		m_pRecvBuffer[i] = 0;

		if (!m_responseCode)
		{
			m_responseString = wxString(m_pRecvBuffer, wxConvLocal);
			if (m_recvBufferPos < 16 || memcmp(m_pRecvBuffer, "HTTP/1.", 7))
			{
				// Invalid HTTP Status-Line
				Close(false);
				return;
			}

			if (m_pRecvBuffer[9] < '1' || m_pRecvBuffer[9] > '5' ||
				m_pRecvBuffer[10] < '0' || m_pRecvBuffer[10] > '9' ||
				m_pRecvBuffer[11] < '0' || m_pRecvBuffer[11] > '9')
			{
				// Invalid response code
				Close(false);
				return;
			}

			m_responseCode = (m_pRecvBuffer[9] - '0') * 100 + (m_pRecvBuffer[10] - '0') * 10 + m_pRecvBuffer[11] - '0';

			if (m_responseCode >= 400)
			{
				// Failed request
				Close(false);
				return;
			}

			if (m_responseCode == 305)
			{
				// Unsupported redirect
				Close(false);
				return;
			}
		}
		else
		{
			if (!i)
			{
				// End of header, data from now on

				// Redirect if neccessary
				if (m_responseCode >= 300)
				{
					delete m_pSocket;
					m_pSocket = 0;

					delete [] m_pRecvBuffer;
					m_pRecvBuffer = 0;

					wxString location = m_location;

					ResetHttpData(false);

					GetExternalIP(location);
					return;
				}

				m_gotHeader = true;

				memmove(m_pRecvBuffer, m_pRecvBuffer + 2, m_recvBufferPos - 2);
				m_recvBufferPos -= 2;
				if (m_recvBufferPos)
				{
					if (m_transferEncoding == chunked)
						OnChunkedData();
					else
						OnData(m_pRecvBuffer, m_recvBufferPos);
				}
				return;
			}
			if (m_recvBufferPos > 12 && !memcmp(m_pRecvBuffer, "Location: ", 10))
			{
				m_location = wxString(m_pRecvBuffer + 10, wxConvLocal);
			}
			else if (m_recvBufferPos > 21 && !memcmp(m_pRecvBuffer, "Transfer-Encoding: ", 19))
			{
				if (!strcmp(m_pRecvBuffer + 19, "chunked"))
					m_transferEncoding = chunked;
				else if (!strcmp(m_pRecvBuffer + 19, "identity"))
					m_transferEncoding = identity;
				else
					m_transferEncoding = unknown;
			}
		}

		memmove(m_pRecvBuffer, m_pRecvBuffer + i + 2, m_recvBufferPos - i - 2);
		m_recvBufferPos -= i + 2;

		if (!m_recvBufferPos)
			break;
	}
}

void CExternalIPResolver::OnData(char* buffer, unsigned int len)
{
	if (buffer)
	{
		unsigned int i;
		for (i = 0; i < len; i++)
		{
			if (buffer[i] == '\r' || buffer[i] == '\n')
				break;
		}

		if (i)
			m_data += wxString(buffer, wxConvLocal, i);

		if (i == len)
			return;
	}

	// Validate ip address
	wxString digit = _T("0*[0-9]{1,3}");
	const wxChar* dot = _T("\\.");
	wxString exp = _T("(^|[^\\.[:digit:]])(") + digit + dot + digit + dot + digit + dot + digit + _T(")([^\\.[:digit:]]|$)");
	wxRegEx regex;
	regex.Compile(exp);

	if (!regex.Matches(m_data))
	{
		Close(false);
		return;
	}

	m_ip = regex.GetMatch(m_data, 2);
	Close(true);
}

void CExternalIPResolver::ResetHttpData(bool resetRedirectCount)
{
	m_gotHeader = false;
	m_location = _T("");
	m_responseCode = 0;
	m_responseString = _T("");
	if (resetRedirectCount)
		m_redirectCount = 0;

	m_transferEncoding = unknown;

	m_chunkData.getTrailer = false;
	m_chunkData.size = 0;
	m_chunkData.terminateChunk = false;

	m_finished = false;
}

void CExternalIPResolver::OnChunkedData()
{
	char* p = m_pRecvBuffer;
	unsigned int len = m_recvBufferPos;

	while (true)
	{
		if (m_chunkData.size != 0)
		{
			unsigned int dataLen = len;
			if (m_chunkData.size < len)
				dataLen = m_chunkData.size.GetLo();
			OnData(p, dataLen);
			if (!m_pRecvBuffer)
				return;

            m_chunkData.size -= dataLen;
			p += dataLen;
			len -= dataLen;

			if (m_chunkData.size == 0)
				m_chunkData.terminateChunk = true;

			if (!len)
				break;
		}

		// Find line ending
		unsigned int i = 0;
		for (i = 0; (i + 1) < len; i++)
		{
			if (p[i] == '\r')
			{
				if (p[i + 1] != '\n')
				{
					Close(false);
					return;
				}
				break;
			}
		}
		if ((i + 1) >= len)
		{
			if (len == m_recvBufferLen)
			{
				// We don't support lines larger than 4096
				Close(false);
				return;
			}
			break;
		}

		p[i] = 0;

		if (m_chunkData.terminateChunk)
		{
			if (i)
			{
				// Chunk has to end with CRLF
				Close(false);
				return;
			}
			m_chunkData.terminateChunk = false;
		}
		else if (m_chunkData.getTrailer)
		{
			if (!i)
			{
				m_finished = true;
				m_recvBufferPos = 0;
				return;
			}

			// Ignore the trailer
		}
		else
		{
			// Read chunk size
			char* q = p;
			while (*q)
			{
				if (*q >= '0' && *q <= '9')
				{
					m_chunkData.size *= 16;
					m_chunkData.size += *q - '0';
				}
				else if (*q >= 'A' && *q <= 'F')
				{
					m_chunkData.size *= 10;
					m_chunkData.size += *q - 'A' + 10;
				}
				else if (*q >= 'a' && *q <= 'f')
				{
					m_chunkData.size *= 10;
					m_chunkData.size += *q - 'a' + 10;
				}
				else if (*q == ';' || *q == ' ')
					break;
				else
				{
					// Invalid size
					Close(false);
					return;
				}
				q++;
			}
			if (m_chunkData.size == 0)
				m_chunkData.getTrailer = true;
		}

		p += i + 2;
		len -= i + 2;

		if (!len)
			break;
	}

	if (p != m_pRecvBuffer)
	{
		memmove(m_pRecvBuffer, p, len);
		m_recvBufferPos = len;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1