/*
 *  Copyright (C) 2003 Thiago Macieira <thiago.macieira@kdemail.net>
 *
 *
 *  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.
 */

#include <config.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <tqsocketnotifier.h>
#include <tqcstring.h>

#include "kresolver.h"
#include "tdesocketaddress.h"
#include "tdesocketdevice.h"
#include "khttpproxysocketdevice.h"

using namespace KNetwork;

KResolverEntry KHttpProxySocketDevice::defaultProxy;

class KNetwork::KHttpProxySocketDevicePrivate
{
public:
  KResolverEntry proxy;
  TQCString request;
  TQCString reply;
  TDESocketAddress peer;

  KHttpProxySocketDevicePrivate()
    : proxy(KHttpProxySocketDevice::defaultProxy)
  { }
};

KHttpProxySocketDevice::KHttpProxySocketDevice(const TDESocketBase* parent)
  : TDESocketDevice(parent), d(new KHttpProxySocketDevicePrivate)
{
}

KHttpProxySocketDevice::KHttpProxySocketDevice(const KResolverEntry& proxy)
  : d(new KHttpProxySocketDevicePrivate)
{
  d->proxy = proxy;
}

KHttpProxySocketDevice::~KHttpProxySocketDevice()
{
  // nothing special to be done during closing
  // TDESocketDevice::~TDESocketDevice closes the socket

  delete d;
}

int KHttpProxySocketDevice::capabilities() const
{
  return CanConnectString | CanNotBind | CanNotListen | CanNotUseDatagrams;
}

const KResolverEntry&
KHttpProxySocketDevice::proxyServer() const
{
  return d->proxy;
}

void KHttpProxySocketDevice::setProxyServer(const KResolverEntry& proxy)
{
  d->proxy = proxy;
}

void KHttpProxySocketDevice::close()
{
  d->reply = d->request = TQCString();
  d->peer = TDESocketAddress();
  TDESocketDevice::close();
}

TDESocketAddress KHttpProxySocketDevice::peerAddress() const
{
  if (isOpen())
    return d->peer;
  return TDESocketAddress();
}

TDESocketAddress KHttpProxySocketDevice::externalAddress() const
{
  return TDESocketAddress();
}

bool KHttpProxySocketDevice::connect(const KResolverEntry& address)
{
  if (d->proxy.family() == AF_UNSPEC)
    // no proxy server set !
    return TDESocketDevice::connect(address);

  if (isOpen())
    {
      // socket is already open
      resetError();
      return true;
    }

  if (m_sockfd == -1)
    // socket isn't created yet
    return connect(address.address().nodeName(), 
		   address.address().serviceName());

  d->peer = address.address();
  return parseServerReply();
}

bool KHttpProxySocketDevice::connect(const TQString& node, const TQString& service)
{
  // same safety checks as above
  if (m_sockfd == -1 && (d->proxy.family() == AF_UNSPEC ||
			 node.isEmpty() || service.isEmpty()))
    {
      // no proxy server set !
      setError(IO_ConnectError, NotSupported);
      return false;
    }

  if (isOpen())
    {
      // socket is already open
      return true;
    }

  if (m_sockfd == -1)
    {
      // must create the socket
      if (!TDESocketDevice::connect(d->proxy))
	return false;		// also unable to contact proxy server
      setState(0);		// unset open flag

      // prepare the request
      TQString request = TQString::fromLatin1("CONNECT %1:%2 HTTP/1.1\r\n"
					    "Cache-Control: no-cache\r\n"
					    "Host: \r\n"
					    "\r\n");
      TQString node2 = node;
      if (node.contains(':'))
	node2 = '[' + node + ']';

      d->request = TQString(request.arg(node2).arg(service)).latin1();
    }

  return parseServerReply();
}

bool KHttpProxySocketDevice::parseServerReply()
{
  // make sure we're connected
  if (!TDESocketDevice::connect(d->proxy)) {
    if (error() == InProgress) {
      return true;
    }
    else if (error() != NoError) {
      return false;
    }
  }

  if (!d->request.isEmpty())
    {
      // send request
      TQ_LONG written = writeBlock(d->request, d->request.length());
      if (written < 0)
	{
	  tqDebug("KHttpProxySocketDevice: would block writing request!");
	  if (error() == WouldBlock)
	    setError(IO_ConnectError, InProgress);
	  return error() == WouldBlock; // error
	}
      tqDebug("KHttpProxySocketDevice: request written");

      d->request.remove(0, written);

      if (!d->request.isEmpty())
	{
	  setError(IO_ConnectError, InProgress);
	  return true;		// still in progress
	}
    }

  // request header is sent
  // must parse reply, but must also be careful not to read too much
  // from the buffer

  int index;
  if (!blocking())
    {
      TQ_LONG avail = bytesAvailable();
      tqDebug("KHttpProxySocketDevice: %ld bytes available", avail);
      setState(0);
      if (avail == 0)
	{
	  setError(IO_ConnectError, InProgress);
	  return true;
	}
      else if (avail < 0)
	return false;		// error!

      TQByteArray buf(avail);
      if (peekBlock(buf.data(), avail) < 0)
	return false;		// error!

      TQCString fullHeaders = d->reply + buf.data();
      // search for the end of the headers
      index = fullHeaders.find("\r\n\r\n");
      if (index == -1)
	{
	  // no, headers not yet finished...
	  // consume data from socket
	  readBlock(buf.data(), avail);
	  d->reply += buf.data();
	  setError(IO_ConnectError, InProgress);
	  return true;
	}

      // headers are finished
      index -= d->reply.length();
      d->reply += fullHeaders.mid(d->reply.length(), index + 4);

      // consume from socket
      readBlock(buf.data(), index + 4);
    }
  else
    {
      int state = 0;
      if (d->reply.right(3) == "\r\n\r")
	state = 3;
      else if (d->reply.right(2) == "\r\n")
	state = 2;
      else if (d->reply.right(1) == "\r")
	state = 1;
      while (state != 4)
	{
	  char c = getch();
	  d->reply += c;

	  if ((state == 3 && c == '\n') ||
	      (state == 1 && c == '\n') ||
	      c == '\r')
	    ++state;
	  else
	    state = 0;
	}
    }	    

  // now really parse the reply
  tqDebug("KHttpProxySocketDevice: get reply: %s\n",
	 d->reply.left(d->reply.find('\r')).data());
  if (d->reply.left(7) != "HTTP/1." ||
      (index = d->reply.find(' ')) == -1 ||
      d->reply[index + 1] != '2')
    {
      setError(IO_ConnectError, NetFailure);
      return false;
    }

  // we've got it
  resetError();
  setState(IO_Open);
  return true;
}