// ------------------------------------------------
// File : servhs.cpp
// Date: 4-apr-2002
// Author: giles
// Desc: 
//		Servent handshaking, TODO: should be in its own class
//
// (c) 2002 peercast.org
// ------------------------------------------------
// 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.
// ------------------------------------------------


#include <stdlib.h>
#include "servent.h"
#include "servmgr.h"
#include "html.h"
#include "stats.h"
#include "peercast.h"

// -----------------------------------
static void termArgs(char *str)
{
	if (str)
	{
		int slen = strlen(str);
		for(int i=0; i<slen; i++)
			if (str[i]=='&') str[i] = 0;
	}
}
// -----------------------------------
char *nextCGIarg(char *cp, char *cmd, char *arg)
{
	if (!*cp)
		return NULL;

	// fetch command
	while (*cp)
	{
		char c = *cp++;
		if (c == '=')
			break;
		else
			*cmd++ = c;
	}
	*cmd = 0;

	// fetch arg
	while (*cp)
	{
		char c = *cp++;
		if (c == '&')
			break;
		else
			*arg++ = c;
	}
	*arg = 0;

	return cp;
}
// -----------------------------------
bool getCGIargBOOL(char *a)
{
	return (strcmp(a,"1")==0);
}
// -----------------------------------
int getCGIargINT(char *a)
{
	return atoi(a);
}

// -----------------------------------
void Servent::handshakeHTTP(HTTP &http, bool isHTTP)
{
	char *in = http.cmdLine;

	if (http.isRequest("GET /"))
	{
		char *fn = in+4;

		char *pt = strstr(fn,HTTP_PROTO1);
		if (pt)
			pt[-1] = 0;

		if (strncmp(fn,"/admin?",7)==0)
		{
			if (!isAllowed(ALLOW_HTML))
				throw HTTPException(HTTP_SC_UNAUTHORIZED,401);


			LOG_DEBUG("Admin client");
			handshakeCMD(fn+7);
		}else if (strncmp(fn,"/admin/?",8)==0)
		{
			if (!isAllowed(ALLOW_HTML))
				throw HTTPException(HTTP_SC_UNAUTHORIZED,401);

			LOG_DEBUG("Admin client");
			handshakeCMD(fn+8);

		}else if (strncmp(fn,"/admin.cgi",10)==0)
		{
			if (!isAllowed(ALLOW_BROADCAST))
				throw HTTPException(HTTP_SC_UNAUTHORIZED,401);

			char *pwdArg = getCGIarg(fn,"pass=");
			char *songArg = getCGIarg(fn,"song=");
			char *mountArg = getCGIarg(fn,"mount=");
			char *urlArg = getCGIarg(fn,"url=");

			if (pwdArg && songArg)
			{
				int i;
				int slen = strlen(fn);
				for(i=0; i<slen; i++)
					if (fn[i]=='&') fn[i] = 0;

				Channel *c=NULL;

				for(i=0; i<ChanMgr::MAX_CHANNELS; i++)
				{
					c = &chanMgr->channels[i];
					if ((c->status == Channel::S_BROADCASTING) &&
					   (c->info.contentType == ChanInfo::T_MP3) )
					{
						// if we have a mount point then check for it, otherwise update all channels.
						if (mountArg)
							if (strcmp(c->mount,mountArg))
								c = NULL;

						if (c)
						{
							c->info.track.title.set(songArg,String::T_ESC);

							if (urlArg)
								if (urlArg[0])
									c->info.track.contact.set(urlArg,String::T_ESC);
							LOG_CHANNEL("Ch.%d Shoutcast update: %s",c->index,songArg);
							c->updateMeta();
						}
					}
				}
			}

		}else if (strncmp(fn,"/pls/",5)==0)
		{
			Channel *c = getChannel(fn+5);
			if (c)
				handshakePLS(&c,1,false);

		}else if (strncmp(fn,"/stream/",8)==0)
		{

			triggerChannel(fn+8,true);

		}else if (strncmp(fn,"/channel/",9)==0)
		{

			triggerChannel(fn+9,false);

		}else if (strncmp(fn,"/rtsp/",6)==0)
		{

			//handshakeRTSP(fn+6);

		}else if (strcmp(fn,"/")==0)
		{
			if (!isAllowed(ALLOW_HTML))
				throw HTTPException(HTTP_SC_UNAUTHORIZED,401);

			LOG_NETWORK("HTML client");
			handshakeCMD(fn+1);

		}else{

		    while (http.nextHeader())
				LOG_DEBUG("GET: %s",http.cmdLine);

			throw HTTPException(HTTP_SC_BADREQUEST,400);
		}
	}else if (http.isRequest("GIV"))
	{
		if (!isAllowed(ALLOW_DATA))
			throw HTTPException(HTTP_SC_UNAUTHORIZED,401);

		char *end = strstr(in+4,":");
		if (!end)
			throw HTTPException(HTTP_SC_BADREQUEST,400);
		*end = 0;

		int index = atoi(in+4);
		// at the moment we don`t really care where the GIV came from, so just give to chan. no. if its waiting.
		Channel *ch = chanMgr->findPushChannel(index);

		if (ch)
			LOG_NETWORK("GIV %d to channel %d",index,ch->index);
		else
			LOG_NETWORK("GIV %d, no channels waiting",index);

		if (!ch)
			throw HTTPException(HTTP_SC_NOTFOUND,404);

		ch->pushSock = sock;
		ch->pushIndex = 0;

		sock = NULL;	// release this servent but dont close socket.	

	}else if (http.isRequest("PEERCAST CONNECT"))
	{
		if (!isAllowed(ALLOW_SERVENT))
			throw HTTPException(HTTP_SC_UNAUTHORIZED,401);

		LOG_DEBUG("PEERCAST client");
		processServent();

	}else if (http.isRequest("GNUTELLA CONNECT"))
	{
		if (!isAllowed(ALLOW_SERVENT))
			throw HTTPException(HTTP_SC_UNAUTHORIZED,401);

		if ((servMgr->allowGnutella) && !servMgr->inFull())
		{
			LOG_DEBUG("GNUTELLA client");
			processServent();
		}else{
			throw HTTPException(HTTP_SC_UNAVAILABLE,503);
		}

	}else if (http.isRequest("SOURCE"))
	{
		if (!isAllowed(ALLOW_BROADCAST))
			throw HTTPException(HTTP_SC_UNAUTHORIZED,401);

		char *mount = NULL;

		char *ps;
		if (ps=strstr(in,"ICE/1.0"))
		{
			mount = in+7;
			*ps = 0;
			LOG_DEBUG("ICE 1.0 client to %s",mount?mount:"unknown");
		}else{
			mount = in+strlen(in);
			while (*--mount)
				if (*mount == '/')
				{
					mount[-1] = 0; // password preceeds
					break;
				}
			strcpy(loginPassword,in+7);
			
			LOG_DEBUG("ICY client to %s",mount?mount:"unknown");
		}

		if (mount)
			strcpy(loginMount,mount);

		handshakeICY(Channel::SRC_ICECAST,isHTTP);
		sock = NULL;	// socket is taken over by channel, so don`t close it

	}else if (http.isRequest(servMgr->password))
	{
		if (!isAllowed(ALLOW_BROADCAST))
			throw HTTPException(HTTP_SC_UNAUTHORIZED,401);

		strcpy(loginPassword,servMgr->password);	// pwd already checked

		sock->writeLine("OK2");
		sock->writeLine("icy-caps:11");
		sock->writeLine("");
		LOG_DEBUG("ShoutCast client");

		handshakeICY(Channel::SRC_SHOUTCAST,isHTTP);
		sock = NULL;	// socket is taken over by channel, so don`t close it

	}else if (http.isRequest("POST"))
	{
		handshakePOST();
	}else
	{
		throw HTTPException(HTTP_SC_BADREQUEST,400);
	}

}
// -----------------------------------
// find a channel in list, or search if not found.
Channel *Servent::getChannel(char *str)
{

	if (!isAllowed(ALLOW_DATA))
		throw HTTPException(HTTP_SC_UNAUTHORIZED,401);


	if (servMgr->streamFull() && !isPrivate())
		throw HTTPException(HTTP_SC_UNAVAILABLE,503);	


	ChanInfo info;
	info.initNameID(str);

	servMgr->procConnectArgs(str,info.id);

	HTTP http(*sock);

	// remove file extension (only added for winamp)
	char *ext = strstr(str,".");

	if (ext) *ext = 0;

	Channel *ch;

	ch=chanMgr->findChannel(info);	

	// channel not in relay list, so find it hitlist
	if (!ch)
		if (!chanMgr->findAndRelay(info,&ch,1))
			ch=NULL;

	if (!ch)
		throw HTTPException(HTTP_SC_NOTFOUND,404);

	bool isFull = servMgr->bitrateFull(ch->getBitrate()) || ch->isFull() || servMgr->streamFull();

	if (isFull && !isPrivate())
		throw HTTPException(HTTP_SC_UNAVAILABLE,503);	

	return ch;
}
// -----------------------------------
void Servent::handshakeIncoming()
{
	sock->timeout = 10000;

	setStatus(S_HANDSHAKE);

	char buf[1024];
	sock->readLine(buf,sizeof(buf));

	char sb[64];
	sock->host.toStr(sb);


	if (stristr(buf,RTSP_PROTO1))
	{
		LOG_DEBUG("RTSP from %s '%s'",sb,buf);
		RTSP rtsp(*sock);
		rtsp.initRequest(buf);
		handshakeRTSP(rtsp);
	}else if (stristr(buf,HTTP_PROTO1))
	{
		LOG_DEBUG("HTTP from %s '%s'",sb,buf);
		HTTP http(*sock);
		http.initRequest(buf);
		handshakeHTTP(http,true);
	}else
	{
		LOG_DEBUG("Connect from %s '%s'",sb,buf);
		HTTP http(*sock);
		http.initRequest(buf);
		handshakeHTTP(http,false);
	}

}
// -----------------------------------
void Servent::triggerChannel(char *n, bool isRaw)	
{
	if (isRaw)
	{
		// check that this servent can listen
		if (!canPreview())
			throw HTTPException(HTTP_SC_UNAVAILABLE,503);	
	}

	Channel *ch = getChannel(n);

	if (ch)
	{

		LOG_DEBUG("Channel client: OK");
		type = T_STREAM;
		chanID = ch->info.id;
		ch->prefetchCnt = 20;	// prefetch for 20 packets 
		processStream(false,isRaw);
	}
}
// -----------------------------------
void writePLSHeader(Stream &s, PlayList::TYPE type)
{
	s.writeLine(HTTP_SC_OK);
	s.writeLine("%s %s",HTTP_HS_SERVER,PCX_AGENT);

	const char *content;
	switch(type)
	{
		case PlayList::T_PLS:
			content = MIME_XM3U;
			break;
		case PlayList::T_ASX:
			content = MIME_ASX;
			break;
		default:
			content = MIME_TEXT;
			break;
	}
	s.writeLine("%s %s",HTTP_HS_CONTENT,content);
    //s.writeLine("Content-Disposition: inline; filename=playlist.m3u");
    s.writeLine("Content-Disposition: inline");
    s.writeLine("Cache-Control: private" );
	s.writeLine("%s %s",HTTP_HS_CONNECTION,"close");

	s.writeLine("");
}

// -----------------------------------
void Servent::handshakePLS(Channel **cl, int num, bool doneHandshake)
{
	char url[256];

	char in[128];

	if (!num)
		throw StreamException("Nothing to play");

	if (!doneHandshake)
		while (sock->readLine(in,128));

	if (getLocalURL(url))
	{

		PlayList::TYPE type;

		Channel *c1 = cl[0];
		if ((c1->info.contentType == ChanInfo::T_WMA) || (c1->info.contentType == ChanInfo::T_WMV))
			type = PlayList::T_ASX;
		else
			type = PlayList::T_PLS;

		writePLSHeader(*sock,type);

		PlayList *pls;

		pls = new PlayList(type,num);

		pls->addChannels(url,cl,num);

		pls->write(*sock);

		delete pls;
	}
}
// -----------------------------------
void Servent::handshakePLS(ChanHitList **cl, int num, bool doneHandshake)
{
	char url[256];
	char in[128];

	if (!doneHandshake)
		while (sock->readLine(in,128));

	if (getLocalURL(url))
	{
		writePLSHeader(*sock,PlayList::T_SCPLS);

		PlayList *pls;

		pls = new PlayList(PlayList::T_SCPLS,num);

		for(int i=0; i<num; i++)
			pls->addChannel(url,cl[i]->info);

		pls->write(*sock);

		delete pls;
	}
}
// -----------------------------------
bool Servent::getLocalURL(char *str)
{
	if (!sock)
		throw StreamException("Not connected");


	char ipStr[64];

	Host h;
	
	if (sock->host.localIP())
		h = sock->getLocalHost();
	else
		h = servMgr->serverHost;

	h.port = servMgr->serverHost.port;

	h.toStr(ipStr);

	sprintf(str,"http://%s",ipStr);			
	return true;
}

// -----------------------------------
// Warning: testing RTSP/RTP stuff below.
// .. moved over to seperate app now.
// -----------------------------------
void Servent::handshakePOST()
{
	char tmp[1024];
    while (sock->readLine(tmp,sizeof(tmp)))
		LOG_DEBUG("POST: %s",tmp);

	throw HTTPException(HTTP_SC_BADREQUEST,400);
}


// -----------------------------------
void Servent::handshakeRTSP(RTSP &rtsp)
{
	throw HTTPException(HTTP_SC_BADREQUEST,400);
}
// -----------------------------------
void Servent::handshakeCMD(char *cmd)
{
	char tmp[256];
	char result[256];
	char arg[512];
	char curr[256];

	char	jumpStr[128];
	char	*jumpArg=NULL;
	bool	retHTML=true;
	strcpy(result,"OK");

	HTTP http(*sock);

	char user[64],pass[64];

	Cookie cookie;


	user[0] = pass[0] = 0;
    while (http.nextHeader())
	{
		char *arg = http.getArgStr();
		if (!arg)
			continue;

		//LOG_DEBUG("%s",http.cmdLine);

		switch (servMgr->authType)
		{
			case ServMgr::AUTH_HTTPBASIC:
				if (http.isHeader("Authorization"))
					http.getAuthUserPass(user,pass);
				break;
			case ServMgr::AUTH_COOKIE:
				if (http.isHeader("Cookie"))
				{
					Cookie c;
					LOG_DEBUG("Got cookie: %s",arg);
					char *idp=arg;
					while ((idp = strstr(idp,"id=")))
					{
						idp+=3;
						c.set(idp,sock->host.ip);
						if (servMgr->cookieList.contains(c))
						{
							LOG_DEBUG("Cookie found");
							cookie = c;
							break;
						}

					}
				}
				break;
		}
	}


	//servMgr->serverHost.toResolvedName(arg);
	servMgr->serverHost.toStr(arg);


	sprintf(tmp,"PeerCast on %s",arg);

	HTML html(tmp,*sock);


	if (!sock->host.isLocalhost())
	{
		if (!cmpCGIarg(cmd,"pass=",servMgr->password))
		{
			switch (servMgr->authType)
			{
				case ServMgr::AUTH_HTTPBASIC:
						
					if (strcmp(pass,servMgr->password)!=0)
					{
						http.writeLine(HTTP_SC_UNAUTHORIZED);
						http.writeLine("WWW-Authenticate: Basic realm=\"PeerCast Admin\"");
						http.writeLine("%s %s",HTTP_HS_SERVER,PCX_AGENT);
						http.writeLine("%s %s",HTTP_HS_CONTENT,MIME_HTML);
						http.writeLine("%s %s",HTTP_HS_CACHE,"no-cache");
						http.writeLine("%s %s",HTTP_HS_CONNECTION,"close");

						http.writeLine("");

						html.startHTML();
							html.addHead();
							html.startBody();
								html.startTagEnd("h3","Please enter a valid password");
							html.end();
						html.end();
						return;
					}
					break;
				case ServMgr::AUTH_COOKIE:
					if (!servMgr->cookieList.contains(cookie))
					{
						addLoginPage(html);
						return;
					}
					break;
				default:
					http.writeLine(HTTP_SC_UNAUTHORIZED);
					http.writeLine("");
					return;
			}
		}
	}



	try
	{
		if (cmpCGIarg(cmd,"cmd=","redirect"))
		{
			char *j = getCGIarg(cmd,"url=");
			if (j)
			{
				termArgs(cmd);
				String url;
				url.set(j,String::T_ESC);
				url.convertTo(String::T_ASCII);

				if (!url.contains("http://"))
					url.prepend("http://");

				html.setRefreshURL(url.cstr());
				html.startHTML();
					html.addHead();
					html.startBody();
						html.startTagEnd("h3","Please wait...");
					html.end();
				html.end();

			}
		}else{

			if (cmpCGIarg(cmd,"cmd=","viewxml"))
			{

				handshakeXML();
				retHTML = false;
			}else if (cmpCGIarg(cmd,"cmd=","clearlog"))
			{
				sys->logBuf->clear();
				sprintf(jumpStr,"/admin?page=viewlog");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","save"))
			{

				peercastInst->saveSettings();

				sprintf(jumpStr,"/admin?page=settings");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","apply"))
			{	
				servMgr->numFilters = 0;
				ServFilter *currFilter=servMgr->filters;

				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					// server
					if (strcmp(curr,"serveractive")==0)
						servMgr->autoServe = getCGIargBOOL(arg);
					else if (strcmp(curr,"port")==0)
						servMgr->serverHost.port = getCGIargINT(arg);
					else if (strcmp(curr,"icymeta")==0)
					{
						int iv = getCGIargINT(arg);
						if (iv < 0) iv = 0;
						else if (iv > 16384) iv = 16384;

						chanMgr->icyMetaInterval = iv;

					}else if (strcmp(curr,"passnew")==0)
						strcpy(servMgr->password,arg);
					else if (strcmp(curr,"root")==0)
						servMgr->isRoot = getCGIargBOOL(arg);
					else if (strcmp(curr,"forceip")==0)
						strcpy(servMgr->forceIP,arg);
					else if (strcmp(curr,"djmsg")==0)
					{
						String msg;
						msg.set(arg,String::T_ESC);
						msg.convertTo(String::T_ASCII);
						chanMgr->setBroadcastMsg(msg);
					}
					else if (strcmp(curr,"pcmsg")==0)
					{
						servMgr->rootMsg.set(arg,String::T_ESC);
						servMgr->rootMsg.convertTo(String::T_ASCII);
					}


					// connections
					else if (strcmp(curr,"max_pr_time")==0)
						servMgr->maxPreviewTime = getCGIargINT(arg);
					else if (strcmp(curr,"max_pr_wait")==0)
						servMgr->maxPreviewWait = getCGIargINT(arg);

					else if (strcmp(curr,"maxup")==0)
						servMgr->maxBitrate = getCGIargINT(arg);
					else if (strcmp(curr,"maxstream")==0)
						servMgr->maxStreams = getCGIargINT(arg);
					else if (strcmp(curr,"maxlisten")==0)
						chanMgr->maxStreamsPerChannel = getCGIargINT(arg);
					else if (strcmp(curr,"maxin")==0)
						servMgr->maxIncoming = getCGIargINT(arg);
					else if (strcmp(curr,"maxout")==0)
						servMgr->maxOutgoing = getCGIargINT(arg);
					else if (strcmp(curr,"maxtry")==0)
						servMgr->maxTryout = getCGIargINT(arg);
					else if (strcmp(curr,"minconn")==0)
						servMgr->minConnected = getCGIargINT(arg);
					else if (strcmp(curr,"servbw")==0)
						servMgr->serventBandwidth = getCGIargINT(arg);
					else if (strncmp(curr,"filt_",5)==0)
					{
						char *fs = curr+5;
						{
							if (strncmp(fs,"ip",2)==0)		// ip must be first
							{
								currFilter = &servMgr->filters[servMgr->numFilters];
								currFilter->init();	
								currFilter->host.fromStrIP(arg,DEFAULT_PORT);
								if ((currFilter->host.ip) && (servMgr->numFilters < (ServMgr::MAX_FILTERS-1)))
								{
									servMgr->numFilters++;
									servMgr->filters[servMgr->numFilters].init();	// clear new entry
								}

							}else if (strncmp(fs,"bn",2)==0)
								currFilter->flags |= ServFilter::F_BAN;
							else if (strncmp(fs,"pr",2)==0)
								currFilter->flags |= ServFilter::F_PRIVATE;
							else if (strncmp(fs,"nw",2)==0)
								currFilter->flags |= ServFilter::F_NETWORK;
							else if (strncmp(fs,"di",2)==0)
								currFilter->flags |= ServFilter::F_DIRECT;
						}
					}

					// client
					else if (strcmp(curr,"clientactive")==0)
						servMgr->autoConnect = getCGIargBOOL(arg);
					else if (strcmp(curr,"lookup")==0)
					{
						String str(arg,String::T_ESC);
						str.convertTo(String::T_ASCII);
						strcpy(servMgr->connectHost,str.cstr());
					}
					else if (strcmp(curr,"deadhitage")==0)
						chanMgr->deadHitAge = getCGIargINT(arg);
					else if (strcmp(curr,"refresh")==0)
						servMgr->refreshHTML = getCGIargINT(arg);
					else if (strcmp(curr,"auth")==0)
					{
						if (strcmp(arg,"cookie")==0)
							servMgr->authType = ServMgr::AUTH_COOKIE;
						else if (strcmp(arg,"http")==0)
							servMgr->authType = ServMgr::AUTH_HTTPBASIC;

					}else if (strcmp(curr,"expire")==0)
					{
						if (strcmp(arg,"session")==0)
							servMgr->cookieList.neverExpire = false;
						else if (strcmp(arg,"never")==0)
							servMgr->cookieList.neverExpire = true;
					}
				}



				// log options
				servMgr->showLog = 0;

				servMgr->showLog |= cmpCGIarg(cmd,"logDebug=","1") ? (1<<LogBuffer::T_DEBUG):0;
				servMgr->showLog |= cmpCGIarg(cmd,"logErrors=","1") ? (1<<LogBuffer::T_ERROR):0;
				servMgr->showLog |= cmpCGIarg(cmd,"logNetwork=","1") ? (1<<LogBuffer::T_NETWORK):0;
				servMgr->showLog |= cmpCGIarg(cmd,"logChannel=","1") ? (1<<LogBuffer::T_CHANNEL):0;


				unsigned int a;

				a=0;
				a = cmpCGIarg(cmd,"allowHTML1=","1") ? (a|ALLOW_HTML) : (a&~ALLOW_HTML);
				a = cmpCGIarg(cmd,"allowServent1=","1") ? (a|ALLOW_SERVENT) : (a&~ALLOW_SERVENT);
				a = cmpCGIarg(cmd,"allowData1=","1") ? (a|ALLOW_DATA) : (a&~ALLOW_DATA);
				a = cmpCGIarg(cmd,"allowBroadcast1=","1") ? (a|ALLOW_BROADCAST) : (a&~ALLOW_BROADCAST);
				servMgr->allowServer1 = a;

				a = 0;
				a = cmpCGIarg(cmd,"allowHTML2=","1") ? (a|ALLOW_HTML) : (a&~ALLOW_HTML);
				a = cmpCGIarg(cmd,"allowServent2=","1") ? (a|ALLOW_SERVENT) : (a&~ALLOW_SERVENT);
				a = cmpCGIarg(cmd,"allowData2=","1") ? (a|ALLOW_DATA) : (a&~ALLOW_DATA);
				a = cmpCGIarg(cmd,"allowBroadcast2=","1") ? (a|ALLOW_BROADCAST) : (a&~ALLOW_BROADCAST);
				servMgr->allowServer2 = a;


				peercastInst->saveSettings();
				peercastApp->updateSettings();

				sprintf(jumpStr,"/admin?page=settings");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"page=","chaninfo"))
			{

				GnuID id;
				char *cp = cmd;
				bool fromRelay=false;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"id")==0)
						id.fromStr(arg);
					else if (strcmp(curr,"relay")==0)
						fromRelay = atoi(arg)!=0;
				}

				html.setRefresh(servMgr->refreshHTML);
				addHeader(html,0);

				
				ChanHitList *chl = chanMgr->findHitListByID(id);
				if (fromRelay)
				{
					Channel *c = chanMgr->findChannelByID(id);
					if (c)
					{
						addChanInfo(html,&c->info,c);
						if (chl)
							addChanHits(html,chl,&c->currSource,c->info);
					}
				}else
				{
					if (chl)
					{
						addChanInfo(html,&chl->info,NULL);
						addChanHits(html,chl,NULL,chl->info);
					}
				}
				addFooter(html);
				

			}else if (cmpCGIarg(cmd,"cmd=","fetch"))
			{

				ChanInfo info;
				String curl;

				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"url")==0)
					{
						curl.set(arg,String::T_ESC);
						curl.convertTo(String::T_ASCII);
					}else if (strcmp(curr,"name")==0)
					{
						info.name.set(arg,String::T_ESC);
						info.name.convertTo(String::T_ASCII);
					}else if (strcmp(curr,"desc")==0)
					{
						info.desc.set(arg,String::T_ESC);
						info.desc.convertTo(String::T_ASCII);
					}else if (strcmp(curr,"genre")==0)
					{
						info.genre.set(arg,String::T_ESC);
						info.genre.convertTo(String::T_ASCII);
					}else if (strcmp(curr,"contact")==0)
					{
						info.url.set(arg,String::T_ESC);
						info.url.convertTo(String::T_ASCII);
					}else if (strcmp(curr,"bitrate")==0)
					{
						info.bitrate = atoi(arg);
					}else if (strcmp(curr,"type")==0)
					{
						info.contentType = ChanInfo::getTypeFromStr(arg);
					}

				}

				Channel *c = chanMgr->createChannel(info,NULL);
				if (c)
					c->startURL(curl.cstr());


				sprintf(jumpStr,"/admin?page=mychans");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","stopserv"))
			{

				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"index")==0)
					{
						int i = atoi(arg);
						if ((i >= 0) && (i < ServMgr::MAX_SERVENTS))
							servMgr->servents[i].thread.active = false;
					}
				}
				sprintf(jumpStr,"/admin?page=connections");					
				jumpArg = jumpStr;
			}else if (cmpCGIarg(cmd,"cmd=","stopfind"))
			{
				chanMgr->lastHit = 0;
				chanMgr->numFinds = 0;
				chanMgr->searchActive = false;

				sprintf(jumpStr,"/admin?page=chans");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","find"))
			{
				ChanInfo info;
				info.init();

				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"name")==0)
						info.name.set(arg);
					else if (strcmp(curr,"genre")==0)
						info.genre.set(arg);
					else if (strcmp(curr,"bitrate")==0)
						info.bitrate = atoi(arg);
					else if (strcmp(curr,"id")==0)
						info.id.fromStr(arg);
				}

				chanMgr->startSearch(info);

				sprintf(jumpStr,"/admin?page=chans");					
				jumpArg = jumpStr;


			}else if (cmpCGIarg(cmd,"cmd=","hitlist"))
			{			
					
				Channel *clist[ChanMgr::MAX_CHANNELS];
				int numChans=0;
				bool stayConnected=hasCGIarg(cmd,"relay");

				for(int i=0; i<ChanMgr::MAX_HITLISTS; i++)
				{
					ChanHitList *chl = &chanMgr->hitlists[i];
					if (chl->isUsed())
					{
						char tmp[64];
						sprintf(tmp,"c%d=",i);
						if (cmpCGIarg(cmd,tmp,"1"))
						{
							Channel *c;
							if (!(c=chanMgr->findChannelByID(chl->info.id)))
							{
								c = chanMgr->createChannel(chl->info,NULL);
								if (!c)
									throw StreamException("out of channels");
								c->stayConnected = stayConnected;
								c->startGet();
							}
							clist[numChans++] = c;
						}
					}
				}

				char *findArg = getCGIarg(cmd,"keywords=");

				if (hasCGIarg(cmd,"play"))
				{
					handshakePLS(clist,numChans,true);
					retHTML = false;

				}else if (hasCGIarg(cmd,"relay"))
				{
					sys->sleep(500);
					sprintf(jumpStr,"/admin?page=mychans");					
					jumpArg = jumpStr;
				}
			}else if (cmpCGIarg(cmd,"cmd=","clear"))
			{
				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"hostcache")==0)
						servMgr->clearHostCache(ServHost::T_SERVENT);
					else if (strcmp(curr,"packets")==0)
					{
						stats.clearRange(Stats::PACKETSSTART,Stats::PACKETSEND);
						servMgr->numVersions = 0;
					}
				}

				sprintf(jumpStr,"/admin?page=index");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","upgrade"))
			{
				if (servMgr->downloadURL[0])
				{
					sprintf(jumpStr,"/admin?cmd=redirect&url=%s",servMgr->downloadURL);
					jumpArg = jumpStr;
				}


			}else if (cmpCGIarg(cmd,"cmd=","chan"))
			{			
					
				Channel *clist2[ChanMgr::MAX_CHANNELS];
				int num2=0;

				Channel *clist[ChanMgr::MAX_CHANNELS];
				ChanInfo info;
				int num = chanMgr->findChannels(info,clist,ChanMgr::MAX_CHANNELS);

				for(int i=0; i<num; i++)
				{
					Channel *c = clist[i];
					char tmp[64];
					sprintf(tmp,"c%d=",c->index);
					if (cmpCGIarg(cmd,tmp,"1"))
						clist2[num2++] = c;
				}

				bool delay=false;

				if (hasCGIarg(cmd,"play"))
				{
					handshakePLS(clist2,num2,true);
					retHTML = false;
				}

				if (hasCGIarg(cmd,"bump"))
				{
					for(int i=0; i<num2; i++)
						clist2[i]->bump = true;
					delay = true;
				}

				if (hasCGIarg(cmd,"keep"))
				{
					for(int i=0; i<num2; i++)
						clist2[i]->stayConnected = true;
					delay = true;
				}

				if (hasCGIarg(cmd,"stop"))
				{
					for(int i=0; i<num2; i++)
						clist2[i]->thread.active = false;
					delay = true;
				}

				if (delay)
					sys->sleep(500);

				sprintf(jumpStr,"/admin?page=mychans");					
				jumpArg = jumpStr;


			}else if (cmpCGIarg(cmd,"cmd=","connect"))
			{			
					
				for(int i=0; i<ServMgr::MAX_SERVENTS; i++)
				{
					Servent *s = &servMgr->servents[i];
					char tmp[64];
					sprintf(tmp,"c%d=",i);
					if (cmpCGIarg(cmd,tmp,"1"))
					{
						if (hasCGIarg(cmd,"stop"))
							s->thread.active = false;
					}
				}
				sprintf(jumpStr,"/admin?page=connections");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","shutdown"))
			{
				servMgr->shutdownTimer = 1;

			}else if (cmpCGIarg(cmd,"cmd=","stop"))
			{
				GnuID id;
				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"id")==0)
						id.fromStr(arg);
				}

				Channel *c = chanMgr->findChannelByID(id);
				if (c)
					c->thread.active = false;

				sys->sleep(500);
				sprintf(jumpStr,"/admin?page=mychans");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","bump"))
			{
				GnuID id;
				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"id")==0)
						id.fromStr(arg);
				}

				Channel *c = chanMgr->findChannelByID(id);
				if (c)
					c->bump = true;

				sprintf(jumpStr,"/admin?page=mychans");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","keep"))
			{
				GnuID id;
				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"id")==0)
						id.fromStr(arg);
				}

				Channel *c = chanMgr->findChannelByID(id);
				if (c)
					c->stayConnected = true;

				sprintf(jumpStr,"/admin?page=mychans");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"cmd=","relay"))
			{
				GnuID id;
				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"id")==0)
						id.fromStr(arg);
				}


				if (!chanMgr->findChannelByID(id))
				{

					ChanHitList *chl = chanMgr->findHitListByID(id);
					if (!chl)
						throw StreamException("channel not found");


					Channel *c = chanMgr->createChannel(chl->info,NULL);
					if (!c)
						throw StreamException("out of channels");

					c->stayConnected = true;
					c->startGet();
				}

				sprintf(jumpStr,"/admin?page=mychans");					
				jumpArg = jumpStr;

			}else if (cmpCGIarg(cmd,"page=","chans"))
			{

				ChanInfo info;
				info.init();
				SORT sort=SORT_NAME;
				bool dir=false;

				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"name")==0)
						info.name.set(arg);
					else if (strcmp(curr,"genre")==0)
						info.genre.set(arg);
					else if (strcmp(curr,"bitrate")==0)
						info.bitrate = atoi(arg);
					else if (strcmp(curr,"id")==0)
						info.id.fromStr(arg);
					else if (strcmp(curr,"sort")==0)
					{

						if (strcmp(arg,"name")==0)
							sort = SORT_NAME;
						else if (strcmp(arg,"bitrate")==0)
							sort = SORT_BITRATE;
						else if (strcmp(arg,"listeners")==0)
							sort = SORT_LISTENERS;
						else if (strcmp(arg,"hosts")==0)
							sort = SORT_HOSTS;
						else if (strcmp(arg,"type")==0)
							sort = SORT_TYPE;
						else if (strcmp(arg,"genre")==0)
							sort = SORT_GENRE;

					}else if (strcmp(curr,"dir")==0)
					{
						if (strcmp(arg,"up")==0)
							dir = true;
						else if (strcmp(arg,"down")==0)
							dir = false;
					}
				}


				addAllChannelsPage(html,sort,dir,&info);

			}else if (cmpCGIarg(cmd,"page=","winamp-settings"))
			{
				addWinampSettingsPage(html);

			}else if (cmpCGIarg(cmd,"page=","winamp-chans"))
			{

				char wildcard[128],type[128];
				bool stop=false;
				wildcard[0] = type[0]=0;
				char *cp = cmd;
				while (cp=nextCGIarg(cp,curr,arg))
				{
					if (strcmp(curr,"wildcard")==0)
						strcpy(wildcard,arg);
					else if (strcmp(curr,"type")==0)
						strcpy(type,arg);
					else if (strcmp(curr,"stop")==0)
						stop=true;
				}

				addWinampChansPage(html,wildcard,type,stop);

			}else if (cmpCGIarg(cmd,"page=","mychans"))
			{

				addMyChannelsPage(html);


			}else if (cmpCGIarg(cmd,"page=","index"))
			{

				addAdminPage(html);

			}else if (cmpCGIarg(cmd,"page=","settings"))
			{

				addSettingsPage(html);

			}else if (cmpCGIarg(cmd,"page=","viewlog"))
			{
				addLogPage(html);

			}else if (cmpCGIarg(cmd,"page=","viewnet"))
			{
				addNetStatsPage(html);

			}else if (cmpCGIarg(cmd,"page=","shutdown"))
			{

				addShutdownPage(html);

			}else if (cmpCGIarg(cmd,"page=","logout"))
			{
				addLogoutPage(html);

			}else if (cmpCGIarg(cmd,"page=","connections"))
			{
				addConnectionsPage(html);

			}else if (cmpCGIarg(cmd,"page=","broadcast"))
			{
				addBroadcastPage(html);

			}else if (cmpCGIarg(cmd,"net=","add"))
			{
				
				GnuID id;
				id.clear();
				while (cmd=nextCGIarg(cmd,curr,arg))
				{
					if (strcmp(curr,"ip")==0)
					{
						Host h;
						h.fromStrIP(arg,DEFAULT_PORT);
						if (servMgr->addOutgoing(h,id,true))
							LOG_NETWORK("Added connection: %s",arg);

					}else if (strcmp(curr,"id")==0)
					{
						id.fromStr(arg);
					}

				}

			}else if (cmpCGIarg(cmd,"cmd=","logout"))
			{
				jumpArg = "/";
				servMgr->cookieList.remove(cookie);

			}else if (cmpCGIarg(cmd,"cmd=","login"))
			{
				GnuID id;
				char idstr[64];
				id.generate();
				id.toStr(idstr);

				cookie.set(idstr,sock->host.ip);
				servMgr->cookieList.add(cookie);	

				http.writeLine(HTTP_SC_FOUND);
				if (servMgr->cookieList.neverExpire)
					http.writeLine("%s id=%s; path=/; expires=\"Mon, 01-Jan-3000 00:00:00 GMT\";",HTTP_HS_SETCOOKIE,idstr);
				else
					http.writeLine("%s id=%s; path=/;",HTTP_HS_SETCOOKIE,idstr);
				http.writeLine("Location: /");
				http.writeLine("");

			}else{

				addAdminPage(html);
			}
		}

	}catch(StreamException &)
	{
		//html.startTagEnd("h1","ERROR - %s",e.msg);
		LOG_ERROR(result);
	}


	if (retHTML)
	{
		if (jumpArg)
		{
			String jmp(jumpArg,String::T_HTML);
			jmp.convertTo(String::T_ASCII);
			html.locateTo(jmp.cstr());
		}
	}


}
// -----------------------------------
static XML::Node *createChannelXML(Channel *c)
{
	XML::Node *n = c->info.createChannelXML();
	n->add(c->createRelayXML(true));
	n->add(c->info.createTrackXML());
//	n->add(c->info.createServentXML());
	return n;
}
// -----------------------------------
static XML::Node *createChannelXML(ChanHitList *chl)
{
	XML::Node *n = chl->info.createChannelXML();
	n->add(chl->createHitsXML());
	n->add(chl->info.createTrackXML());
//	n->add(chl->info.createServentXML());
	return n;
}
// -----------------------------------
void Servent::handshakeXML()
{
	int i;
				

    sock->writeLine(HTTP_SC_OK);
	sock->writeLine("%s %s",HTTP_HS_SERVER,PCX_AGENT);
    sock->writeLine("%s %s",HTTP_HS_CONTENT,MIME_XML);
	sock->writeLine("Connection: close");

    sock->writeLine("");

	XML xml;

	XML::Node *rn = new XML::Node("peercast");
	xml.setRoot(rn);


	rn->add(new XML::Node("servent uptime=\"%d\"",servMgr->getUptime()));

	rn->add(new XML::Node("bandwidth out=\"%d\" in=\"%d\"",servMgr->totalOutput(false),chanMgr->totalInput()));
	rn->add(new XML::Node("connections total=\"%d\" streams=\"%d\"",servMgr->totalConnected(),servMgr->numStreams(false)));

	Channel *clist[ChanMgr::MAX_CHANNELS];
	ChanInfo info;
	int num = chanMgr->findChannels(info,clist,ChanMgr::MAX_CHANNELS);

	XML::Node *an = new XML::Node("channels_relayed total=\"%d\"",num);
	rn->add(an);
	for(i=0; i<num; i++)
		an->add(createChannelXML(clist[i]));


	XML::Node *fn = new XML::Node("channels_found total=\"%d\"",chanMgr->numHitLists());
	rn->add(fn);
	for(i=0; i<ChanMgr::MAX_HITLISTS; i++)
		if (chanMgr->hitlists[i].isUsed())
			fn->add(createChannelXML(&chanMgr->hitlists[i]));

		XML::Node *hc = new XML::Node("host_cache");
	for(i=0; i<ServMgr::MAX_HOSTCACHE; i++)
	{
		ServHost *sh = &servMgr->hostCache[i];
		if (sh->type != ServHost::T_NONE)
		{
			char ipstr[64];
			sh->host.toStr(ipstr);

			if (sh->netid.isSet())
			{
				char netidStr[64];
				sh->netid.toStr(netidStr);
				hc->add(new XML::Node("host ip=\"%s\" type=\"%s\" time=\"%d\" networkID=\"%s\"",ipstr,ServHost::getTypeStr(sh->type),sh->time,netidStr));
			}else{
				hc->add(new XML::Node("host ip=\"%s\" type=\"%s\" time=\"%d\"",ipstr,ServHost::getTypeStr(sh->type),sh->time));
			}

		}
	}
	rn->add(hc);

	xml.write(*sock);

}
// -----------------------------------
void Servent::handshakeHTML(char *cmd)
{

	HTTP http(*sock);

	char tmp[512];

	while (http.nextHeader())
		LOG_DEBUG("HTTP: %s",http.cmdLine);


	char name[128];
	//servMgr->serverHost.toResolvedName(name);
	servMgr->serverHost.toStr(name);
	sprintf(tmp,"PeerCast on %s",name);

	HTML html(tmp,*sock);

	bool showLogin=true;

	if (!strlen(servMgr->password) && sock->host.isLocalhost())
		showLogin = false;


		sock->writeLine(HTTP_SC_FOUND);
		//sock->writeLine("Location: /admin?page=index");
		sock->writeLine("Location: /admin?");
		sock->writeLine("");
		return;

	if (showLogin)
	{
		addLoginPage(html);
	}else{
		sock->writeLine(HTTP_SC_FOUND);
		//sock->writeLine("Location: /admin?page=index");
		sock->writeLine("Location: /admin?");
		sock->writeLine("");
	}


}
// -----------------------------------
void Servent::readICYHeader(HTTP &http, ChanInfo &info, char *pwd)
{
	char *arg = http.getArgStr();
	if (!arg) return;

	if (http.isHeader("x-audiocast-name") || http.isHeader("icy-name") || http.isHeader("ice-name"))
		info.name.set(arg,String::T_ASCII);
	else if (http.isHeader("x-audiocast-url") || http.isHeader("icy-url") || http.isHeader("ice-url"))
		info.url.set(arg,String::T_ASCII);
	else if (http.isHeader("x-audiocast-bitrate") || http.isHeader("ice-bitrate"))
		info.bitrate = atoi(arg);
	else if (http.isHeader("x-audiocast-genre") || http.isHeader("ice-genre"))
		info.genre.set(arg,String::T_ASCII);
	else if (http.isHeader("x-audiocast-description") || http.isHeader("ice-description"))
		info.desc.set(arg,String::T_ASCII);
	else if ((http.isHeader("icy-br")) || http.isHeader("ice-bitrate"))
		info.bitrate = atoi(arg);
	else if (http.isHeader("icy-genre"))
		info.genre.set(arg,String::T_ASCII);
	else if (http.isHeader("Authorization"))
		http.getAuthUserPass(NULL,pwd);
	else if (http.isHeader(PCX_HS_CHANNELID))
		info.id.fromStr(arg);
	else if (http.isHeader("ice-password"))
	{
		if (pwd)
			if (strlen(arg) < 64)
				strcpy(pwd,arg);
	}else if (http.isHeader("content-type"))
	{
		if (stristr(arg,MIME_OGG))
			info.contentType = ChanInfo::T_OGG;
		else if (stristr(arg,MIME_XOGG))
			info.contentType = ChanInfo::T_OGG;
		else if (stristr(arg,MIME_MP3))
			info.contentType = ChanInfo::T_MP3;
		else if (stristr(arg,MIME_XMP3))
			info.contentType = ChanInfo::T_MP3;
		else if (stristr(arg,MIME_NSV))
			info.contentType = ChanInfo::T_NSV;
//		else if (stristr(arg,MIME_XPEERCAST))
//			info.contentType = ChanInfo::T_PEERCAST;
		else if (stristr(arg,MIME_RAW))
			info.contentType = ChanInfo::T_RAW;
	}

}

// -----------------------------------
void Servent::handshakeICY(Channel::SRC_TYPE type, bool isHTTP)
{
	ChanInfo info;

	HTTP http(*sock);

	// default to mp3 for shoutcast DSP (doesn`t send content-type)
	if (type == Channel::SRC_SHOUTCAST)
		info.contentType = ChanInfo::T_MP3;

	while (http.nextHeader())
	{
		LOG_DEBUG("ICY %s",http.cmdLine);
		readICYHeader(http,info,loginPassword);
	}


		
	// check password before anything else, if needed
	if (strcmp(servMgr->password,loginPassword)!=0)
		throw HTTPException(HTTP_SC_UNAUTHORIZED,401);


	// attach channel ID to name, channel ID is also encoded with IP address 
	// to help prevent channel hijacking. 


	Host sh = servMgr->serverHost;

	info.id = chanMgr->broadcastID;
	info.id.encode(&sh,info.name.cstr(),loginMount,info.bitrate);

	LOG_DEBUG("Incoming source: %s : %s",info.name.cstr(),ChanInfo::getTypeStr(info.contentType));


	if (isHTTP)
		sock->writeString("%s\n\n",HTTP_SC_OK);
	else
		sock->writeLine("OK");

	Channel *c = chanMgr->findChannelByID(info.id);
	if (c)
		c->thread.active = false;


	info.comment = chanMgr->broadcastMsg;

	c = chanMgr->createChannel(info,loginMount);
	if (!c)
		throw HTTPException(HTTP_SC_UNAVAILABLE,503);


	c->startICY(sock,type);
}
