/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
////////////////////////////////////////////////////////////////////////////////
#Title: xbDnItf
#Project: xdcBase
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"

#include "xbDnItf.h"
#include "xbXmlItf.h"
#include "osdep/mswin/xbODregistry.h"
#include "xbFileTools.h"

xbEimpl(xbEdn,xbEadv);
xbEimpl(xbEdnUser,xbEdn);
xbEimpl(xbEdnHttp,xbEdn);
xbEimpl(xbEdnRetry,xbEdn);
xbEimpl(xbEdnRetryLater,xbEdnRetry);
xbEimpl(xbEdnRetrySession,xbEdnRetry);
xbEimpl(xbEdnRetryNow,xbEdnRetry);

xbStatPointFolder	g_xbStatDnCenter("dnCenter","Download center");
xbStatPointFolder	g_xbStatDnTraffic(&g_xbStatDnCenter,"dnTraffic","Download/Upload traffic");

#pragma warning(push)
#pragma warning(disable: 4311)

///////////////////////////////////////////////////////////////////////////////////
//tools for path manip.
xbString xbUrlDecode(const xbString &url)
{
	xbString ret;
	if (!::xbUrlUnescape(url,url.Length,&ret,CP_ACP))
		return url;
	return ret;
}

xbString xbUrlEncode(const xbString &url,LPCSTR pszChToEncode,LPCSTR pszChToExclude)
{
	xbString ret;
	if (!::xbUrlEscape(url,url.Length,&ret,pszChToEncode,pszChToExclude))
		return url;
	return ret;
}

const char *_xbbase64_strA = 
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	"abcdefghijklmnopqrstuvwxyz"
	"0123456789+/=";
xbStringA xbBase64encodeA(const byte *pDta,int dtaLen)
{
	xbStringA ret;
	char buf[4];
	register int lg = 0;
	register const uchar *ptr = pDta;
	register ulong res = 0;
	while(lg < dtaLen)
	{
		res <<= 8; 
		res += *ptr;
		lg++;
		ptr++;
		if ((lg % 3) == 0)
		{
			buf[0] = _xbbase64_strA[(res >> 18) & 0x3F];
			buf[1] = _xbbase64_strA[(res >> 12) & 0x3F];
			buf[2] = _xbbase64_strA[(res >> 6) & 0x3F];
			buf[3] = _xbbase64_strA[res & 0x3F];
			ret.Add(buf,4);
		}
	}
	if ((lg % 3) == 0) 
		return ret;
	if ((lg%3) == 1)
	{
		//8bits valid
		buf[0] = _xbbase64_strA[(res>>2) & 0x3F];
		buf[1] = _xbbase64_strA[(res & 0x3)<<4];
		buf[2] = '=';
	}
	else
	{
		xbAssert((lg%3) == 2);
		//16bits valid
		buf[0] = _xbbase64_strA[(res >> 10) & 0x3F];
		buf[1] = _xbbase64_strA[(res >> 4) & 0x3F];
		buf[2] = _xbbase64_strA[(res & 0xF)<<2];
	}
	buf[3] = '=';

	ret.Add(buf,4);

	return ret;
}

xbStringA xbBase64decodeA(const char *pStr,int strLen)
{
	xbStringA ret;

	uchar buf[3];
	register int lg = 0;
	register const uchar *ptr = (const uchar *)pStr;
	register ulong res = 0;
	while(strLen > 0)
	{
		strLen--;
		uchar ix = -1;
		if (*ptr == '=')
			break;
		if (*ptr >= 'A' && *ptr <= 'Z')
			ix = *ptr - 'A'; 
		else if (*ptr >= 'a' && *ptr <= 'z')
			ix = *ptr - 'a' + 26; 
		else if (*ptr >= '0' && *ptr <= 'z')
			ix = *ptr - '0' + 52; 
		else if (*ptr == '+')
			ix = 62;
		else if (*ptr == '/')
			ix = 63;
		else
		{
			if (*ptr != '\r' && *ptr != '\n'
				&& *ptr != ' ' && *ptr != '\t')
				xbThrow xbEsys("xbBase64decode() : Bad string to decode (ch)");
			ptr++;
			continue;
		}
		ptr++;

		res <<= 6; 
		res += ix;
		lg++;
		if ((lg % 4) == 0)
		{
			buf[0] = uchar((res >> 16) & 0xFF);
			buf[1] = uchar((res >> 8) & 0xFF);
			buf[2] = uchar(res & 0xFF);
			ret.Add((const char*)buf,3);
		}
	}
	if ((lg % 4) == 0) return ret;
	
	switch(lg % 4)
	{
	//case 1 : pas possible, chaine encodée >= 8 bits
	case 2 : //12 bits, 8 bits utilisés uniquement
		buf[0] = uchar((res>>4) & 0xFF);
		lg = 1;
		break;
	case 3 : //18 bits, 16 bits utilisés uniquement
		buf[0] = uchar((res >> 10) & 0xFF);
		buf[1] = uchar((res >> 2) & 0xFF);
		lg = 2;
		break;
	default:
		xbThrow xbEsys("xbBase64decode() : Bad string to decode (lg)");
	}

	ret.Add((const char*)buf,lg);
	
	return ret;
}

const wchar *_xbbase64_strW = 
	L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	L"abcdefghijklmnopqrstuvwxyz"
	L"0123456789+/=";
xbStringW xbBase64encodeW(const byte *pDta,int dtaLen)
{
	xbStringW ret;
	wchar buf[4];
	register int lg = 0;
	register const uchar *ptr = pDta;
	register ulong res = 0;
	while(lg < dtaLen)
	{
		res <<= 8; 
		res += *ptr;
		lg++;
		ptr++;
		if ((lg % 3) == 0)
		{
			buf[0] = _xbbase64_strW[(res >> 18) & 0x3F];
			buf[1] = _xbbase64_strW[(res >> 12) & 0x3F];
			buf[2] = _xbbase64_strW[(res >> 6) & 0x3F];
			buf[3] = _xbbase64_strW[res & 0x3F];
			ret.Add(buf,4);
		}
	}
	if ((lg % 3) == 0) 
		return ret;
	if ((lg%3) == 1)
	{
		//8bits valid
		buf[0] = _xbbase64_strW[(res>>2) & 0x3F];
		buf[1] = _xbbase64_strW[(res & 0x3)<<4];
		buf[2] = L'=';
	}
	else
	{
		xbAssert((lg%3) == 2);
		//16bits valid
		buf[0] = _xbbase64_strW[(res >> 10) & 0x3F];
		buf[1] = _xbbase64_strW[(res >> 4) & 0x3F];
		buf[2] = _xbbase64_strW[(res & 0xF)<<2];
	}
	buf[3] = L'=';

	ret.Add(buf,4);

	return ret;
}

xbStringA xbBase64decodeW(const wchar *pStr,int strLen)
{
	xbStringA ret;

	uchar buf[3];
	register int lg = 0;
	register const wchar *ptr = pStr;
	register ulong res = 0;
	while(strLen > 0)
	{
		strLen--;
		wchar ix = -1;
		if (*ptr == L'=')
			break;
		if (*ptr >= L'A' && *ptr <= L'Z')
			ix = *ptr - L'A'; 
		else if (*ptr >= L'a' && *ptr <= L'z')
			ix = *ptr - L'a' + 26; 
		else if (*ptr >= L'0' && *ptr <= L'z')
			ix = *ptr - L'0' + 52; 
		else if (*ptr == L'+')
			ix = 62;
		else if (*ptr == L'/')
			ix = 63;
		else
		{
			if (*ptr != L'\r' && *ptr != L'\n'
				&& *ptr != L' ' && *ptr != L'\t')
				xbThrow xbEsys("xbBase64decode() : Bad string to decode (ch)");
			ptr++;
			continue;
		}
		ptr++;

		res <<= 6; 
		res += ix;
		lg++;
		if ((lg % 4) == 0)
		{
			buf[0] = uchar((res >> 16) & 0xFF);
			buf[1] = uchar((res >> 8) & 0xFF);
			buf[2] = uchar(res & 0xFF);
			ret.Add((const char*)buf,3);
		}
	}
	if ((lg % 4) == 0) return ret;
	
	switch(lg % 4)
	{
	//case 1 : pas possible, chaine encodée >= 8 bits
	case 2 : //12 bits, 8 bits utilisés uniquement
		buf[0] = uchar((res>>4) & 0xFF);
		lg = 1;
		break;
	case 3 : //18 bits, 16 bits utilisés uniquement
		buf[0] = uchar((res >> 10) & 0xFF);
		buf[1] = uchar((res >> 2) & 0xFF);
		lg = 2;
		break;
	default:
		xbThrow xbEsys("xbBase64decode() : Bad string to decode (lg)");
	}

	ret.Add((const char*)buf,lg);
	
	return ret;
}

xbString xbUrlSlash(const xbString &url)
{
	if (url.IndexOf('\\') <= 0)
		return url;
	xbString ret(url);
	ret.OwnData();
	char *ptr = (LPSTR)(LPCSTR)ret;
	char *pend = ptr + ret.Length;

	while(ptr < pend)
	{
		if (*ptr == '\\')
			*ptr = '/';
		ptr++;
	}
	return ret;
}

xbString _xbUrlAslash(const xbString &url)
{
	if (url.IndexOf('/') <= 0)
		return url;
	xbString ret(url);
	ret.OwnData();
	char *ptr = (LPSTR)(LPCSTR)ret;
	char *pend = ptr + ret.Length;

	while(ptr < pend)
	{
		if (*ptr == '/')
			*ptr = '\\';
		ptr++;
	}
	return ret;
}

xbString xbSubPath(const xbString &path,xbString *pRet,bool *pbEndSlash)
{
	int pos_aslash = path.IndexOf('\\');
	int pos_slash = path.IndexOf('/');
	if (pos_aslash < 0 && pos_slash < 0)
	{
		if (pbEndSlash)
			*pbEndSlash = false;
		xbString ret(path);
		if (pRet) *pRet = xbString();
		return ret;
	}
	if (pbEndSlash)
		*pbEndSlash = true;
	xbString ret,stay;
	if (pos_slash >= 0 && (pos_aslash<0 || pos_slash < pos_aslash))
	{
		ret = path.Left(pos_slash);
		if (pRet) stay = path.Mid(pos_slash+1);
	}
	else
	{
		ret = path.Left(pos_aslash);
		if (pRet) stay = path.Mid(pos_aslash+1);
	}	

	ret.Trim();
	if (pRet) 
	{
		stay.Trim();
		*pRet = stay;
	}
	return ret;
}

xbString xbSubPathEnd(const xbString &path,xbString *pRet,bool *pbStartSlash)
{
	int pos_aslash = path.LastIndexOf('\\');
	int pos_slash = path.LastIndexOf('/');
	if (pos_aslash < 0 && pos_slash < 0)
	{
		if (pbStartSlash)
			*pbStartSlash = false;
		xbString ret(path);
		if (pRet) *pRet = xbString();
		return ret;
	}
	if (pbStartSlash)
		*pbStartSlash = true;
	xbString ret,stay;
	if (pos_slash > pos_aslash)
	{
		ret = path.Mid(pos_slash+1);
		if (pRet) stay = path.Left(pos_slash);
	}
	else
	{
		ret = path.Mid(pos_aslash+1);
		if (pRet) stay = path.Left(pos_aslash);
	}	

	ret.Trim();
	if (pRet) 
	{
		stay.Trim();
		*pRet = stay;
	}
	return ret;
}

bool xbIsAbsUrl(const xbString &_url)
{
	xbString url(_url);
	bool bSlash;
	xbString proto(xbGetProtocol(url,&url,&bSlash));

	if (proto.IsValid() && bSlash)
		return true;

	if (proto == "file" || proto.IsEmpty())
	{
		xbString drv(xbGetDrive(url,&url));
		if (drv.IsValid()) return true;

		if (url.IsValid() && url.First() == '\\') return true;
		return false;
	}

/*	if (proto == "res")
	{
		xbString sub(xbSubPath(url));

		if (sub != "." && sub != "..") 
		{
			return true;
		}
		return false;
	}*/

	if (proto == "data")
		return true;

	return false;
}

/*	if (ret != "http"
		&& ret != "https"
		&& ret != "ftp"
		&& ret != "file"
		&& ret != "res"
		&& ret != "gopher"
		&& ret != "data") return xbString();*/
xbString	xbGetProtocol(const xbString &url,xbString *pRet,bool *pbSlash,bool bDblSlashRemove)
{
	int pos = url.IndexOf(':');
	if (pos <= 0) 
	{
		if (pbSlash) 
			*pbSlash = false;
		if (pRet)
			*pRet = url;
		return xbString();
	}
	xbString ret(url.Left(pos-1));
	ret.Trim();
	ret.Lower();

	if (ret.Length < 2
		|| ret.First() == '/' || ret.First() == '\\') 
	{
		if (pbSlash) 
			*pbSlash = false;
		if (pRet)
			*pRet = url;
		return xbString();
	}

	{
		const char *ptr = ret;
		const char *pend = ptr + ret.Length;

		while(ptr < pend)
		{
			if ((*ptr < '0' || *ptr > '9')
				&& (*ptr < 'a' || *ptr > 'z')
				&& *ptr != '%' && *ptr != '.')
			{
				if (pbSlash) 
					*pbSlash = false;
				if (pRet)
					*pRet = url;
				return xbString();
			}
			ptr++;
		}
	}

	if (!pRet && !pbSlash)
		return ret;

	xbString path(url.Mid(pos));
	path.Trim();
	if (bDblSlashRemove && path.Left(2) == "//") 
	{
		if (pbSlash) 
			*pbSlash = true;
		if (pRet) 
		{
			path.Delete(0,2);
			path.Trim();
		}
	}
	else 
	{
		if (pbSlash) 
		{
			if (path.Left(2) == "//") 
				*pbSlash = true;
			else
				*pbSlash = false;
		}
	}
	if (pRet)
		*pRet = path;

	return ret;
}

xbString	xbGetDrive(const xbString &_url,xbString *pRet)
{
	xbString url(_url);
	xbString proto(xbGetProtocol(url,&url));
	if (!proto.IsEmpty() && proto != "file") 
	{
		if (pRet)
			*pRet = url;
		return xbString();
	}

	if (url.IsEmpty() || url.First() == '/' || url.First() == '\\')
	{
		if (pRet)
			*pRet = url;
		return xbString();
	}
	int pos = url.IndexOf(':');
	if (pos <= 0) 
	{
		if (pRet)
			*pRet = url;
		return xbString();
	}
	xbString ret(url.Left(pos-1));
	ret.Trim();
	ret.Upper();

	{
		const char *ptr = ret;
		const char *pend = ptr + ret.Length;

		while(ptr < pend)
		{
			if ((*ptr < '0' || *ptr > '9')
				&& (*ptr < 'A' || *ptr > 'Z')
				&& *ptr != '%' && *ptr != '.')
			{
				if (pRet)
					*pRet = url;
				return xbString();
			}
			ptr++;
		}
	}

	if (pRet) 
	{
		xbString path(url.Mid(pos));
		path.Trim();
		*pRet = path;
	}
	return ret;
}

xbString	xbMakeAbsUrl(const xbString &url,LPCSTR pCurFile,sIxbUrlRoots *piRoots)
{
	if (url.IsEmpty()) return url;
	if (xbIsAbsUrl(url)) return url;

	xbString	urlParse(url);
	xbString	urlProto(xbGetProtocol(urlParse,&urlParse));

	xbString	ret;
	if (pCurFile)
	{
		ret = pCurFile;
		xbString retProto = xbGetProtocol(ret);
		if (retProto.IsEmpty()) 
			retProto = "file";

		if (urlProto.IsValid() && xbStricmp(retProto,urlProto) != 0)
		{
			ret.Empty();
		}
	}
	if (ret.IsEmpty())
	{
		if (piRoots) 
			ret = piRoots->GetUrlRootForProtocol(urlProto);
		if (ret.IsEmpty())
		{
			if (urlProto == "res")
				return url;
			xbThrow xbEsys("xbMakeAbsUrl("+url+") : no root url");
		}
	}

	return ::xbAddPath(ret,urlParse);
}

bool		xbGetResUrl(const xbString &url,xbString *pDll,xbString *pRet,LPCSTR *ppResRet,xbString *pType,LPCSTR *ppTypeRet,xbString *ppUrlIfNot)
{
	xbString	ret(url);
	xbString	protoc(xbGetProtocol(ret,(pDll||pRet||ppResRet||pType||ppTypeRet)?&ret:NULL));
	if (protoc != "res") return false;
	if (!pDll && !pRet && !ppResRet && !pType && !ppTypeRet) return true;
	if (ret.IsEmpty()) return false;
	if (ret.First() == '*')
	{
		xbString ifnot;
		int pos_ifnot = ret.IndexOf('!');
		if (pos_ifnot>0)
		{
			ifnot = ret.Mid(pos_ifnot);
			ret = ret.Left(pos_ifnot-1);
		}
		int cCnt = 0;
		xbODregKey key(xbODregClassesRoot,ret.Mid(1));
		for(;;)
		{
			cCnt++;
			if (!key.Exists())
				break;
			xbODregKey subkey(key,"DefaultIcon");
			if (!subkey.Exists())
			{
				if (cCnt > 1)
					break;
				key.Init(xbODregClassesRoot,key.DefaultValue.AsString);
				continue;
			}
			xbString val(g_xbCfgEngine.GetExpandedString(subkey.DefaultValue.AsString)),num; //C:\PROGRA~1\QUICKT~1\QuickTimePlayer.exe,-131
			if (val.IndexOf('%') > 0)
				break;

			val.GetToken(0,",",&val,&num);
			if (num.IsValid())
				num = xbString(-xbStrToInt(num));
			else 
				num = "0";
			if (val.First() == '\"')
				val.Delete(0);
			if (val.Last() == '\"')
				val.Delete(val.GetLength()-1);

			return xbGetResUrl("res://"+val+"/#"+num,pDll,pRet,ppResRet,pType,ppTypeRet);
		}

		if (ifnot.IsEmpty())
			return false;
		
		if (::xbGetProtocol(ifnot).IsEmpty())
			ifnot="res://"+ifnot;

		if (ppUrlIfNot)
			*ppUrlIfNot = ifnot;
		
		return false;
	}
	xbString dll;
	ret = xbSubPathEnd(ret,&dll);
	if (pDll)
	{
		*pDll = xbUrlDecode(dll);
	}
	else 
	{
		if (!dll.IsEmpty()) return false;
	}

	xbString type;
	int pos_type = ret.LastIndexOf('!');
	if (pos_type>0)
	{
		type = xbUrlDecode(ret.Mid(pos_type).RetTrim());
		type.Upper();
		ret = xbUrlDecode(ret.Left(pos_type-1).RetTrim());
	}
	else ret = xbUrlDecode(ret);
	if (ret.IsEmpty()) return false;

	if (pRet) 
	{
		*pRet = ret;
		pRet->OwnData();
	}

	if (ret.First() == '#')
	{
		int id = xbStrToInt(ret.Mid(1));
		if (id <= 0)
		{
			if (!pRet) return false;
			*pRet = xbString(id-1);
			if (ppResRet) 
				*ppResRet = *pRet;
		}
		else
		{
			if (ppResRet)
				*ppResRet = (LPCSTR)MAKEINTRESOURCE(id);
		}
	}
	else
	{
		if (!pRet) return false;
		if (ppResRet) 
			*ppResRet = *pRet;
	}

	if (pType) *pType = type;

	if (type.IsEmpty())
	{
		if (ppTypeRet) *ppTypeRet = NULL;
	}
	else
	{
		if (type.First() == '#')
		{
			if (!ppTypeRet) return false;

			int id = xbStrToInt(type.Mid(1));

			*ppTypeRet = (LPCSTR)MAKEINTRESOURCE(id);
		}
		else
		{
			LPCTSTR id = NULL;
			if (type == "CURSOR") id = RT_GROUP_CURSOR;
			else if (type == "SCURSOR") id = RT_CURSOR;
			else if (type == "BITMAP") id = RT_BITMAP;
			else if (type == "ICON") id = RT_GROUP_ICON;
			else if (type == "SICON") id = RT_ICON;
			else if (type == "MENU") id = RT_MENU;
			else if (type == "DIALOG") id = RT_DIALOG;
			else if (type == "STRING") id = RT_STRING;
			else if (type == "FONTDIR") id = RT_FONTDIR;
			else if (type == "FONT") id = RT_FONT;
			else if (type == "ACCELERATOR") id = RT_ACCELERATOR;
			else if (type == "DATA") id = RT_RCDATA;
			else if (type == "MESSAGETABLE") id = RT_MESSAGETABLE;
			else if (type == "VERSION") id = RT_VERSION;
			else if (type == "DLGINCLUDE") id = RT_DLGINCLUDE;
			#ifndef _PDA
			else if (type == "PLUGPLAY") id = RT_PLUGPLAY;
			else if (type == "VXD") id = RT_VXD;
			else if (type == "ANICURSOR") id = RT_ANICURSOR;
			else if (type == "ANIICON") id = RT_ANIICON;
			else if (type == "HTML") id = RT_HTML;
			#endif
			if (id)
			{
				if (!ppTypeRet) return false;
				*ppTypeRet = (LPCSTR)id;
			}
			else
			{
				if (!pType) return false;
				if (ppTypeRet) *ppTypeRet = *pType;
			}
		}
	}

	return true;
}

struct xbEnumResSt
{
	int			curix;
	int			ix;
	xbString	ret;
	xbEnumResSt(int _ix) 
	{
		ix = _ix;
		curix = 0;
	}
};

BOOL CALLBACK xbEnumResNameProc(
  HMODULE hModule,   // module handle
  LPCTSTR lpszType, // pointer to resource type
  LPTSTR lpszName,  // pointer to resource name
  LONG_PTR lParam       // application-defined parameter
)
{
	xbEnumResSt	*pSt = (xbEnumResSt*)lParam;
	if ((++pSt->curix) != pSt->ix) 
		return TRUE;
	xbAssert(lpszName != NULL);
	if ((xbNptr(lpszName) & 0xFFFF) == xbNptr(lpszName))
	{
		pSt->ret = "#"+xbString(ulong(xbNptr(lpszName)));
	}
	else
		pSt->ret = lpszName;
	return FALSE;
}

xbString	xbGetResNo(int ix,LPCSTR typePtr,HMODULE hMod)
{
	xbEnumResSt	St(ix);
	EnumResourceNames(hMod,typePtr,xbEnumResNameProc,(LPARAM)&St);
	return St.ret;
}

bool		xbGetFileUrl(const xbString &url,xbString *pRet)
{
	xbString	ret(url);
	xbString	protoc(xbGetProtocol(ret,pRet?&ret:NULL));
	if (protoc.IsValid() && protoc != "file") return false;
	if (!pRet) return true;
	if (ret.IsEmpty()) return false;
	*pRet = ::xbPathSlash(xbUrlDecode(ret));
	return true;
}

bool		xbGetUncUrl(const xbString &url,xbString *pMachine,xbString *pRet)
{
	xbString ret;
	if (!xbGetFileUrl(url,&ret))
		return false;
	return xbGetUncPathPart(ret,pMachine,pRet);
}

bool		xbGetUncPathPart(const xbString &path,xbString *pMachine,xbString *pRet)
{
	if (path.Length < 5 || xbStrnicmp(path,"\\\\",2) != 0)
		return false;
	LPCSTR ptr = (LPCSTR)path+2;
	while(*ptr == '\\')
		ptr++;
	LPCSTR ptr2 = ptr;
	while(*ptr2 != '\\')
	{
		if (!*ptr2)
			return false;
		ptr2++;
	}
	LPCSTR ptr3 = ptr2;
	while(*ptr3 == '\\')
		ptr3++;
	if (!*ptr3)
		return false;
	if (pMachine)
		*pMachine = xbString(ptr, (int)(xbNptr(ptr2) - xbNptr(ptr)));
	if (pRet)
		*pRet = ptr3;

	return true;
}

bool		xbGetNetUrl(const xbString &url,xbString *pRet)
{
	xbString	ret(url);
	xbString	protoc(xbGetProtocol(ret,pRet?&ret:NULL));
	if (protoc != "http"
		&& protoc != "https"
		&& protoc != "ftp"
		&& protoc != "gopher") return false;
	if (!pRet) return true;
	if (ret.IsEmpty()) return false;
	*pRet = ret;
	return true;
}

bool		xbGetDataUrl(const xbString &url,xbString *pRet)
{
	xbString	ret(url);
	xbString	protoc(xbGetProtocol(ret,pRet?&ret:NULL));
	if (protoc != "data") return false;
	if (!pRet) return true;
	*pRet = ret;
	return true;
}

xbString	xbMakeResUrl(LPCSTR pRes,LPCSTR pType,HMODULE hMod)
{
	xbString	ret("res:");
	if (hMod != NULL)
	{
		ret += "//";
		char buf[512];
		*buf = 0;
		if (!::GetModuleFileName(hMod,buf,sizeof(buf)))
			xbThrow xbEsys(xbHR_LAST,"xbMakeResUrl() failure");
		ret += ::xbUrlSlash(buf);
		ret += '/';
	}
	if ((xbNptr(pRes) & 0xFFFF) == xbNptr(pRes))
	{
		ret += "#"+xbString(ulong(xbNptr(pRes)));
	}
	else 
	{
		ret += pRes;
	}
	if (pType)
	{
		if ((xbNptr(pType) & 0xFFFF) == xbNptr(pType))
		{
			ret += "!#"+xbString(ulong(xbNptr(pType)));
		}
		else 
		{
			ret += '!';
			ret += pType;
		}
	}
	return ret;
}

///////////////////////////////////////////////////////////////////////////////////
//xbDnData
bool xbDnData::SubDnOp(xbDnOp op,const xbString &url,IxbDnObject *pObj)
{
	CheckStop();
	return m_pSession->DoDnOp(this,op,url,pObj);
}

void xbDnData::CheckStop() const
{
	if (!shouldStop())
		return;
	if (!m_pThEv->TaskToStop())
		xbThrow xbEdnUser(E_ABORT,"Upload/Download cancelled, download center stopping...",closeReasonLabel()+"Url:"+m_Url+xbEdebug("\nxbDnData::CheckStop()"));

	if (m_pSession->isHotPlugCloseEvent())
		xbThrow xbEhotPlug("Upload/Download cancelled, download center updating plugins...",m_pSession->closeReasonLabel()+"Url:"+m_Url+xbEdebug("\nxbDnData::CheckStop"));

	xbThrow xbEdnUser(E_ABORT,"Upload/Download cancelled, task stopping...",closeReasonLabel()+"Url:"+m_Url+xbEdebug("\nxbDnData::CheckStop()"));
}

void xbDnData::AddDnTask(xbDnOp op,const xbString &url,IxbDnObject *pObj)
{
	m_arrDnTask.Add(DnTask(op,url,pObj));
}

bool xbDnData::GetDnTask(xbDnOp *pOp,xbString *pUrl,xbRefPtr<IxbDnObject> *ppObj)
{
	if (m_arrDnTask.IsEmpty()) 
		return false;
	DnTask	&dn = m_arrDnTask.GetAt(0);
	*pOp = dn.m_op;
	*pUrl = dn.m_url;
	*ppObj = dn.m_obj;
	m_arrDnTask.Delete(0);
	return true;
}

bool xbDnData::HasDnTask()
{
	return m_arrDnTask.IsEmpty()?false:true;
}

void xbDnData::plugStartRead()
{
	if (!m_bPlugOpen)
	{
		if (m_pPrev) m_pPrev->plugStartRead();
		return;
	}

	if (m_op != xbDnLoad)
		return;

	if (m_piStm.IsEmpty())
		return;

	{
		xbRefQIPtr<IxbDnProtoRead>	prProtoRead(m_piStm,xbOp_Optional);
		if (prProtoRead.IsValid()
			&& !prProtoRead->GetSerializeRecursiveRead(this,m_pObj))
			return;
	}

	m_PlugStm.SetLength(0);
	try
	{
		BYTE	buf[8192];
		for(;;)
		{
			ulong rdCnt = m_piStm->Read(buf,sizeof(buf),false);

			CheckStop();
//			if (ShouldStop()) break;

			if (!rdCnt) break;

			m_PlugStm.Write(buf,rdCnt);
		}
		m_PlugStm.Seek(0);
		m_piStm.Empty();
		if (m_pObj)
			m_pObj->setDnOpProto(NULL);
	}
	catch(...)
	{
		eProtectTrace(UrlClose("dnData::plugStartRead error"));
		m_bPlugErr = true;
		m_bPlugOpen = true;
		throw;
	}
	if (m_pPrev) 
		m_pPrev->plugStartRead();
}

bool xbDnData::UrlOpen()
{
	xbAssert(m_piStm.IsEmpty() && m_poStm.IsEmpty());
	UrlClose("dnData opening new url, closing previous stream");

	if (m_pPrev) 
		m_pPrev->plugStartRead();

	try
	{
		if (m_op == xbDnLoad) 
		{
			if (m_pSession->ReadUrl(this,m_pObj,&m_piStm))
			{
				if (m_pObj)
					m_pObj->setDnOpProto(NULL);
				xbAssert(m_piStm.IsEmpty());
				m_piStm.Empty();
				return true;
			}
			xbAssert(m_piStm.IsValid());
			xbRefQIPtr<IxbDnProtoStream>	prProto(m_piStm,xbOp_Optional);
			if (prProto.IsValid())
			{
				prProto->SetDnData(this);
			}
		}
		else
		{
			if (m_pSession->WriteUrl(this,m_pObj,&m_poStm))
			{
				if (m_pObj)
					m_pObj->setDnOpProto(NULL);
				xbAssert(m_poStm.IsEmpty());
				m_poStm.Empty();
				return true;
			}
			xbAssert(m_poStm.IsValid());
			xbRefQIPtr<IxbDnProtoStream>	prProto(m_poStm,xbOp_Optional);
			if (prProto.IsValid())
			{
				prProto->SetDnData(this);
			}
		}
	}
	catch(...)
	{
		eProtectBegin();
		if (m_pObj)
			m_pObj->setDnOpProto(NULL);
		eProtectEndTrace();
		throw;
	}
	m_bPlugOpen = true;
	return false;
}

xbString xbDnData::getCloseReason() const
{
	if (m_closeReason.IsValid())
		return m_closeReason;
	xbString ret;
	if (m_pObj)
	{
		ret =  m_pObj->getStopReason();
		if (ret.IsValid())
			return ret;
	}
	if (m_piStm.IsValid())
	{
		xbRefQIPtr<const IxbDnProtoStream>	prProto(m_piStm,xbOp_Optional);
		if (prProto.IsValid())
		{
			ret = prProto->getCloseReason();
			if (ret.IsValid())
				return ret;
		}
	}
	if (m_poStm.IsValid())
	{
		xbRefQIPtr<const IxbDnProtoStream>	prProto(m_poStm,xbOp_Optional);
		if (prProto.IsValid())
		{
			ret = prProto->getCloseReason();
			if (ret.IsValid())
				return ret;
		}
	}

	return m_pSession->getCloseReason();
}

void xbDnData::UrlClose(LPCSTR pReason)
{
	if (m_pObj)
	{
		eProtect(m_pObj->setDnOpProto(NULL));
	}
	if (m_piStm.IsValid())
	{
		xbRefQIPtr<IxbDnProtoStream>	prProto(m_piStm,xbOp_Optional);
		if (prProto.IsValid())
		{
			prProto->SetDnData(NULL);
			prProto->askClose(pReason);
		}
	}
	m_piStm.Empty();
	if (m_poStm.IsValid())
	{
		xbRefQIPtr<IxbDnProtoStream>	prProto(m_poStm,xbOp_Optional);
		if (prProto.IsValid())
		{
			prProto->SetDnData(NULL);
			prProto->askClose(pReason);
		}
	}
	m_poStm.Empty();
	m_bPlugOpen = false;
	m_bPlugErr = false;
	m_PlugStm.SetLength(0);

	if (pReason && m_closeReason.IsEmpty())
		m_closeReason = pReason;
}

ulong xbDnData::UrlRead(void *pData,ulong DataLg,bool bDataLgRequired)
{
	if (!m_bPlugOpen) 
		xbThrow xbEdn("xbDnData::UrlRead("+m_Url+") : url not opened");
	if (m_op != xbDnLoad) 
		xbThrow xbEdn("xbDnData::UrlRead("+m_Url+") : url opened for write operations");
	if (m_piStm.IsValid())
	{
		return m_piStm->Read(pData,DataLg,bDataLgRequired);
	}
	if (m_bPlugErr) 
		xbThrow xbEdnUser(xbHR_LAST,"Can't download url '"+m_Url+"' (error during download) !","xbDnData::UrlRead(), plugStartRead previous failure");

	return m_PlugStm.Read(pData,DataLg,bDataLgRequired);
}

ulong xbDnData::UrlWrite(void *pData,ulong DataLg,bool bDataLgRequired)
{
	if (!m_bPlugOpen) 
		xbThrow xbEdn("xbDnData::UrlWrite("+m_Url+") : url not opened");
	if (m_op != xbDnWrite) 
		xbThrow xbEdn("xbDnData::UrlWrite("+m_Url+") : url opened for read operations");

	return m_poStm->Write(pData,DataLg,bDataLgRequired);
}

int64 xbDnData::UrlGetSize() const
{
	if (!m_bPlugOpen) 
		xbThrow xbEdn("xbDnData::UrlGetSize("+m_Url+") : url not opened");
	if (m_op != xbDnLoad) 
		xbThrow xbEdn("xbDnData::UrlGetSize("+m_Url+") : url opened for write operations");
	if (m_piStm.IsValid())
	{
		{
			xbRefQIPtr<const IxbLongSeekStream>	pSeekStm(m_piStm,xbOp_Optional);
			if (pSeekStm.IsValid())
				return pSeekStm->GetLongLength();
		}
		{
			xbRefQIPtr<const IxbSeekStream>	pSeekStm(m_piStm,xbOp_Optional);
			if (pSeekStm.IsValid())
				return pSeekStm->GetLength();
		}
		{
			xbRefQIPtr<const IxbDnProtoStream>	pProtoStm(m_piStm,xbOp_Optional);
			if (pProtoStm.IsValid())
				return pProtoStm->GetReadContentLength();
		}
		return -1;
	}
	if (m_bPlugErr) 
		xbThrow xbEdnUser(xbHR_LAST,"Can't download url '"+m_Url+"' (error during download) !","xbDnData::UrlGetSize(), plugStartRead previous failure");

	return m_PlugStm.GetLength();
}

bool xbDnData::UrlSetSize(int64 sz)
{
	if (!m_bPlugOpen) 
		xbThrow xbEdn("xbDnData::UrlSetSize("+m_Url+") : url not opened");
	if (m_op != xbDnWrite) 
		xbThrow xbEdn("xbDnData::UrlSetSize("+m_Url+") : url opened for read operations");
	{
		xbRefQIPtr<IxbLongSeekStream>	pSeekStm(m_poStm,xbOp_Optional);
		if (pSeekStm.IsValid())
		{
			pSeekStm->SetLongLength(sz);
			return true;
		}
	}
	xbRefQIPtr<IxbSeekStream>	pSeekStm(m_poStm,xbOp_Optional);
	if (pSeekStm.IsEmpty()) 
		return false;

	if (sz >= 0x100000000)
		xbThrow xbEdn("xbDnData::UrlSetSize("+m_Url+") : sz too big (sz="+xbString(sz)+")");
	pSeekStm->SetLength(int(sz));
	return true;
}

void xbDnData::UrlGetReadStm(xbRefPtr<IxbInputStream> *ppStm)
{
	if (!m_bPlugOpen) 
		xbThrow xbEdn("xbDnData::UrlGetReadStm("+m_Url+") : url not opened");

	if (m_op != xbDnLoad) 
		xbThrow xbEdn("xbDnData::UrlGetReadStm("+m_Url+") : url opened for write operations");
	if (m_piStm.IsValid())
	{
		*ppStm = m_piStm;
		return;
	}
	if (m_bPlugErr) 
		xbThrow xbEdnUser(xbHR_LAST,"Can't download url '"+m_Url+"' (error during download) !","xbDnData::UrlGetReadStm(), plugStartRead previous failure");

	*ppStm = xbRefQIPtr<IxbInputStream>(&m_PlugStm);
}

void xbDnData::UrlGetWriteStm(xbRefPtr<IxbOutputStream> *ppStm)
{
	if (!m_bPlugOpen) 
		xbThrow xbEdn("xbDnData::UrlGetWriteStm("+m_Url+") : url not opened");
	if (m_op != xbDnWrite || m_poStm.IsEmpty()) 
		xbThrow xbEdn("xbDnData::UrlGetWriteStm("+m_Url+") : url opened for read operations");

	*ppStm = m_poStm;
}

void xbDnData::UrlWritingEnd(bool bCompleted)
{
	if (!m_bPlugOpen) 
		xbThrow xbEdn("xbDnData::UrlWritingEnd("+m_Url+") : url not opened");
	if (m_op != xbDnWrite || m_poStm.IsEmpty()) 
		xbThrow xbEdn("xbDnData::UrlWritingEnd("+m_Url+") : url opened for read operations");

	xbRefQIPtr<IxbDnProtoWrite>	pProto(m_poStm,xbOp_Optional);
	if (pProto.IsEmpty()) 
		return;
	pProto->onWritingStreamEnd(this,bCompleted);
}


xbString xbAddPath(const xbString &_oldUrl,const xbString &_path,sIxbUrlRoots *piRoots)
{
	//Bugs corrigés sur 6.0.4.88 (?# mal gérés)
	if (_path.IsEmpty())
		return _oldUrl;

	xbString path,dyna;
	{
		char chSep;
		_path.GetToken(0,"?#",&path,&dyna,&chSep);
		if (chSep != 0)
			dyna = chSep + dyna;
	}

	xbString proto(::xbGetProtocol(path,&path));
	if (proto.IsValid())
		return _path;

	xbString oldUrl(_oldUrl);
	if (_oldUrl.IsEmpty())
	{
		if (piRoots)
			oldUrl = piRoots->GetUrlRootForProtocol(xbString());
	}
	oldUrl.GetToken(0,"?#",&oldUrl);
	if (oldUrl.IsEmpty())
		xbThrow xbEsys("xbAddPath("+_path+") : no current url");


	bool bIsFile = false;;
	proto = ::xbGetProtocol(oldUrl,&oldUrl);
	if (proto.IsEmpty() || proto.CompareNoCase("file") == 0)
	{
		if (proto.IsEmpty())
			proto = "file";
		xbString drv(::xbGetDrive(oldUrl,&oldUrl));

		if (drv.IsValid())
		{
			xbString drvdest(::xbGetDrive(path,&path));
			if (drvdest.IsValid())
				drv = drvdest;
			proto += ':'+ drv;
		}			

		bIsFile = true;
	}

	if (/*(path.IsEmpty()&&dyna.IsEmpty()) ||*/ 
		path.IsValid() && (path.First() == '/' || path.First() == '\\'))
	{
//		if (path.IsEmpty()) 
//			path = (bIsFile?"\\":"/");

		if (bIsFile) 
			return proto + ':' + path + dyna;

		xbString srv(::xbSubPath(oldUrl));
		return proto + "://" + srv + path + dyna;
	}

	bool bEndSlash = false;
	if (oldUrl.IsValid() && path.IsValid())
	{
		char ch = oldUrl.Last();
		if (ch != '/' && ch != '\\')
		{
			xbString del(::xbSubPathEnd(oldUrl,&oldUrl));
			if (!bIsFile && oldUrl.IsEmpty()) //Bugs corrigés sur 5.0.3.69 / > 9/5/03 test bIsFile added
			{
				oldUrl = del;
				bEndSlash = true;
			}
		}
	}
	//Bugs corrigés sur 5.0.3.69 / > 14/1/03 if bIsFile = true don't add //
	xbString ret(oldUrl);
	while(path.IsValid())
	{
		xbString sub(xbSubPath(path,&path,&bEndSlash));
		if (sub.IsEmpty() || sub == ".") 
		{
			bEndSlash = true;
			continue;
		}
		if (sub == "..")
		{
			ret.TrimRight("\\/");
			xbSubPathEnd(ret,&ret);
			bEndSlash = true;
			continue;
		}
		if (ret.IsValid())
		{
			ret.TrimRight("\\/");
			ret += (bIsFile?xbPath_SLASHch:'/');
		}
		ret += sub;
	}
	ret = proto+(bIsFile?":":"://") + ret;

	if (bEndSlash)
	{
		char ch = ret.Last();
		if (ch != '/' && ch != '\\')
			ret += (bIsFile?xbPath_SLASHch:'/');
	}
	return ret+dyna;
}

xbString xbSetProtocol(const xbString &_url,const xbString &proto)
{
	xbString url(_url);
	::xbGetProtocol(url,&url);

	return proto + "://" + url;
}


xbString xbAddPath(const xbString &_oldUrl,const xbString &_path)
{
	if (_path.IsEmpty())
		return _oldUrl;

	xbString path,dyna;
	{
		char chSep;
		_path.GetToken(0,"?#",&path,&dyna,&chSep);
		if (chSep != 0)
			dyna = chSep + dyna;
	}

	xbString proto(::xbGetProtocol(path,&path));
	if (proto.IsValid())
		return _path;

	xbString oldUrl(_oldUrl);
	oldUrl.GetToken(0,"?#",&oldUrl);


	bool bIsFile = false;;
	proto = ::xbGetProtocol(oldUrl,&oldUrl);
	if (proto.IsEmpty() || proto.CompareNoCase("file") == 0)
	{
		xbString drv(::xbGetDrive(oldUrl,&oldUrl));

		if (drv.IsValid())
		{
			if (proto.IsEmpty())
				proto = "file";

			xbString drvdest(::xbGetDrive(path,&path));
			if (drvdest.IsValid())
				drv = drvdest;
			proto += ':'+ drv;
		}			

		bIsFile = proto.IsValid();
	}

	if (oldUrl.IsEmpty() || (path.IsValid() && (path.First() == '/' || path.First() == '\\')))
	{
		if (proto.IsEmpty())
			return path + dyna;

		if (bIsFile) 
			return proto + ':' + path + dyna;

		xbString srv(::xbSubPath(oldUrl));
		return proto + "://" + srv + path + dyna;
	}

	bool bEndSlash = false;
	if (path.IsValid() && proto.IsValid())
	{
		char ch = oldUrl.Last();
		if (ch != '/' && ch != '\\')
		{
			xbString del(::xbSubPathEnd(oldUrl,&oldUrl));
			if (!bIsFile && oldUrl.IsEmpty()) //Bugs corrigés sur 5.0.3.69 / > 9/5/03 test bIsFile added
			{
				oldUrl = del;
				bEndSlash = true;
			}
		}
	}
	//Bugs corrigés sur 5.0.3.69 / > 14/1/03 if bIsFile = true don't add //
	xbString ret(oldUrl);
	while(path.IsValid())
	{
		xbString sub(xbSubPath(path,&path,&bEndSlash));
		if (sub.IsEmpty() || sub == ".") 
		{
			bEndSlash = proto.IsValid();
			continue;
		}
		if (sub == "..")
		{
			ret.TrimRight("\\/");
			xbSubPathEnd(ret,&ret);
			bEndSlash = proto.IsValid();
			continue;
		}
		if (ret.IsValid())
		{
			ret.TrimRight("\\/");
			ret += (bIsFile?xbPath_SLASHch:'/');
		}
		ret += sub;
	}
	if (proto.IsValid())
		ret = proto+(bIsFile?":":"://") + ret;

	if (bEndSlash)
	{
		if (ret.IsValid())
		{
			char ch = ret.Last();
			if (ch != '/' && ch != '\\')
				ret += (bIsFile?xbPath_SLASHch:'/');
		}
		else
			ret = (bIsFile?xbPath_SLASH:"/");
	}
	return ret+dyna;
}

#pragma warning(pop)
