/*
	CVSNT Generic API
    Copyright (C) 2004 Tony Hoyle and March-Hare Software Ltd

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/* Win32 specific */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h>
#define SECURITY_WIN32
#include <security.h>

#include <config.h>
#include "../lib/api_system.h"
#include "../cvs_string.h"
#include "../HttpSocket.h"
#include "../SSPIHandler.h"
#include "../../version.h"

/*
** Translation Table as described in RFC1113
*/
static const unsigned char cb64[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

#include <ctype.h>

// in autoproxy
bool GetProxyForHost(const char *url, const char *hostname, cvs::string& proxy, cvs::string& proxy_port);

CHttpSocket::CHttpSocket()
{
	m_bProxy = false;
	m_nAuthStage=0;
}

CHttpSocket::~CHttpSocket()
{
}

bool CHttpSocket::create(const char *urlHost, bool auto_proxy /*= true*/, const char *proxy_address /*= NULL*/, const char *proxy_port /*= NULL*/, const char *username /*= NULL*/, const char *password /*= NULL*/)
{
	if(!_setUrl(urlHost))
		return false;

	m_bAutoProxy = auto_proxy;
	m_proxyUser = username?username:"";
	m_proxyPassword = password?password:"";

	if(proxy_address && proxy_port)
	{
		m_proxyName = proxy_address;
		m_proxyPort = proxy_port;
		m_bProxy = true;
	}

	return _create();
}

bool CHttpSocket::_setUrl(const char *urlHost)
{
	if(!urlHost || strncmp(urlHost,"http://",7))
		return false;

	cvs::string url_string = urlHost;
	char *address,*port,*p;

	address=(char*)(url_string.data()+7);
	p=strpbrk(address,":/");
	if(p && *p==':')
	{
		*p='\0';
		port=p+1;
		p=strchr(port,'/');
	}
	else
		port="80";
	if(p)
		*p='\0';
	// We don't process the URL location here

	m_urlHost = urlHost;
	m_port = port;
	m_address = address;
	return true;
}

bool CHttpSocket::_create()
{
	m_nAuthStage=0;
	if(m_bAutoProxy)
	{
		m_bProxy = GetProxyForHost(m_urlHost.c_str(),m_address.c_str(),m_proxyName,m_proxyPort);
	}
	if(m_bProxy)
		return CSocketIO::create(m_proxyName.c_str(),m_proxyPort.c_str(),false);
	else
		return CSocketIO::create(m_address.c_str(),m_port.c_str(),false);
}

bool CHttpSocket::request(const char *command, const char *location, const char *content /* = NULL */, size_t content_length /* = 0 */)
{
	CSSPIHandler sspi;
	cvs::string loc;

	if(!command || !*command || !location || location[0]!='/')
		return false;

	if(!m_activeSocket && !CSocketIO::connect())
		return false;

	if(!content && content_length)
		content_length = 0;

	bool bAgain = false;

	m_requestHeaderList["Proxy-Connection"].clear();
	if(m_bProxy)
		m_requestHeaderList["Proxy-Connection"].push_back("Keep-Alive");

	do
	{
		bAgain = false;
		if(!_request(command,location,content,content_length))
			return false;
		switch(m_responseCode)
		{
		case 302: // Moved
//			_asm int 3
			loc = m_responseHeaderList["Location"][0].c_str();
			location = loc.c_str();
			if(!_setUrl(location))
				return false;
// Do proxies really change across redirects??  I suspect this is a rare case...
//			CSocketIO::close();
//			if(!_create();
//				return false;
//			if(!CSocketIO::connect())
//				return false;
			location=strchr(location+7,'/');
			if(!location) location="/";
			bAgain = true;
			break;
		case 407: // Proxy authentication required
			{
				char *proxy_auth,*p;
				char *auth_type=NULL,*realm;
				cvs::string negotiate,neg_enc;
				std::vector<cvs::string>& auth = m_responseHeaderList["Proxy-Authenticate"];

				if(m_nAuthStage==-1)
					break; // Failed to auth
				if(!m_nAuthStage)
				{
					// NTLM doesn't work - there's some magic voodoo you have to do to use it 
					// that isn't in any of the documentation, and it's impossible to find by
					// experimentation
					//
					// The code is all there and is correct according to the available documentation...
					//
					bool bHasNtlm = sspi.init("NTLM");

					for(size_t n=0; n<auth.size();n++)
					{
						proxy_auth = (char*)auth[n].c_str();
						p=strchr(proxy_auth,' ');
						if(p)
							*(p++)='\0';
						if((!auth_type && !strcmp(proxy_auth,"Basic")) || (bHasNtlm && !strcmp(proxy_auth,"NTLM")))
						{
							auth_type=proxy_auth;
							realm=p;
						}
					}
					if(!auth_type)
						break; // Auth not understood
					if(!strcmp(auth_type,"Basic"))
					{
						cvs::string auth_plain, auth_enc;
						cvs::sprintf(auth_plain,64,"%s:%s",m_proxyUser.c_str(),m_proxyPassword.c_str());
						base64Enc((const unsigned char *)auth_plain.c_str(),auth_plain.length(),auth_enc);
						cvs::sprintf(auth_plain,64,"Basic %s",auth_enc.c_str());
						m_requestHeaderList["Proxy-Authorization"].clear();
						m_requestHeaderList["Proxy-Authorization"].push_back(auth_plain);
						bAgain=true;
						break;
					}
					if(!strcmp(auth_type,"NTLM"))
					{
						CSocketIO::close();
						if(!_create())
							return false;
						if(!CSocketIO::connect())
							return false;
						m_nAuthStage = 1;

						// You must restart the connection to begin authentication... not sure why
						//
						// HTTP clients don't support security (confidentiality, sequence detect, etc. )
						// so we don't use it.
						if(!sspi.ClientStart(false,bAgain,m_proxyUser.c_str(),m_proxyPassword.c_str()))
							return false;

						size_t len;
						const unsigned char *ob = sspi.getOutputBuffer(len);
						base64Enc(ob,len,neg_enc);
						cvs::sprintf(negotiate,256,"NTLM %s",neg_enc.c_str());
						m_requestHeaderList["Proxy-Authorization"].clear();
						m_requestHeaderList["Proxy-Authorization"].push_back(negotiate);
						break;
					}
					else
					{
						// Unknown auth type
						break;
					}
				}
				else
				{
					// NTLM stage 1
					for(size_t n=0; n<auth.size();n++)
					{
						proxy_auth = (char*)auth[n].c_str();
						p=strchr(proxy_auth,' ');
						if(p)
							*(p++)='\0';
						if(!strcmp(proxy_auth,"NTLM"))
						{
							auth_type=proxy_auth;
							realm=p;
						}
					}
					if(!auth_type || !realm)
						return false;
					cvs::string resp;
					base64Dec((const unsigned char *)realm, strlen(realm), resp);
					if(!sspi.ClientStep(bAgain, resp.data(), resp.size()))
						return false;

					size_t len;
					const unsigned char *ob = sspi.getOutputBuffer(len);
					base64Enc(ob,len,neg_enc);
					cvs::sprintf(negotiate,256,"NTLM %s",neg_enc.c_str());
					m_requestHeaderList["Proxy-Authorization"].clear();
					m_requestHeaderList["Proxy-Authorization"].push_back(negotiate);
					if(!bAgain)
					{
						m_nAuthStage=-1;
						bAgain=true; // We must keep going, to send the final packet
					}
					break;
				}
				break;
			}
			break;
		default:
			break;
		}
	} while(bAgain);

	return true;
}

bool CHttpSocket::_request(const char *command, const char *location, const char *content, size_t content_length)
{
	cvs::string line;

	if(m_bProxy)
	{
		if(CSocketIO::printf("%s http://%s%s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n",command,m_address.c_str(),location,m_address.c_str(),content_length)<0)
			return false;
	}
	else
	{
		if(CSocketIO::printf("%s %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n",command,location,m_address.c_str(),content_length)<0)
			return false;
	}
	if(m_requestHeaderList.find("User-Agent")==m_requestHeaderList.end())
		m_requestHeaderList["User-Agent"].push_back("Cvsapi "CVSNT_PRODUCTVERSION_SHORT" (Win32)");
	for(headerList_t::const_iterator i = m_requestHeaderList.begin(); i!=m_requestHeaderList.end(); i++)
	{
		if(!strcmp(i->first.c_str(),"Content-Length") || !strcmp(i->first.c_str(),"Host"))
			continue;
		for(size_t j=0; j<i->second.size(); j++)
		{
			if(CSocketIO::printf("%s: %s\r\n",i->first.c_str(),i->second[j].c_str())<0)
				return false;
		}
	}
	CSocketIO::printf("\r\n");
	if(content_length)
		if(CSocketIO::send(content,(int)content_length)<0)
			return false;
	CSocketIO::getline(line);
	char *p,*l=(char*)line.c_str();
	p=strchr(l,' ');
	if(p)
		*p='\0';
	m_responseProtocol = l;
	if(p)
	{
		l=++p;
		p=strchr(p,' ');
		if(p)
			*p='\0';
		m_responseCode = atoi(l);
	}
	if(p)
		m_responseString = p+1;
	m_responseHeaderList.clear();
	while(CSocketIO::getline(line))
	{
		if(!line.length())
			break;
		l=(char*)line.c_str();
		p=strchr(l,':');
		if(p)
		{
			*(p++)='\0';
			while(*p && isspace((unsigned char)*p))
				p++;
			m_responseHeaderList[l].push_back(p);
		}
		else
			m_responseHeaderList[l].push_back("");
	}
	if(m_responseHeaderList.find("Content-Length")!=m_responseHeaderList.end())
	{
		size_t len = atoi(m_responseHeaderList["Content-Length"][0].c_str());
		m_content.resize(len);
		if(len)
		{
			if(CSocketIO::recv((char*)m_content.data(),(int)len)<0)
				return false;
		}
	}
	else
		m_content="";
	return true;
}

/************************************************************
 *    uuencode/decode functions
 ************************************************************/

//
//  Taken from NCSA HTTP and wwwlib.
//
//  NOTE: These conform to RFC1113, which is slightly different then the Unix
//        uuencode and uudecode!
//

static const int pr2six[256]={
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,62,64,64,64,63,
    52,53,54,55,56,57,58,59,60,61,64,64,64,64,64,64,64,0,1,2,3,4,5,6,7,8,9,
    10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,64,64,64,64,64,64,26,27,
    28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,
    64,64,64,64,64,64,64,64,64,64,64,64,64
};

static const char six2pr[64] = {
    'A','B','C','D','E','F','G','H','I','J','K','L','M',
    'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
    'a','b','c','d','e','f','g','h','i','j','k','l','m',
    'n','o','p','q','r','s','t','u','v','w','x','y','z',
    '0','1','2','3','4','5','6','7','8','9','+','/'
};

bool CHttpSocket::base64Enc(const unsigned char *from, size_t len, cvs::string& to)
{
   unsigned char *outptr;
   unsigned int i;

   //
   //  Resize the buffer to 133% of the incoming data
   //

   to.resize(len + ((len + 3) / 3) + 4);

   outptr = (unsigned char *)to.data();

   for (i=0; i<len; i += 3) {
      *(outptr++) = six2pr[*from >> 2];            /* c1 */
      *(outptr++) = six2pr[((*from << 4) & 060) | ((from[1] >> 4) & 017)]; /*c2*/
      *(outptr++) = six2pr[((from[1] << 2) & 074) | ((from[2] >> 6) & 03)];/*c3*/
      *(outptr++) = six2pr[from[2] & 077];         /* c4 */

      from += 3;
   }

   /* If len was not a multiple of 3, then we have encoded too
    * many characters.  Adjust appropriately.
    */
   if(i == len+1) {
      /* There were only 2 bytes in that last group */
      outptr[-1] = '=';
   } else if(i == len+2) {
      /* There was only 1 byte in that last group */
      outptr[-1] = '=';
      outptr[-2] = '=';
   }

   *outptr = '\0';

   return true;
}

bool CHttpSocket::base64Dec(const unsigned char *from, size_t len, cvs::string& to)
{
    int nbytesdecoded;
    const unsigned char *bufin = from;
	unsigned char *bufout;
    int nprbytes;

    /* Figure out how many characters are in the input buffer.
     * If this would decode into more bytes than would fit into
     * the output buffer, adjust the number of input bytes downwards.
     */
    while(pr2six[*(bufin++)] <= 63)
		;
    nprbytes = (int)(bufin - from - 1);
    nbytesdecoded = ((nprbytes+3)/4) * 3;

	to.resize(nbytesdecoded+4);

    bufout = (unsigned char *)to.data();

    bufin = from;

    while (nprbytes > 0) {
        *(bufout++) =
            (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
        *(bufout++) =
            (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
        *(bufout++) =
            (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
        bufin += 4;
        nprbytes -= 4;
    }

    if(nprbytes & 03) {
        if(pr2six[bufin[-2]] > 63)
            nbytesdecoded -= 2;
        else
            nbytesdecoded -= 1;
    }

	to.resize(nbytesdecoded);

    return true;
}




syntax highlighted by Code2HTML, v. 0.9.1