// http_handler.cpp,v 1.17 2004/09/24 14:42:42 shuston Exp

// ============================================================================
//
// = LIBRARY
//    apps/JAWS/clients/Caching
//
// = FILENAME
//    http_handler.cpp
//
// = AUTHOR
//    James Hu
//
// ============================================================================

#include "ace/OS_NS_stdio.h"
#include "ace/OS_NS_string.h"
#include "ace/Filecache.h"
#include "http_handler.h"

ACE_RCSID(Caching, http_handler, "http_handler.cpp,v 1.17 2004/09/24 14:42:42 shuston Exp")

HTTP_Handler::HTTP_Handler (void)
{
}

HTTP_Handler::HTTP_Handler (const char * path)
{
  // How long is the request going to be?
  this->request_[0] = '\0';
  this->request_size_ =
    ACE_OS::strlen ("GET ")
    + ACE_OS::strlen (path)
    + ACE_OS::strlen (" HTTP/1.0\r\nAccept: HTTP/1.0\r\n\r\n");

  // Make the request.
  if ((u_int) this->request_size_ < sizeof (this->request_))
    ACE_OS::sprintf (this->request_,
                     "GET %s HTTP/1.0\r\nAccept: HTTP/1.0\r\n\r\n",
                     path);

  // Find the filename.
  const char *last = ACE_OS::strrchr (path, '/');

  if (last == 0)
    last = path;
  else if (last[1] == '\0')
    last = "index.html";
  else
    last = last+1;

  ACE_OS::sprintf (this->filename_, "%s", last);
}

int
HTTP_Handler::open (void *)
{
  // If you want threads, use the activate stuff.
#if 0
  if (this->activate () != 0)
    {
      ACE_ERROR_RETURN ((LM_ERROR, "HTTP_Handler::open, whups!\n"), -1);
    }

  return 0;
#else
  return this->svc ();
#endif /* 0 */
}

int
HTTP_Handler::svc (void)
{
  static char buf[BUFSIZ];
  int count = 0;

  ACE_DEBUG ((LM_DEBUG, "[%t] sending request --\n%s", this->request_));

  this->peer ().send_n (this->request_, this->request_size_);

  // Read in characters until encounter \r\n\r\n
  int done = 0;
  char *contentlength;

  do
    {
      while (((count += this->peer ().recv_n (buf + count, 1)) > 0)
             && ((u_int) count < sizeof (buf)))
        {
          buf[count] = '\0';

          if (count < 2)
            continue;
          done = ACE_OS::strcmp (buf + count - 4, "\n\n") == 0;

          if (done)
            break;

          if (count < 4)
            continue;

          done = ACE_OS::strcmp (buf + count - 4, "\r\n\r\n") == 0;

          if (done)
            break;
        }

      if (!done)
        {
          char *last = ACE_OS::strrchr (buf, '\n');
          last[0] = '\0';

          if ((contentlength = ACE_OS::strstr (buf, "\nContent-length:"))
              || (contentlength = ACE_OS::strstr (buf, "\nContent-Length:")))
            done = 1;
          else
            {
              last[0] = '\n';
              count = ACE_OS::strlen (last);
              ACE_OS::memmove (buf, last, count + 1);
            }
        }
      else
        {
          contentlength = ACE_OS::strstr (buf, "\nContent-length:");

          if (!contentlength)
            contentlength =
              ACE_OS::strstr (buf, "\nContent-Length:");
        }

    }
  while (!done);

  // ASSERT (contentlength != 0)
  if (contentlength
      && (::sscanf (contentlength, "\nContent-%*[lL]ength: %d ",
                    &this->response_size_) == 1))
    {
      ACE_Filecache_Handle afh (ACE_TEXT_CHAR_TO_TCHAR (this->filename_),
                                this->response_size_);

      this->peer ().recv_n (afh.address (), this->response_size_);

      ACE_DEBUG ((LM_DEBUG,
                  "  ``%s'' is now cached.\n",
                  this->filename_));
    }
  else
    {
      // Maybe we should do something more clever here, such as extend
      // ACE_Filecache_Handle to allow the creation of cache objects
      // whose size is unknown?

      // Another possibility is to write the contents out to a file,
      // and then cache it.

      // Perhaps make ACE_Filecache_Handle more savvy, and allow a
      // constructor which accepts a PEER as a parameter.
      ACE_DEBUG ((LM_DEBUG,
                  "HTTP_Handler, no content-length header!\n"));
    }

  return 0;
}

const char *
HTTP_Handler::filename (void) const
{
  return this->filename_;
}

int
HTTP_Connector::connect (const char * url)
{
  char host[BUFSIZ];
  u_short port;
  char path[BUFSIZ];

  if (this->parseurl (url, host, &port, path) == -1)
    {
      ACE_DEBUG ((LM_DEBUG,
                  "HTTP_Connector, error parsing url: %s\n",
                  url));
      return -1;
    }

  HTTP_Handler hh (path);
  HTTP_Handler *hhptr = &hh;

  // First check the cache.
  if (ACE_Filecache::instance ()->find (ACE_TEXT_CHAR_TO_TCHAR (hh.filename ())) == 0)
    {
      ACE_DEBUG ((LM_DEBUG, "  ``%s'' is already cached.\n",
                  hh.filename ()));
      return 0;
    }

  return this->connector_.connect (hhptr, ACE_INET_Addr (port, host));
}

#define DEFAULT_SERVER_PORT 80

// extract the main components of a URL
int
HTTP_Connector::parseurl (const char *url,
                          char *host,
                          u_short *port,
                          char *path)
{
  int status = 0;

  // hackish, but useful
  if (3 != ::sscanf (url, "http://%[^:/]:%hu%s", host, port, path))
    {
      if (2 != ::sscanf (url, "http://%[^:/]:%hu", host, port))
        {
          if (2 != ::sscanf (url, "http://%[^:/]%s", host, path))
            {
              if (1 != ::sscanf (url, "http://%[^:/]", host))
                status = -1;
              else
                {
                  *port = DEFAULT_SERVER_PORT;
                  ACE_OS::strcpy (path, "/");
                }
            }
          else
            *port = DEFAULT_SERVER_PORT;
        }
      else ACE_OS::strcpy (path, "/");
    }

  // 0 => success
  // -1 => error
  return status;
}

#if defined (ACE_HAS_EXPLICIT_TEMPLATE_INSTANTIATION)
template class ACE_Connector_Base<HTTP_Handler>;
template class ACE_Connector<HTTP_Handler, ACE_SOCK_CONNECTOR>;
template class ACE_NonBlocking_Connect_Handler<HTTP_Handler>;
template class ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>;
#elif defined (ACE_HAS_TEMPLATE_INSTANTIATION_PRAGMA)
#pragma instantiate ACE_Connector_Base<HTTP_Handler>
#pragma instantiate ACE_Connector<HTTP_Handler, ACE_SOCK_CONNECTOR>
#pragma instantiate ACE_NonBlocking_Connect_Handler<HTTP_Handler>
#pragma instantiate ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>
#endif /* ACE_HAS_EXPLICIT_TEMPLATE_INSTANTIATION */
