#include "ace/Message_Block.h"
#include "HTTP_Request.h"
#include "HTTP_Helpers.h"
#include "HTTP_Config.h"
#include "ace/OS_NS_string.h"
#include "ace/OS_NS_pwd.h"
#include "ace/Log_Msg.h"

const char *const
HTTP_Request::static_header_strings_[HTTP_Request::NUM_HEADER_STRINGS] =
{
  "Date",
  "Pragma",
  "Authorization",
  "From",
  "If-Modified-Since",
  "Referrer",
  "User-Agent",
  "Allow",
  "Content-Encoding",
  "Content-Length",
  "Content-Type",
  "Expires",
  "Last-Modified"
};

const char *const
HTTP_Request::static_method_strings_[HTTP_Request::NUM_METHOD_STRINGS] =
{
  "GET",
  "HEAD",
  "POST",
  "PUT"
};

// For reasons of efficiency, this class expects buffer to be
// null-terminated, and buflen does NOT include the \0.

HTTP_Request::HTTP_Request (void)
  : got_request_line_ (0),
    method_ (0),
    uri_ (0),
    version_ (0),
    path_ (0),
    cgi_ (0),
    cgi_env_ (0),
    cgi_args_ (0),
    query_string_ (0),
    path_info_ (0),
    header_strings_ (HTTP_Request::static_header_strings_),
    method_strings_ (HTTP_Request::static_method_strings_)
{

  for (size_t i = 0;
       i < HTTP_Request::NUM_HEADER_STRINGS;
       i++)
    this->headers_.recognize (this->header_strings_[i]);
}

HTTP_Request::~HTTP_Request (void)
{
  ACE_OS::free (this->method_);
  ACE_OS::free (this->uri_);
  ACE_OS::free (this->version_);
  ACE_OS::free (this->path_);
  ACE_OS::free (this->query_string_);
  ACE_OS::free (this->path_info_);

  delete [] this->cgi_env_;
}

int
HTTP_Request::parse_request (ACE_Message_Block &mb)
{
  mb.wr_ptr ()[0] = '\0';

  // Note that RFC 822 does not mention the maximum length of a header
  // line.  So in theory, there is no maximum length.

  // In Apache, they assume that each header line should not exceed
  // 8K.

  int result = this->headers_.complete_header_line (mb.rd_ptr ());

  if (result != 0)
    {
      if (!this->got_request_line ())
        {
          this->parse_request_line (mb.rd_ptr ());
          while (this->headers_.complete_header_line (mb.rd_ptr ()) > 0)
            this->headers_.parse_header_line (mb.rd_ptr ());
        }
      else if (result > 0)
        do
          this->headers_.parse_header_line (mb.rd_ptr ());
        while (this->headers_.complete_header_line (mb.rd_ptr ()) > 0);
    }

  mb.wr_ptr (ACE_OS::strlen(mb.rd_ptr ()) - mb.length ());

  if (this->headers_.end_of_headers ()
      || (this->got_request_line () && this->version () == 0))
    return this->init (mb.rd_ptr (), mb.length ());
  else
    return 0;
}

void
HTTP_Request::parse_request_line (char *const request_line)
{
  char *ptr = request_line;
  char *buf = request_line;
  int offset = 1;

  this->status_ = HTTP_Status_Code::STATUS_OK;

  ptr = ACE_OS::strchr (request_line, '\n');

  if (ptr > request_line && ptr[-1] == '\r')
    ptr--, offset++;

  if (ptr == request_line)
    {
      this->status_ = HTTP_Status_Code::STATUS_BAD_REQUEST;
      return;
    }

  *ptr = '\0';
  ptr += offset;

  char *lasts = 0; // for strtok_r

  // Get the request type.
  this->got_request_line_ = 1;

  if (this->method (ACE_OS::strtok_r (buf, " \t", &lasts))
      && this->uri (ACE_OS::strtok_r (0, " \t", &lasts)))
    {
      this->type (this->method ());

      if (this->version (ACE_OS::strtok_r (0, " \t", &lasts)) == 0
          && this->type () != HTTP_Request::GET)
        this->status_ = HTTP_Status_Code::STATUS_NOT_IMPLEMENTED;

      if (this->path (this->uri ()) == 0)
        this->status_ = HTTP_Status_Code::STATUS_NOT_FOUND;
    }

  ACE_DEBUG ((LM_DEBUG, " (%t) request %s %s %s parsed\n",
              (this->method () ? this->method () : "-"),
              (this->uri () ? this->uri () : "="),
              (this->version () ? this->version () : "HTTP/0.9")));

  ACE_OS::memmove (buf, ptr, ACE_OS::strlen (ptr)+1);
}

int
HTTP_Request::init (char *const buffer,
                    int buflen)
{
  // Initialize these every time.
  content_length_ = -1;

  // Extract the data pointer.
  data_ = buffer;
  datalen_ = 0;

  // Set the datalen
  if (data_ != 0)
    datalen_ = buflen;
  else
    datalen_ = 0;

  ACE_DEBUG ((LM_DEBUG, " (%t) init has initialized\n"));

  return 1;
}

const char *
HTTP_Request::method (void) const
{
  return this->method_;
}

const char *
HTTP_Request::uri (void) const
{
  return this->uri_;
}

const char *
HTTP_Request::version (void) const
{
  return this->version_;
}

const char *
HTTP_Request::path (void) const
{
  return this->path_;
}

int
HTTP_Request::cgi (void) const
{
  return this->cgi_;
}

const char **
HTTP_Request::cgi_env (void) const
{
  return (const char **)this->cgi_env_;
}

const char *
HTTP_Request::cgi_args (void) const
{
  return this->cgi_args_;
}

const char *
HTTP_Request::query_string (void) const
{
  return this->query_string_;
}

const char *
HTTP_Request::path_info (void) const
{
  return this->path_info_;
}

int
HTTP_Request::got_request_line (void) const
{
  return this->got_request_line_;
}

int
HTTP_Request::type (void) const
{
  return type_;
}

const Headers &
HTTP_Request::headers (void) const
{
  return this->headers_;
}

const char *
HTTP_Request::header_strings (int index) const
{
  const char *hs = 0;

  if (0 <= index && index < NUM_HEADER_STRINGS)
    hs = this->header_strings_[index];

  return hs;
}

const char *
HTTP_Request::header_values (int index) const
{
  const char *hs = 0;
  const char *hv = 0;

  if (0 <= index && index < NUM_HEADER_STRINGS)
    {
      hs = this->header_strings_[index];
      hv = this->headers_[hs].value ();
    }

  return hv;
}

char *
HTTP_Request::data (void)
{
  return data_;
}

int
HTTP_Request::data_length (void)
{
  return datalen_;
}

int
HTTP_Request::content_length (void)
{
  if (this->content_length_ == -1)
    {
      const char * clv = this->headers_["Content-length"].value ();
      this->content_length_ = (clv ? ACE_OS::atoi (clv) : 0);
    }

  return this->content_length_;
}

int
HTTP_Request::status (void)
{
  return this->status_;
}

const char *
HTTP_Request::status_string (void)
{
  return HTTP_Status_Code::instance ()[this->status_];
}

void
HTTP_Request::dump (void)
{
  ACE_DEBUG ((LM_DEBUG, "%s command.\n"
              "filename is %s,"
              " length of the file is %d,"
              " data string is %s,"
              " datalen is %d,"
              " status is %d, which is %s\n\n",
              this->method () ? this->method () : "EMPTY",
              this->uri () ? this->uri () : "EMPTY",
              this->content_length (),
              this->data () ? this->data () : "EMPTY",
              this->data_length (),
              this->status (),
              this->status_string ()));
}

const char *
HTTP_Request::method (const char *method_string)
{
  if (this->method_)
    ACE_OS::free (this->method_);

  if (method_string == 0)
    {
      this->status_ = HTTP_Status_Code::STATUS_BAD_REQUEST;
      this->method_ = 0;
    }
  else
    this->method_ = ACE_OS::strdup (method_string);

  return this->method_;
}

const char *
HTTP_Request::uri (char *uri_string)
{
  if (this->uri_)
    ACE_OS::free (this->uri_);

  if (uri_string == 0)
    {
      this->status_ = HTTP_Status_Code::STATUS_BAD_REQUEST;
      this->uri_ = 0;
    }
  else
    {
      this->uri_ =  ACE_OS::strdup (uri_string);
      this->cgi (this->uri_);
      HTTP_Helper::HTTP_decode_string (this->uri_);
    }

  return this->uri_;
}

const char *
HTTP_Request::version (const char *version_string)
{
  if (this->version_)
    ACE_OS::free (this->version_);

  if (version_string)
    this->version_ = ACE_OS::strdup (version_string);
  else
    this->version_ = 0;

  return this->version_;
}

int
HTTP_Request::type (const char *type_string)
{
  this->type_ = HTTP_Request::NO_TYPE;

  if (type_string == 0)
    return this->type_;

  for (size_t i = 0;
       i < HTTP_Request::NUM_METHOD_STRINGS;
       i++)

    if (ACE_OS::strcmp (type_string, this->method_strings_[i]) == 0)
      {
        this->type_ = i;
        break;
      }

  if (this->type_ == HTTP_Request::NO_TYPE)
    this->status_ = HTTP_Status_Code::STATUS_NOT_IMPLEMENTED;

  return this->type_;
}

int
HTTP_Request::cgi (char *uri_string)
{
  this->cgi_ = 0;
  this->cgi_env_ = 0;
  this->cgi_args_ = 0;

  ACE_DEBUG ((LM_DEBUG, " (%t) HTTP_Request::cgi (%s)\n", uri_string));

  if (uri_string == 0 || ACE_OS::strlen (uri_string) == 0)
    return 0;

  // There are 2 cases where a file could be a CGI script
  //
  // (1) the file has a CGI extension.
  // (2) the file resides in a CGI bin directory.

  char *extra_path_info = 0;
  if (this->cgi_in_path (uri_string, extra_path_info)
      || this->cgi_in_extension (uri_string, extra_path_info))
    {
      cgi_args_and_env (extra_path_info);

      if (extra_path_info)
        {
          this->path_info_ = ACE_OS::strdup (extra_path_info);
          HTTP_Helper::HTTP_decode_string (this->path_info_);
          *extra_path_info = '\0';
        }
    }

  return this->cgi_;
}

int
HTTP_Request::cgi_in_path (char *uri_string, char *&extra_path_info)
{
  char *cgi_path;

  ACE_DEBUG ((LM_DEBUG, " (%t) HTTP_Request::cgi_in_path (%s)\n",
              uri_string));

  if (HTTP_Config::instance ()->cgi_path ())
    cgi_path = ACE_OS::strdup (HTTP_Config::instance ()->cgi_path ());
  else
    cgi_path = ACE_OS::strdup ("");

  // error checking considered helpful!
  if (cgi_path == 0)
    return 0;

  char *lasts = 0;
  char *cgi_path_next = ACE_OS::strtok_r (cgi_path, ":", &lasts);

  if (cgi_path_next)
    do
      {
        int len = ACE_OS::strlen (cgi_path_next);

        // match path to cgi path
        int in_cgi_path = 0;

        if (*cgi_path_next == '/')
          {
            // cgi path next points to an ``absolute'' path
            extra_path_info = uri_string;
            in_cgi_path =
              (ACE_OS::strncmp (extra_path_info, cgi_path_next, len) == 0);
          }
        else
          {
            // cgi path next points to a ``relative'' path
            extra_path_info = ACE_OS::strstr (uri_string, cgi_path_next);
            in_cgi_path = (extra_path_info != 0);
          }

        if (in_cgi_path)
          {
            if (extra_path_info[len] == '/')
              {
                this->cgi_ = 1;
                extra_path_info += len;

                // move past the executable name
                do
                  extra_path_info++;
                while (*extra_path_info != '/'
                       && *extra_path_info != '?'
                       && *extra_path_info != '\0');

                if (*extra_path_info == '\0')
                  extra_path_info = 0;

                break;
              }
          }
        extra_path_info = 0;

        cgi_path_next = ACE_OS::strtok_r (0, ":", &lasts);
      }
    while (cgi_path_next);

  ACE_OS::free (cgi_path);

  return this->cgi_;
}

int
HTTP_Request::cgi_in_extension (char *uri_string, char *&extra_path_info)
{
  extra_path_info = ACE_OS::strstr (uri_string, ".cgi");

  ACE_DEBUG ((LM_DEBUG, " (%t) HTTP_Request::cgi_in_extension (%s)\n",
              uri_string));

  while (extra_path_info != 0)
    {
      extra_path_info += 4;
      // skip past ``.cgi''

      switch (*extra_path_info)
        {
        case '\0':
          extra_path_info = 0;
          break;
        case '/':
        case '?':
          break;
        default:
          extra_path_info = ACE_OS::strstr (extra_path_info, ".cgi");
          continue;
        }
      this->cgi_ = 1;
      break;
    }

  return this->cgi_;
}

void
HTTP_Request::cgi_args_and_env (char *&extra_path_info)
{
  char *cgi_question = 0;

  if (extra_path_info)
    cgi_question = ACE_OS::strchr (extra_path_info, '?');

  if (extra_path_info == cgi_question)
    extra_path_info = 0;

  if (cgi_question)
    {
      *cgi_question++ = '\0';

      if (*cgi_question != '\0')
        {
          // We need the ``original'' QUERY_STRING for the
          // environment.  We will substitute '+'s for spaces in the
          // other copy.

          this->query_string_ = ACE_OS::strdup (cgi_question);

          char *ptr = cgi_question;
          int count = 0;
          do
            if (*ptr == '+')
              *ptr = ' ';
            else if (*ptr == '&' || *ptr == '=')
              count++;
          while (*++ptr);

          count++;

          if (ACE_OS::strchr (cgi_question, '='))
            {
              ACE_NEW (this->cgi_env_, char *[count+1]);

              int i = 0;
              ptr = cgi_question;
              do
                {
                  this->cgi_env_ [i++] = ptr;

                  while (*ptr++)
                      if (*ptr == '&' || *ptr == '=')
                        *ptr = '\0';

                  HTTP_Helper::HTTP_decode_string (this->cgi_env_[i-1]);
                }
              while (i < count);

              this->cgi_env_[count] = 0;
            }
          else
            {
              this->cgi_args_ = cgi_question;
              HTTP_Helper::HTTP_decode_string (cgi_question);
            }
        }
    }
}

const char *
HTTP_Request::path (const char *uri_string)
{
  char const *file_name = uri_string;
  char buf[MAXPATHLEN + 1];
  buf[0] = '\0';

  if (file_name == 0) return 0;

  if (*file_name == '/')
    {
      file_name++;
      if (*file_name == '~')
        {
          char *ptr = buf;

          while (*++file_name && *file_name != '/')
            *ptr++ = *file_name;

          *ptr = '\0';

          if (ptr == buf)
            ACE_OS::strcpy (buf, ACE_OS::getenv ("HOME"));
          else
            {
#if !defined (ACE_WIN32) && !defined (VXWORKS)
              char pw_buf[BUFSIZ];
              struct passwd pw_struct;
              struct passwd *pw_struct_ptr;
              if (ACE_OS::getpwnam_r (buf, &pw_struct, pw_buf,
                                      sizeof (pw_buf), &pw_struct_ptr) == 0)
                return 0;
              ACE_OS::strcpy (buf, pw_struct.pw_dir);
#endif /* NOT ACE_WIN32 AND NOT VXWORKS */
            }

          ACE_OS::strcat (buf, "/");
          ACE_OS::strcat (buf, HTTP_Config::instance ()->user_dir ());
          ACE_OS::strcat (buf, file_name);
        }
      else
        {
          // With a starting '/' but no '~'
          ACE_OS::strcat (buf, HTTP_Config::instance ()->document_root ());
          ACE_OS::strcat (buf, file_name - 1);
        }
    }

  if (*buf != '\0')
    this->path_ = ACE_OS::strdup (buf);

  return this->path_;
}
