//this file is part of eMule
//Copyright (C)2002-2008 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.net") / http://www.emule-project.net )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#include "stdafx.h"
#include <share.h>
#include <fcntl.h>
#include <io.h>
#include "emule.h"
#include "IPFilter.h"
#include "OtherFunctions.h"
#include "StringConversion.h"
#include "Preferences.h"
#include "emuledlg.h"
#include "Log.h"

// Contrib - IPFilter-Update
//>>> shadow2004::IPFilter-Update [Morph]
#include "HttpDownloadDlg.h"
#include "ZipFile.h"
//<<< shadow2004::IPFilter-Update [Morph]
// <-------- IPFilter-Update

// Contrib - ed2k updates
//>>> shadow2004::ed2k updates [WiZaRd]
#include "./EMF/extractfile.h"
#include "ED2KLink.h"
#include "DownloadQueue.h"
#include "PartFile.h"
#include "TransferWnd.h"
#include "SharedFileList.h"
//<<< shadow2004::ed2k updates [WiZaRd]
// <-------- ed2k updates

// Contrib - optimization
#include "Statistics.h" //>>> WiZaRd::Optimization
// <-------- optimization

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


#define	DFLT_FILTER_LEVEL	100 // default filter level if non specified

CIPFilter::CIPFilter()
{
	m_pLastHit = NULL;
	m_bModified = false;
	LoadFromDefaultFile(false);
}

CIPFilter::~CIPFilter()
{
	if (m_bModified)
	{
		try{

// Contrib - ed2k updates
//>>> shadow2004::ed2k updates [WiZaRd]
/*
			SaveToDefaultFile();
*/
			SaveToFile();
//<<< shadow2004::ed2k updates [WiZaRd]
// <-------- ed2k updates

		}
		catch(CString){
		}
	}
	RemoveAllIPFilters();
}

static int __cdecl CmpSIPFilterByStartAddr(const void* p1, const void* p2)
{
	const SIPFilter* rng1 = *(SIPFilter**)p1;
	const SIPFilter* rng2 = *(SIPFilter**)p2;
	return CompareUnsigned(rng1->start, rng2->start);
}

CString CIPFilter::GetDefaultFilePath() const
{

// Contrib - AddonDirectory
//>>> shadow2004::AddonDirectory
/*
	return thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + DFLT_IPFILTER_FILENAME;
*/
	return thePrefs.GetMuleDirectory(EMULE_ADDONDIR) + DFLT_IPFILTER_FILENAME;
//<<< shadow2004::AddonDirectory
// <-------- AddonDirectory

}

int CIPFilter::LoadFromDefaultFile(bool bShowResponse)
{
	RemoveAllIPFilters();

// Contrib - AddonDirectory
//>>> WiZaRd::AddonDirectory
	//move file from config to addon dir
	if(!PathFileExists(GetDefaultFilePath()) && PathFileExists(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + DFLT_IPFILTER_FILENAME))
		::MoveFile(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + DFLT_IPFILTER_FILENAME, GetDefaultFilePath());
//<<< WiZaRd::AddonDirectory
// <-------- AddonDirectory

// Contrib - static ip filter
	// ==> Static IP Filter - Stulle
	AddFromFile2(GetDefaultStaticFilePath());
	// <== Static IP Filter - Stulle
// <-------- static ip filter

	return AddFromFile(GetDefaultFilePath(), bShowResponse);
}

int CIPFilter::AddFromFile(LPCTSTR pszFilePath, bool bShowResponse)
{
	DWORD dwStart = GetTickCount();
	FILE* readFile = _tfsopen(pszFilePath, _T("r"), _SH_DENYWR);
	if (readFile != NULL)
	{
		enum EIPFilterFileType
		{
			Unknown = 0,
			FilterDat = 1,		// ipfilter.dat/ip.prefix format
			PeerGuardian = 2,	// PeerGuardian text format
			PeerGuardian2 = 3	// PeerGuardian binary format
		} eFileType = Unknown;

		setvbuf(readFile, NULL, _IOFBF, 32768);

		TCHAR szNam[_MAX_FNAME];
		TCHAR szExt[_MAX_EXT];
		_tsplitpath(pszFilePath, NULL, NULL, szNam, szExt);
		if (_tcsicmp(szExt, _T(".p2p")) == 0 || (_tcsicmp(szNam, _T("guarding.p2p")) == 0 && _tcsicmp(szExt, _T(".txt")) == 0))
			eFileType = PeerGuardian;
		else if (_tcsicmp(szExt, _T(".prefix")) == 0)
			eFileType = FilterDat;
		else
		{
			VERIFY( _setmode(_fileno(readFile), _O_BINARY) != -1 );
			static const BYTE _aucP2Bheader[] = "\xFF\xFF\xFF\xFFP2B";
			BYTE aucHeader[sizeof _aucP2Bheader - 1];
			if (fread(aucHeader, sizeof aucHeader, 1, readFile) == 1)
			{
				if (memcmp(aucHeader, _aucP2Bheader, sizeof _aucP2Bheader - 1)==0)
					eFileType = PeerGuardian2;
				else
				{
					(void)fseek(readFile, 0, SEEK_SET);
					VERIFY( _setmode(_fileno(readFile), _O_TEXT) != -1 ); // ugly!
				}
			}
		}

		int iFoundRanges = 0;
		int iLine = 0;
		if (eFileType == PeerGuardian2)
		{
			// Version 1: strings are ISO-8859-1 encoded
			// Version 2: strings are UTF-8 encoded
			uint8 nVersion;
			if (fread(&nVersion, sizeof nVersion, 1, readFile)==1 && (nVersion==1 || nVersion==2))
			{
				while (!feof(readFile))
				{
					CHAR szName[256];
					int iLen = 0;
					for (;;) // read until NUL or EOF
					{
						int iChar = getc(readFile);
						if (iChar == EOF)
							break;
						if (iLen < sizeof szName - 1)
							szName[iLen++] = (CHAR)iChar;
						if (iChar == '\0')
							break;
					}
					szName[iLen] = '\0';

					uint32 uStart;
					if (fread(&uStart, sizeof uStart, 1, readFile) != 1)
						break;
					uStart = ntohl(uStart);

					uint32 uEnd;
					if (fread(&uEnd, sizeof uEnd, 1, readFile) != 1)
						break;
					uEnd = ntohl(uEnd);

					iLine++;
					// (nVersion == 2) ? OptUtf8ToStr(szName, iLen) :
					AddIPRange(uStart, uEnd, DFLT_FILTER_LEVEL, CStringA(szName, iLen));
					iFoundRanges++;
				}
			}
		}
		else
		{
			CStringA sbuffer;
			CHAR szBuffer[1024];
			while (fgets(szBuffer, _countof(szBuffer), readFile) != NULL)
			{
				iLine++;
				sbuffer = szBuffer;

				// ignore comments & too short lines
				if (sbuffer.GetAt(0) == '#' || sbuffer.GetAt(0) == '/' || sbuffer.GetLength() < 5) {
					sbuffer.Trim(" \t\r\n");
					DEBUG_ONLY( (!sbuffer.IsEmpty()) ? TRACE("IP filter: ignored line %u\n", iLine) : 0 );
					continue;
				}

				if (eFileType == Unknown)
				{
					// looks like html
					if (sbuffer.Find('>') > -1 && sbuffer.Find('<') > -1)
						sbuffer.Delete(0, sbuffer.ReverseFind('>') + 1);

					// check for <IP> - <IP> at start of line
					UINT u1, u2, u3, u4, u5, u6, u7, u8;
					if (sscanf(sbuffer, "%u.%u.%u.%u - %u.%u.%u.%u", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) == 8)
					{
						eFileType = FilterDat;
					}
					else
					{
						// check for <description> ':' <IP> '-' <IP>
						int iColon = sbuffer.Find(':');
						if (iColon > -1)
						{
							CStringA strIPRange = sbuffer.Mid(iColon + 1);
							UINT u1, u2, u3, u4, u5, u6, u7, u8;
							if (sscanf(strIPRange, "%u.%u.%u.%u - %u.%u.%u.%u", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) == 8)
							{
								eFileType = PeerGuardian;
							}
						}
					}
				}

				bool bValid = false;
				uint32 start = 0;
				uint32 end = 0;
				UINT level = 0;
				CStringA desc;
				if (eFileType == FilterDat)
					bValid = ParseFilterLine1(sbuffer, start, end, level, desc);
				else if (eFileType == PeerGuardian)
					bValid = ParseFilterLine2(sbuffer, start, end, level, desc);

				// add a filter
				if (bValid)
				{
					AddIPRange(start, end, level, desc);
					iFoundRanges++;
				}
				else
				{
					sbuffer.Trim(" \t\r\n");
					DEBUG_ONLY( (!sbuffer.IsEmpty()) ? TRACE("IP filter: ignored line %u\n", iLine) : 0 );
				}
			}
		}
		fclose(readFile);

		// sort the IP filter list by IP range start addresses
		qsort(m_iplist.GetData(), m_iplist.GetCount(), sizeof(m_iplist[0]), CmpSIPFilterByStartAddr);

		// merge overlapping and adjacent filter ranges
		int iDuplicate = 0;
		int iMerged = 0;
		if (m_iplist.GetCount() >= 2)
		{
			// On large IP-filter lists there is a noticeable performance problem when merging the list.
			// The 'CIPFilterArray::RemoveAt' call is way too expensive to get called during the merging,
			// thus we use temporary helper arrays to copy only the entries into the final list which
			// are not get deleted.

			// Reserve a byte array (its used as a boolean array actually) as large as the current
			// IP-filter list, so we can set a 'to delete' flag for each entry in the current IP-filter list.
			char* pcToDelete = new char[m_iplist.GetCount()];
			memset(pcToDelete, 0, m_iplist.GetCount());
			int iNumToDelete = 0;

			SIPFilter* pPrv = m_iplist[0];
			int i = 1;
			while (i < m_iplist.GetCount())
			{
				SIPFilter* pCur = m_iplist[i];
				if (   pCur->start >= pPrv->start && pCur->start <= pPrv->end	 // overlapping
					|| pCur->start == pPrv->end+1 && pCur->level == pPrv->level) // adjacent
				{
					if (pCur->start != pPrv->start || pCur->end != pPrv->end) // don't merge identical entries
					{
						//TODO: not yet handled, overlapping entries with different 'level'
						if (pCur->end > pPrv->end)
							pPrv->end = pCur->end;
						//pPrv->desc += _T("; ") + pCur->desc; // this may create a very very long description string...
						iMerged++;
					}
					else
					{
						// if we have identical entries, use the lowest 'level'
						if (pCur->level < pPrv->level)
							pPrv->level = pCur->level;
						iDuplicate++;
					}
					delete pCur;
					//m_iplist.RemoveAt(i);	// way too expensive (read above)
					pcToDelete[i] = 1;		// mark this entry as 'to delete'
					iNumToDelete++;
					i++;
					continue;
				}
				pPrv = pCur;
				i++;
			}

			// Create new IP-filter list which contains only the entries from the original IP-filter list
			// which are not to be deleted.
			if (iNumToDelete > 0)
			{
				CIPFilterArray newList;
				newList.SetSize(m_iplist.GetCount() - iNumToDelete);
				int iNewListIndex = 0;
				for (int i = 0; i < m_iplist.GetCount(); i++) {
					if (!pcToDelete[i])
						newList[iNewListIndex++] = m_iplist[i];
				}
				ASSERT( iNewListIndex == newList.GetSize() );

				// Replace current list with new list. Dump, but still fast enough (only 1 memcpy)
				m_iplist.RemoveAll();
				m_iplist.Append(newList);
				newList.RemoveAll();
				m_bModified = true;
			}
			delete[] pcToDelete;
		}

		if (thePrefs.GetVerbose())
		{
			DWORD dwEnd = GetTickCount();
			AddDebugLogLine(false, _T("Loaded IP filters from \"%s\""), pszFilePath);
			AddDebugLogLine(false, _T("Parsed lines/entries:%u  Found IP ranges:%u  Duplicate:%u  Merged:%u  Time:%s"), iLine, iFoundRanges, iDuplicate, iMerged, CastSecondsToHM((dwEnd-dwStart+500)/1000));
		}
		AddLogLine(bShowResponse, GetResString(IDS_IPFILTERLOADED), m_iplist.GetCount());
	}
	return m_iplist.GetCount();
}

// Contrib - ed2k updates
//>>> shadow2004::ed2k updates [WiZaRd]
/*
void CIPFilter::SaveToDefaultFile()
*/
void CIPFilter::SaveToFile(const CString& path)
//<<< shadow2004::ed2k updates [WiZaRd]
// <-------- ed2k updates

{

// Contrib - AddonDirectory
//>>> WiZaRd::AddonDirectory
/*
	CString strFilePath = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + DFLT_IPFILTER_FILENAME;
*/
// Contrib - ed2k updates
//>>> shadow2004::ed2k updates [WiZaRd]
	CString strFilePath = path;
	if(strFilePath.IsEmpty())
//<<< shadow2004::ed2k updates [WiZaRd]
// <-------- ed2k updates

	    strFilePath = GetDefaultFilePath();
//<<< WiZaRd::AddonDirectory
// <-------- AddonDirectory

	FILE* fp = _tfsopen(strFilePath, _T("wt"), _SH_DENYWR);
	if (fp != NULL)
	{
		for (int i = 0; i < m_iplist.GetCount(); i++)
		{
			const SIPFilter* flt = m_iplist[i];

			CHAR szStart[16];
			ipstrA(szStart, _countof(szStart), htonl(flt->start));

			CHAR szEnd[16];
			ipstrA(szEnd, _countof(szEnd), htonl(flt->end));

			if (fprintf(fp, "%-15s - %-15s , %3u , %s\n", szStart, szEnd, flt->level, flt->desc) == 0 || ferror(fp))
			{
				CString strError;
				strError.Format(_T("Failed to save IP filter to file \"%s\" - %s"), strFilePath, _tcserror(errno));
				throw strError;
			}
		}
		fclose(fp);
		m_bModified = false;
	}
	else
	{
		CString strError;
		strError.Format(_T("Failed to save IP filter to file \"%s\" - %s"), strFilePath, _tcserror(errno));
		throw strError;
	}
}

bool CIPFilter::ParseFilterLine1(const CStringA& sbuffer, uint32& ip1, uint32& ip2, UINT& level, CStringA& desc) const
{
	UINT u1, u2, u3, u4, u5, u6, u7, u8, uLevel = DFLT_FILTER_LEVEL;
	int iDescStart = 0;
	int iItems = sscanf(sbuffer, "%u.%u.%u.%u - %u.%u.%u.%u , %u , %n", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8, &uLevel, &iDescStart);
	if (iItems < 8)
		return false;

	((BYTE*)&ip1)[0] = (BYTE)u4;
	((BYTE*)&ip1)[1] = (BYTE)u3;
	((BYTE*)&ip1)[2] = (BYTE)u2;
	((BYTE*)&ip1)[3] = (BYTE)u1;

	((BYTE*)&ip2)[0] = (BYTE)u8;
	((BYTE*)&ip2)[1] = (BYTE)u7;
	((BYTE*)&ip2)[2] = (BYTE)u6;
	((BYTE*)&ip2)[3] = (BYTE)u5;

	if (iItems == 8) {
		level = DFLT_FILTER_LEVEL;	// set default level
		return true;
	}

	level = uLevel;

	if (iDescStart > 0)
	{
		LPCSTR pszDescStart = (LPCSTR)sbuffer + iDescStart;
		int iDescLen = sbuffer.GetLength() - iDescStart;
		if (iDescLen > 0) {
			if (*(pszDescStart + iDescLen - 1) == '\n')
				--iDescLen;
		}
		memcpy(desc.GetBuffer(iDescLen), pszDescStart, iDescLen * sizeof(pszDescStart[0]));
		desc.ReleaseBuffer(iDescLen);
	}

	return true;
}

bool CIPFilter::ParseFilterLine2(const CStringA& sbuffer, uint32& ip1, uint32& ip2, UINT& level, CStringA& desc) const
{
	int iPos = sbuffer.ReverseFind(':');
	if (iPos < 0)
		return false;

	desc = sbuffer.Left(iPos);
	desc.Replace("PGIPDB", "");
	desc.Trim();

	CStringA strIPRange = sbuffer.Mid(iPos + 1, sbuffer.GetLength() - iPos);
	UINT u1, u2, u3, u4, u5, u6, u7, u8;
	if (sscanf(strIPRange, "%u.%u.%u.%u - %u.%u.%u.%u", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) != 8)
		return false;

	((BYTE*)&ip1)[0] = (BYTE)u4;
	((BYTE*)&ip1)[1] = (BYTE)u3;
	((BYTE*)&ip1)[2] = (BYTE)u2;
	((BYTE*)&ip1)[3] = (BYTE)u1;

	((BYTE*)&ip2)[0] = (BYTE)u8;
	((BYTE*)&ip2)[1] = (BYTE)u7;
	((BYTE*)&ip2)[2] = (BYTE)u6;
	((BYTE*)&ip2)[3] = (BYTE)u5;

	level = DFLT_FILTER_LEVEL;

	return true;
}

void CIPFilter::RemoveAllIPFilters()
{
	for (int i = 0; i < m_iplist.GetCount(); i++)
	{

// Contrib - fix
/*
		delete m_iplist[i];
*/
		//Xman Code Fix: deleting the description-String can throw an exception
		try
		{
			delete m_iplist[i];
		}
		catch(...)
		{
			//nothing
		}
// <-------- fix

	}
	m_iplist.RemoveAll();
	m_pLastHit = NULL;
}

bool CIPFilter::IsFiltered(uint32 ip) /*const*/
{
	return IsFiltered(ip, thePrefs.GetIPFilterLevel());
}

static int __cdecl CmpSIPFilterByAddr(const void* pvKey, const void* pvElement)
{
	uint32 ip = *(uint32*)pvKey;
	const SIPFilter* pIPFilter = *(SIPFilter**)pvElement;

	if (ip < pIPFilter->start)
		return -1;
	if (ip > pIPFilter->end)
		return 1;
	return 0;
}

bool CIPFilter::IsFiltered(uint32 ip, UINT level) /*const*/
{
	if (m_iplist.GetCount() == 0 || ip == 0)
		return false;

	ip = htonl(ip);

	// to speed things up we use a binary search
	//	*)	the IP filter list must be sorted by IP range start addresses
	//	*)	the IP filter list is not allowed to contain overlapping IP ranges (see also the IP range merging code when
	//		loading the list)
	//	*)	the filter 'level' is ignored during the binary search and is evaluated only for the found element
	//
	// TODO: this can still be improved even more:
	//	*)	use a pre assembled list of IP ranges which contains only the IP ranges for the currently used filter level
	//	*)	use a dumb plain array for storing the IP range structures. this will give more cach hits when processing
	//		the list. but(!) this would require to also use a dumb SIPFilter structure (don't use data items with ctors).
	//		otherwise the creation of the array would be rather slow.
	SIPFilter** ppFound = (SIPFilter**)bsearch(&ip, m_iplist.GetData(), m_iplist.GetCount(), sizeof(m_iplist[0]), CmpSIPFilterByAddr);
	if (ppFound && (*ppFound)->level < level)
	{
		(*ppFound)->hits++;
		m_pLastHit = *ppFound;

// Contrib - optimization
		theStats.filteredclients++; //>>> WiZaRd::Optimization
// <-------- optimization

		return true;
	}

	return false;
}

CString CIPFilter::GetLastHit() const
{
	return m_pLastHit ? CString(m_pLastHit->desc) : _T("Not available");
}

const CIPFilterArray& CIPFilter::GetIPFilter() const
{
	return m_iplist;
}

bool CIPFilter::RemoveIPFilter(const SIPFilter* pFilter)
{
	for (int i = 0; i < m_iplist.GetCount(); i++)
	{
		if (m_iplist[i] == pFilter)
		{

// Contrib - fix
/*
			delete m_iplist[i];
*/
			//Xman Code Fix: deleting the description-String can throw an exception
			try
			{
				delete m_iplist[i];
			}
			catch(...)
			{
				//nothing
			}
// <-------- fix

			m_iplist.RemoveAt(i);
			return true;
		}
	}
	return false;
}

// Contrib - IPFilter-Update
//>>> shadow2004::IPFilter-Update [Morph]
//>>> shadow2004::ed2k updates [WiZaRd]
/*
void CIPFilter::UpdateIPFilterURL()
*/
void CIPFilter::UpdateIPFilterURL(const CString& m_sURL)
//<<< shadow2004::ed2k updates [WiZaRd]
{
	CString sbuffer;
//>>> shadow2004::ed2k updates [WiZaRd]
	CString strURL;
	if(m_sURL.IsEmpty())
		strURL = thePrefs.GetUpdateURLIPFilter();
	else
		strURL = m_sURL;
//	CString strURL = thePrefs.GetUpdateURLIPFilter();
//<<< shadow2004::ed2k updates [WiZaRd]
	strURL.TrimRight(_T(".txt"));
	strURL.TrimRight(_T(".dat"));
	strURL.TrimRight(_T(".zip"));
	strURL.Append(_T(".txt"));

	TCHAR szTempFilePath[_MAX_PATH];
//>>> shadow2004::ed2k updates [WiZaRd]
	_tmakepathlimit(szTempFilePath, NULL, thePrefs.GetMuleDirectory(EMULE_ADDONDIR), DFLT_IPFILTER_FILENAME, _T("tmp"));	//>>> shadow2004::AddonDirectory
//	FILE* readFile= _tfsopen(szTempFilePath, _T("r"), _SH_DENYWR);
//<<< shadow2004::ed2k updates [WiZaRd]

	CHttpDownloadDlg dlgDownload;
	dlgDownload.m_strTitle = _T("Downloading IP filter version file");
	dlgDownload.m_sURLToDownload = strURL;
	dlgDownload.m_sFileToDownloadInto = szTempFilePath;

	if (dlgDownload.DoModal() != IDOK)
	{
		_tremove(szTempFilePath);
		AddLogLine(true, _T("Error downloading %s"), strURL);
		return;
	}
//>>> shadow2004::ed2k updates [WiZaRd]
	CStdioFile IPFilterDotTxtFile;
	UINT uVersion = 0;
	CString strED2KLink = _T("");
	if(IPFilterDotTxtFile.Open(szTempFilePath, CFile::modeRead | CFile::shareDenyWrite))
	{
		CString strVersion;
		IPFilterDotTxtFile.ReadString(strVersion);
		IPFilterDotTxtFile.ReadString(strED2KLink);
		IPFilterDotTxtFile.Close();
		strVersion.Trim();
		strED2KLink.Trim();
		uVersion = (UINT)_tstoi(strVersion);
	}
	_tremove(szTempFilePath);
//<<< shadow2004::ed2k updates [WiZaRd]

	if (thePrefs.GetIPfilterVersion() >= uVersion && ::PathFileExists(GetDefaultFilePath()) && FileSize(GetDefaultFilePath()) >= 10240)   //>>> shadow2004::ed2k updates [WiZaRd]
		return;

//>>> shadow2004::ed2k updates [WiZaRd]
	bool bED2KUpdatePending = false;
	CPartFile* file = NULL;
	if (!thePrefs.GetDLingIpFilterLink().IsEmpty())
	{
		CED2KLink* pLink = CED2KLink::CreateLinkFromUrl(thePrefs.GetDLingIpFilterLink());
		if (pLink != NULL)
		{
			CED2KFileLink* pFileLink = pLink->GetFileLink();
			if (pFileLink != NULL)
			{
				file = theApp.downloadqueue->GetFileByID(pFileLink->GetHashKey());
				if(file)
					bED2KUpdatePending = true;
				else //no file?! reset the link and version!
				{
					thePrefs.SetDLingIpFilterVersion(0);
					thePrefs.SetDLingIpFilterLink(L"");
				}
			}
		}
		delete pLink;
	}
	else
		thePrefs.SetDLingIpFilterVersion(0); //just to be sure
	if(!strED2KLink.IsEmpty() && uVersion > thePrefs.GetDLingIpFilterVersion())
	{
//>>> Delete pending file...
		if(bED2KUpdatePending)
		{
			ASSERT(file != NULL);
			//partially copied from DownloadListCtrl.cpp
			bool validdelete = false;
			bool removecompl = false;
			if (file->GetStatus() != PS_COMPLETING)
				validdelete = true;
			if (file->GetStatus() == PS_COMPLETE)
			{
				removecompl = true;
				ASSERT(0); //should NEVER happen!
			}
			bool bRemovedItems = false;
			if ((removecompl && !validdelete) || validdelete) //should always be true
			{
				theApp.emuledlg->transferwnd->downloadlistctrl.HideSources(file);
				switch (file->GetStatus())
				{
					case PS_WAITINGFORHASH:
					case PS_HASHING:
					case PS_COMPLETING:
						bRemovedItems = true;
						break;
					case PS_COMPLETE:
					{
						BOOL delsucc = FALSE;
						if (!PathFileExists(file->GetFilePath()))
							delsucc = TRUE;
						else
						{
							// Delete
							if (!thePrefs.GetRemoveToBin())
								delsucc = DeleteFile(file->GetFilePath());
							else
							{
								// delete to recycle bin :(
								TCHAR todel[MAX_PATH+1];
								memset(todel, 0, sizeof todel);
								_tcsncpy(todel, file->GetFilePath(), ARRSIZE(todel)-2);

								SHFILEOPSTRUCT fp = {0};
								fp.wFunc = FO_DELETE;
								fp.hwnd = theApp.emuledlg->m_hWnd;
								fp.pFrom = todel;
								fp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT;// | FOF_NOERRORUI
								delsucc = (SHFileOperation(&fp) == 0);
							}
						}
						if (delsucc)
							theApp.sharedfiles->RemoveFile(file);
						else
							theApp.QueueLogLineEx(LOG_ERROR, GetResString(IDS_ERR_DELFILE) + _T("\r\n\r\n%s"), file->GetFilePath(), GetErrorMessage(GetLastError()));

						theApp.emuledlg->transferwnd->downloadlistctrl.RemoveFile(file);
						bRemovedItems = true;
						break;
					}
					case PS_PAUSED:
						file->DeleteFile();
						bRemovedItems = true;
						break;
					default:
						if (file->GetCategory())
							theApp.downloadqueue->StartNextFileIfPrefs(file->GetCategory());
						file->DeleteFile();
						bRemovedItems = true;
						break;
				}
			}
			if (bRemovedItems)
			{
				theApp.emuledlg->transferwnd->downloadlistctrl.AutoSelectItem();
				theApp.emuledlg->transferwnd->UpdateCatTabTitles();
				theApp.QueueLogLineEx(LOG_WARNING, L"You were downloading ipfilter.dat v%u - it has been deleted because v%u is newer!", thePrefs.GetDLingIpFilterVersion(), uVersion);
			}
			else
				theApp.QueueLogLineEx(LOG_ERROR, L"You are downloading ipfilter.dat v%u - v%u is newer but we failed to delete it :(", thePrefs.GetDLingIpFilterVersion(), uVersion);
		}
//<<< Delete pending file...
		CED2KLink* pLink = CED2KLink::CreateLinkFromUrl(strED2KLink);
		if (pLink != NULL)
		{
			CED2KFileLink* pFileLink = pLink->GetFileLink();
			if (pFileLink != NULL)
			{
				//add if not already in downloadqueue... if it IS already present then update the old file
				CPartFile* file = theApp.downloadqueue->GetFileByID(pFileLink->GetHashKey());
				if (file == NULL)
				{
					theApp.downloadqueue->AddFileLinkToDownload(pFileLink);
					file = theApp.downloadqueue->GetFileByID(pFileLink->GetHashKey());
				}
				if (file != NULL)
				{
					bED2KUpdatePending = true;
					thePrefs.SetDLingIpFilterVersion(uVersion);
					thePrefs.SetDLingIpFilterLink(strED2KLink);
					file->SetSpecialFile(TYPE_IPF);
					thePrefs.Save();
					// Autostart IPFilter Download also if standard ED2K links will start paused [shadow2004]
					if (thePrefs.GetAutoResumePausedIPFilter() && thePrefs.addnewfilespaused && file->CanResumeFile())
					{
						if (file->GetStatus() == PS_INSUFFICIENT)
							file->ResumeFileInsufficient();
						else
							file->ResumeFile();
					}
					// End Autostart
					// Set the preferred upload-priority [shadow2004]
					if (thePrefs.GetIPFilterUploadPrioManuell())
					{
						file->SetUpPriority(thePrefs.GetIPFilterUploadPrio());
						if(thePrefs.GetIPFilterUploadPrio() == PR_AUTO)
							file->SetAutoUpPriority(true);
					}
					// End Set the preferred upload-priority [shadow2004]
				}
				else
					AddLogLine(false, L"Error updating ipfilter via ed2k - could not add the file :(");
			}
		}
		delete pLink;
	}
	if(!bED2KUpdatePending) //if no ed2kupdate is pending perform the "default" update procedure
	{
		CString IPFilterURL;
		if(m_sURL.IsEmpty())
			IPFilterURL = thePrefs.GetUpdateURLIPFilter();
		else
			IPFilterURL = m_sURL;
//<<< shadow2004::ed2k updates [WiZaRd]

		_tmakepathlimit(szTempFilePath, NULL, thePrefs.GetMuleDirectory(EMULE_ADDONDIR), DFLT_IPFILTER_FILENAME, _T("tmp"));	//>>> shadow2004::AddonDirectory

		CHttpDownloadDlg dlgDownload;
		dlgDownload.m_strTitle = _T("Downloading IP filter file");
		dlgDownload.m_sURLToDownload = IPFilterURL;
		dlgDownload.m_sFileToDownloadInto = szTempFilePath;
		if (dlgDownload.DoModal() != IDOK || FileSize(szTempFilePath) < 10240)
		{
			_tremove(szTempFilePath);
			AddLogLine(true, _T("IP Filter download failed"));
			return;
		}

		if(!Extract(szTempFilePath, thePrefs.GetMuleDirectory(EMULE_ADDONDIR) + DFLT_IPFILTER_FILENAME, DFLT_IPFILTER_FILENAME, L"guarding.p2p", true))	//>>> shadow2004::AddonDirectory
			return;

		LoadFromDefaultFile();

		thePrefs.SetIpfilterVersion(uVersion);   //>>> shadow2004::ed2k updates [WiZaRd]
		thePrefs.Save();
	}
}
//<<< shadow2004::IPFilter-Update [Morph]
// <-------- IPFilter-Update

// Contrib - ed2k updates
//>>> shadow2004::ed2k updates [WiZaRd]
void CIPFilter::AddFilterFile(const CString& path, const bool& removeold)
{
	if(!PathFileExists(path)) //just to be sure...
	{
		AddDebugLogLine(false, _T("Failed to update \"%s\" with \"%s\" - file does not exist!"), GetDefaultFilePath(), path);
		return;
	}

	if(removeold)
		RemoveAllIPFilters();

	//Backup current file
	const CString strBackupFilePath = GetDefaultFilePath() + _T(".bak");
	::MoveFile(GetDefaultFilePath(), strBackupFilePath);

	bool bLoaded = false; //success indicator

	//first try to unzip...
	if(Extract(path, thePrefs.GetMuleDirectory(EMULE_ADDONDIR) + DFLT_IPFILTER_FILENAME, DFLT_IPFILTER_FILENAME, DFLT_IPFILTER_FILENAME, false))	//>>> shadow2004::AddonDirectory
		bLoaded = AddFromFile(GetDefaultFilePath())!=0;

	//success
	if(bLoaded)
	{
		SaveToFile(); //save the current file...
		::DeleteFile(strBackupFilePath); //kick the old file...
		AddDebugLogLine(false, _T("Successfully updated \"%s\" with \"%s\""), GetDefaultFilePath(), path);
	}
	else //failed
	{
		::MoveFile(strBackupFilePath, GetDefaultFilePath()); //restore backup
		AddDebugLogLine(false, _T("Failed to update \"%s\" with \"%s\""), GetDefaultFilePath(), path);
		if(removeold) // restore old ipfilters if they were deleted
			AddFromFile(GetDefaultFilePath());
	}
}
//<<< shadow2004::ed2k updates [WiZaRd]
// <-------- ed2k updates

// Contrib - static ip filter
// ==> Static IP Filter - Stulle
void CIPFilter::AddFromFile2(LPCTSTR pszFilePath)
{
	FILE* readFile = _tfsopen(pszFilePath, _T("r"), _SH_DENYWR);
	if (readFile != NULL)
	{
		enum EIPFilterFileType
		{
			Unknown = 0,
			FilterDat = 1,		// ipfilter.dat/ip.prefix format
			PeerGuardian = 2,	// PeerGuardian text format
			PeerGuardian2 = 3	// PeerGuardian binary format
		} eFileType = Unknown;

		setvbuf(readFile, NULL, _IOFBF, 32768);

		TCHAR szNam[_MAX_FNAME];
		TCHAR szExt[_MAX_EXT];
		_tsplitpath(pszFilePath, NULL, NULL, szNam, szExt);
		if (_tcsicmp(szExt, L".p2p") == 0 || (_tcsicmp(szNam, L"guarding.p2p") == 0 && _tcsicmp(szExt, L".txt") == 0))
			eFileType = PeerGuardian;
		else if (_tcsicmp(szExt, L".prefix") == 0)
			eFileType = FilterDat;
		else
		{
			VERIFY( _setmode(_fileno(readFile), _O_BINARY) != -1 );
			static const BYTE _aucP2Bheader[] = "\xFF\xFF\xFF\xFFP2B";
			BYTE aucHeader[sizeof _aucP2Bheader - 1];
			if (fread(aucHeader, sizeof aucHeader, 1, readFile) == 1)
			{
				if (memcmp(aucHeader, _aucP2Bheader, sizeof _aucP2Bheader - 1)==0)
					eFileType = PeerGuardian2;
				else
				{
					fseek(readFile, 0, SEEK_SET);
					VERIFY( _setmode(_fileno(readFile), _O_TEXT) != -1 ); // ugly!
				}
			}
		}

		int iLine = 0;
		if (eFileType == PeerGuardian2)
		{
			// Version 1: strings are ISO-8859-1 encoded
			// Version 2: strings are UTF-8 encoded
			uint8 nVersion;
			if (fread(&nVersion, sizeof nVersion, 1, readFile)==1 && (nVersion==1 || nVersion==2))
			{
				while (!feof(readFile))
				{
					CHAR szName[256];
					int iLen = 0;
					for (;;) // read until NUL or EOF
					{
						int iChar = getc(readFile);
						if (iChar == EOF)
							break;
						if (iLen < sizeof szName - 1)
							szName[iLen++] = (CHAR)iChar;
						if (iChar == '\0')
							break;
					}
					szName[iLen] = '\0';

					uint32 uStart;
					if (fread(&uStart, sizeof uStart, 1, readFile) != 1)
						break;
					uStart = ntohl(uStart);

					uint32 uEnd;
					if (fread(&uEnd, sizeof uEnd, 1, readFile) != 1)
						break;
					uEnd = ntohl(uEnd);

					iLine++;
					// (nVersion == 2) ? OptUtf8ToStr(szName, iLen) :
					AddIPRange(uStart, uEnd, DFLT_FILTER_LEVEL, CStringA(szName, iLen));
				}
			}
		}
		else
		{
			CStringA sbuffer;
			CHAR szBuffer[1024];
			while (fgets(szBuffer, _countof(szBuffer), readFile) != NULL)
			{
				iLine++;
				sbuffer = szBuffer;

				// ignore comments & too short lines
				if (sbuffer.GetAt(0) == '#' || sbuffer.GetAt(0) == '/' || sbuffer.GetLength() < 5) {
					sbuffer.Trim(" \t\r\n");
					DEBUG_ONLY( (!sbuffer.IsEmpty()) ? TRACE("IP filter (static): ignored line %u\n", iLine) : 0 );
					continue;
				}

				if (eFileType == Unknown)
				{
					// looks like html
					if (sbuffer.Find('>') > -1 && sbuffer.Find('<') > -1)
						sbuffer.Delete(0, sbuffer.ReverseFind('>') + 1);

					// check for <IP> - <IP> at start of line
					UINT u1, u2, u3, u4, u5, u6, u7, u8;
					if (sscanf(sbuffer, "%u.%u.%u.%u - %u.%u.%u.%u", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) == 8)
					{
						eFileType = FilterDat;
					}
					else
					{
						// check for <description> ':' <IP> '-' <IP>
						int iColon = sbuffer.Find(':');
						if (iColon > -1)
						{
							CStringA strIPRange = sbuffer.Mid(iColon + 1);
							UINT u1, u2, u3, u4, u5, u6, u7, u8;
							if (sscanf(strIPRange, "%u.%u.%u.%u - %u.%u.%u.%u", &u1, &u2, &u3, &u4, &u5, &u6, &u7, &u8) == 8)
							{
								eFileType = PeerGuardian;
							}
						}
					}
				}

				bool bValid = false;
				uint32 start = 0;
				uint32 end = 0;
				UINT level = 0;
				CStringA desc;
				if (eFileType == FilterDat)
					bValid = ParseFilterLine1(sbuffer, start, end, level, desc);
				else if (eFileType == PeerGuardian)
					bValid = ParseFilterLine2(sbuffer, start, end, level, desc);

				// add a filter
				if (bValid)
				{
					AddIPRange(start, end, level, desc);
				}
				else
				{
					sbuffer.Trim(" \t\r\n");
					DEBUG_ONLY( (!sbuffer.IsEmpty()) ? TRACE("IP filter (static): ignored line %u\n", iLine) : 0 );
				}
			}
		}
		fclose(readFile);

		// sort the IP filter list by IP range start addresses
		qsort(m_iplist.GetData(), m_iplist.GetCount(), sizeof(m_iplist[0]), CmpSIPFilterByStartAddr);

		// merge overlapping and adjacent filter ranges
		if (m_iplist.GetCount() >= 2)
		{
			// On large IP-filter lists there is a noticeable performance problem when merging the list.
			// The 'CIPFilterArray::RemoveAt' call is way too expensive to get called during the merging,
			// thus we use temporary helper arrays to copy only the entries into the final list which
			// are not get deleted.

			// Reserve a byte array (its used as a boolean array actually) as large as the current
			// IP-filter list, so we can set a 'to delete' flag for each entry in the current IP-filter list.
			char* pcToDelete = new char[m_iplist.GetCount()];
			memset(pcToDelete, 0, m_iplist.GetCount());
			int iNumToDelete = 0;

			SIPFilter* pPrv = m_iplist[0];
			int i = 1;
			while (i < m_iplist.GetCount())
			{
				SIPFilter* pCur = m_iplist[i];
				if (   pCur->start >= pPrv->start && pCur->start <= pPrv->end	 // overlapping
					|| pCur->start == pPrv->end+1 && pCur->level == pPrv->level) // adjacent
				{
					if (pCur->start != pPrv->start || pCur->end != pPrv->end) // don't merge identical entries
					{
						//TODO: not yet handled, overlapping entries with different 'level'
						if (pCur->end > pPrv->end)
							pPrv->end = pCur->end;
						//pPrv->desc += _T("; ") + pCur->desc; // this may create a very very long description string...
					}
					else
					{
						// if we have identical entries, use the lowest 'level'
						if (pCur->level < pPrv->level)
							pPrv->level = pCur->level;
					}
					delete pCur;
					//m_iplist.RemoveAt(i);	// way too expensive (read above)
					pcToDelete[i] = 1;		// mark this entry as 'to delete'
					iNumToDelete++;
					i++;
					continue;
				}
				pPrv = pCur;
				i++;
			}

			// Create new IP-filter list which contains only the entries from the original IP-filter list
			// which are not to be deleted.
			if (iNumToDelete > 0)
			{
				CIPFilterArray newList;
				newList.SetSize(m_iplist.GetCount() - iNumToDelete);
				int iNewListIndex = 0;
				for (int i = 0; i < m_iplist.GetCount(); i++) {
					if (!pcToDelete[i])
						newList[iNewListIndex++] = m_iplist[i];
				}
				ASSERT( iNewListIndex == newList.GetSize() );

				// Replace current list with new list. Dump, but still fast enough (only 1 memcpy)
				m_iplist.RemoveAll();
				m_iplist.Append(newList);
				newList.RemoveAll();
				m_bModified = true;
			}
			delete[] pcToDelete;
		}
	}
	return;
}

CString CIPFilter::GetDefaultStaticFilePath() const
{

// By Taz - AddonDirectory
//	return thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + DFLT_STATIC_IPFILTER_FILENAME;
	return thePrefs.GetMuleDirectory(EMULE_ADDONDIR) + DFLT_STATIC_IPFILTER_FILENAME;
// <------- AddonDirectory

}
// <== Static IP Filter - Stulle
// <-------- static ip filter

// By Taz - POint and Filter
#ifdef POF
void CIPFilter::Append2StaticFile(const CUpDownClient* client)
{

	// sort the IP filter list by IP range start addresses
	qsort(m_iplist.GetData(), m_iplist.GetCount(), sizeof(m_iplist[0]), CmpSIPFilterByHash);

	// Replace already filtered Hash

	CString strFilePath = GetDefaultStaticFilePath();

	FILE* fp = _tfsopen(strFilePath, _T("at"), _SH_DENYWR);
	if (fp != NULL)
	{
		CHAR szStart[16];
		ipstrA(szStart, _countof(szStart), htonl(client->GetIP()));

		if (fprintf(fp, "%-15s - %-15s , %3u , [Hash] : [%s], Mod : %s, [Nick] : [%s]\n", szStart, szStart, 0, md4str(client->GetUserHash()), client->DbgGetFullClientSoftVer(), client->GetUserName() ? client->GetUserName() : L"") == 0 || ferror(fp))
		{
			CString strError;
			strError.Format(L"Failed appending to static IP filter file \"%s\" - %s", strFilePath, _tcserror(errno));
			throw strError;
		}
		fclose(fp);
	}
	else
	{
		CString strError;
		strError.Format(L"Failed appending to static IP filter file \"%s\" - %s", strFilePath, _tcserror(errno));
		throw strError;
	}

	AddFromFile2(GetDefaultStaticFilePath());
}

static int __cdecl CmpSIPFilterByHash(const void* p1, const void* p2)
{
	const SIPFilter* rng1 = *(SIPFilter**)p1;
	const SIPFilter* rng2 = *(SIPFilter**)p2;
	return CompareUnsigned(rng1->start, rng2->start);
}
#endif
// <------- POint and Filter