/*
  The oSIP library implements the Session Initiation Protocol (SIP -rfc3261-)
  Copyright (C) 2001,2002,2003,2004,2005  Aymeric MOIZARD jack@atosc.org
  
  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
*/


#include <stdlib.h>
#include <stdio.h>

#include <osipparser2/osip_port.h>
#include <osipparser2/osip_message.h>
#include <osipparser2/osip_parser.h>
#include "parser.h"

int
osip_www_authenticate_init (osip_www_authenticate_t ** dest)
{
  *dest =
    (osip_www_authenticate_t *) osip_malloc (sizeof (osip_www_authenticate_t));
  if (*dest == NULL)
    return -1;
  memset (*dest, 0, sizeof (osip_www_authenticate_t));
  return 0;
}

/* fills the www-authenticate header of message.               */
/* INPUT :  char *hvalue | value of header.   */
/* OUTPUT: osip_message_t *sip | structure to save results. */
/* returns -1 on error. */
int
osip_message_set_www_authenticate (osip_message_t * sip, const char *hvalue)
{
  osip_www_authenticate_t *www_authenticate;
  int i;

  if (hvalue == NULL || hvalue[0] == '\0')
    return 0;

  if (sip == NULL || sip->www_authenticates == NULL)
    return -1;
  i = osip_www_authenticate_init (&www_authenticate);
  if (i != 0)
    return -1;
  i = osip_www_authenticate_parse (www_authenticate, hvalue);
  if (i != 0)
    {
      osip_www_authenticate_free (www_authenticate);
      return -1;
    }
  sip->message_property = 2;
  osip_list_add (sip->www_authenticates, www_authenticate, -1);
  return 0;
}

int
__osip_quoted_string_set (const char *name, const char *str,
                          char **result, const char **next)
{
  *next = str;
  if (*result != NULL)
    return 0;                   /* already parsed */
  *next = NULL;
  while ((' ' == *str) || ('\t' == *str) || (',' == *str))
    if (*str)
      str++;
    else
      return -1;                /* bad header format */

  if (strlen (str) <= strlen (name))
    return -1;                  /* bad header format... */
  if (osip_strncasecmp (name, str, strlen (name)) == 0)
    {
      const char *quote1;
      const char *quote2;
      const char *tmp;
      const char *hack = strchr (str, '=');

      if (hack == NULL)
        return -1;

      while (' ' == *(hack - 1))        /* get rid of extra spaces */
        hack--;
      if ((size_t) (hack - str) != strlen (name))
        {
          *next = str;
          return 0;
        }

      quote1 = __osip_quote_find (str);
      if (quote1 == NULL)
        return -1;              /* bad header format... */
      quote2 = __osip_quote_find (quote1 + 1);
      if (quote2 == NULL)
        return -1;              /* bad header format... */
      if (quote2 - quote1 == 1)
        {
          /* this is a special case! The quote contains nothing! */
          /* example:   Digest opaque="",cnonce=""               */
          /* in this case, we just forget the parameter... this  */
          /* this should prevent from user manipulating empty    */
          /* strings */
          tmp = quote2 + 1;     /* next element start here */
          for (; *tmp == ' ' || *tmp == '\t'; tmp++)
            {
            }
          for (; *tmp == '\n' || *tmp == '\r'; tmp++)
            {
            }                   /* skip LWS */
          *next = NULL;
          if (*tmp == '\0')     /* end of header detected */
            return 0;
          if (*tmp != '\t' && *tmp != ' ')
            /* LWS here ? */
            *next = tmp;
          else
            {                   /* it is: skip it... */
              for (; *tmp == ' ' || *tmp == '\t'; tmp++)
                {
                }
              if (*tmp == '\0') /* end of header detected */
                return 0;
              *next = tmp;
            }
          return 0;
        }
      *result = (char *) osip_malloc (quote2 - quote1 + 3);
      if (*result == NULL)
        return -1;
      osip_strncpy (*result, quote1, quote2 - quote1 + 1);
      tmp = quote2 + 1;         /* next element start here */
      for (; *tmp == ' ' || *tmp == '\t'; tmp++)
        {
        }
      for (; *tmp == '\n' || *tmp == '\r'; tmp++)
        {
        }                       /* skip LWS */
      *next = NULL;
      if (*tmp == '\0')         /* end of header detected */
        return 0;
      if (*tmp != '\t' && *tmp != ' ')
        /* LWS here ? */
        *next = tmp;
      else
        {                       /* it is: skip it... */
          for (; *tmp == ' ' || *tmp == '\t'; tmp++)
            {
            }
          if (*tmp == '\0')     /* end of header detected */
            return 0;
          *next = tmp;
        }
  } else
    *next = str;                /* wrong header asked! */
  return 0;
}

int
__osip_token_set (const char *name, const char *str, char **result,
                  const char **next)
{
  const char *beg;
  const char *tmp;

  *next = str;
  if (*result != NULL)
    return 0;                   /* already parsed */
  *next = NULL;

  beg = strchr (str, '=');
  if (beg == NULL)
    return -1;                  /* bad header format... */

  if (strlen (str) < 6)
    return 0;                   /* end of header... */

  while ((' ' == *str) || ('\t' == *str) || (',' == *str))
    if (*str)
      str++;
    else
      return -1;                /* bad header format */

  if (osip_strncasecmp (name, str, strlen (name)) == 0)
    {
      const char *end;

      end = strchr (str, ',');
      if (end == NULL)
        end = str + strlen (str);       /* This is the end of the header */

      if (end - beg < 2)
        return -1;
      *result = (char *) osip_malloc (end - beg);
      if (*result == NULL)
        return -1;
      osip_clrncpy (*result, beg + 1, end - beg - 1);

      /* make sure the element does not contain more parameter */
      tmp = (*end) ? (end + 1) : end;
      for (; *tmp == ' ' || *tmp == '\t'; tmp++)
        {
        }
      for (; *tmp == '\n' || *tmp == '\r'; tmp++)
        {
        }                       /* skip LWS */
      *next = NULL;
      if (*tmp == '\0')         /* end of header detected */
        return 0;
      if (*tmp != '\t' && *tmp != ' ')
        /* LWS here ? */
        *next = tmp;
      else
        {                       /* it is: skip it... */
          for (; *tmp == ' ' || *tmp == '\t'; tmp++)
            {
            }
          if (*tmp == '\0')     /* end of header detected */
            return 0;
          *next = tmp;
        }
  } else
    *next = str;                /* next element start here */
  return 0;
}

/* fills the www-authenticate strucuture.                      */
/* INPUT : char *hvalue | value of header.         */
/* OUTPUT: osip_message_t *sip | structure to save results. */
/* returns -1 on error. */
/* TODO:
   digest-challenge tken has no order preference??
   verify many situations (extra SP....)
*/
int
osip_www_authenticate_parse (osip_www_authenticate_t * wwwa, const char *hvalue)
{
  const char *space;
  const char *next = NULL;

  space = strchr (hvalue, ' '); /* SEARCH FOR SPACE */
  if (space == NULL)
    return -1;

  if (space - hvalue + 1 < 2)
    return -1;
  wwwa->auth_type = (char *) osip_malloc (space - hvalue + 1);
  if (wwwa->auth_type == NULL)
    return -1;
  osip_strncpy (wwwa->auth_type, hvalue, space - hvalue);

  for (;;)
    {
      int parse_ok = 0;

      if (__osip_quoted_string_set ("realm", space, &(wwwa->realm), &next))
        return -1;
      if (next == NULL)
        return 0;               /* end of header detected! */
      else if (next != space)
        {
          space = next;
          parse_ok++;
        }
      if (__osip_quoted_string_set ("domain", space, &(wwwa->domain), &next))
        return -1;
      if (next == NULL)
        return 0;               /* end of header detected! */
      else if (next != space)
        {
          space = next;
          parse_ok++;
        }
      if (__osip_quoted_string_set ("nonce", space, &(wwwa->nonce), &next))
        return -1;
      if (next == NULL)
        return 0;               /* end of header detected! */
      else if (next != space)
        {
          space = next;
          parse_ok++;
        }
      if (__osip_quoted_string_set ("opaque", space, &(wwwa->opaque), &next))
        return -1;
      if (next == NULL)
        return 0;               /* end of header detected! */
      else if (next != space)
        {
          space = next;
          parse_ok++;
        }
      if (__osip_token_set ("stale", space, &(wwwa->stale), &next))
        return -1;
      if (next == NULL)
        return 0;               /* end of header detected! */
      else if (next != space)
        {
          space = next;
          parse_ok++;
        }
      if (__osip_token_set ("algorithm", space, &(wwwa->algorithm), &next))
        return -1;
      if (next == NULL)
        return 0;               /* end of header detected! */
      else if (next != space)
        {
          space = next;
          parse_ok++;
        }
      if (__osip_quoted_string_set ("qop", space, &(wwwa->qop_options), &next))
        return -1;
      if (next == NULL)
        return 0;               /* end of header detected! */
      else if (next != space)
        {
          space = next;
          parse_ok++;
        }
      if (0 == parse_ok)
        {
          char *quote1, *quote2, *tmp;

          /* CAUTION */
          /* parameter not understood!!! I'm too lazy to handle IT */
          /* let's simply bypass it */
          if (strlen (space) < 1)
            return 0;
          tmp = strchr (space + 1, ',');
          if (tmp == NULL)      /* it was the last header */
            return 0;
          quote1 = __osip_quote_find (space);
          if ((quote1 != NULL) && (quote1 < tmp))       /* this may be a quoted string! */
            {
              quote2 = __osip_quote_find (quote1 + 1);
              if (quote2 == NULL)
                return -1;      /* bad header format... */
              if (tmp < quote2) /* the comma is inside the quotes! */
                space = strchr (quote2, ',');
              else
                space = tmp;
              if (space == NULL)        /* it was the last header */
                return 0;
          } else
            space = tmp;
          /* continue parsing... */
        }
    }
  return 0;                     /* ok */
}

/* returns the www_authenticate header.            */
/* INPUT : osip_message_t *sip | sip message.   */
/* returns null on error. */
int
osip_message_get_www_authenticate (const osip_message_t * sip, int pos,
                                   osip_www_authenticate_t ** dest)
{
  osip_www_authenticate_t *www_authenticate;

  *dest = NULL;
  if (osip_list_size (sip->www_authenticates) <= pos)
    return -1;                  /* does not exist */

  www_authenticate =
    (osip_www_authenticate_t *) osip_list_get (sip->www_authenticates, pos);

  *dest = www_authenticate;
  return pos;
}

char *
osip_www_authenticate_get_auth_type (osip_www_authenticate_t * www_authenticate)
{
  return www_authenticate->auth_type;
}

void
osip_www_authenticate_set_auth_type (osip_www_authenticate_t *
                                     www_authenticate, char *auth_type)
{
  www_authenticate->auth_type = (char *) auth_type;
}

char *
osip_www_authenticate_get_realm (osip_www_authenticate_t * www_authenticate)
{
  return www_authenticate->realm;
}

void
osip_www_authenticate_set_realm (osip_www_authenticate_t * www_authenticate,
                                 char *realm)
{
  www_authenticate->realm = (char *) realm;
}

char *
osip_www_authenticate_get_domain (osip_www_authenticate_t * www_authenticate)
{
  return www_authenticate->domain;
}

void
osip_www_authenticate_set_domain (osip_www_authenticate_t * www_authenticate,
                                  char *domain)
{
  www_authenticate->domain = (char *) domain;
}

char *
osip_www_authenticate_get_nonce (osip_www_authenticate_t * www_authenticate)
{
  return www_authenticate->nonce;
}

void
osip_www_authenticate_set_nonce (osip_www_authenticate_t * www_authenticate,
                                 char *nonce)
{
  www_authenticate->nonce = (char *) nonce;
}

char *
osip_www_authenticate_get_stale (osip_www_authenticate_t * www_authenticate)
{
  return www_authenticate->stale;
}

void
osip_www_authenticate_set_stale (osip_www_authenticate_t * www_authenticate,
                                 char *stale)
{
  www_authenticate->stale = (char *) stale;
}

char *
osip_www_authenticate_get_opaque (osip_www_authenticate_t * www_authenticate)
{
  return www_authenticate->opaque;
}

void
osip_www_authenticate_set_opaque (osip_www_authenticate_t * www_authenticate,
                                  char *opaque)
{
  www_authenticate->opaque = (char *) opaque;
}

char *
osip_www_authenticate_get_algorithm (osip_www_authenticate_t * www_authenticate)
{
  return www_authenticate->algorithm;
}

void
osip_www_authenticate_set_algorithm (osip_www_authenticate_t *
                                     www_authenticate, char *algorithm)
{
  www_authenticate->algorithm = (char *) algorithm;
}

char *
osip_www_authenticate_get_qop_options (osip_www_authenticate_t * www_authenticate)
{
  return www_authenticate->qop_options;
}

void
osip_www_authenticate_set_qop_options (osip_www_authenticate_t *
                                       www_authenticate, char *qop_options)
{
  www_authenticate->qop_options = (char *) qop_options;
}



/* returns the www_authenticate header as a string.          */
/* INPUT : osip_www_authenticate_t *www_authenticate | www_authenticate header.  */
/* returns null on error. */
int
osip_www_authenticate_to_str (const osip_www_authenticate_t * wwwa, char **dest)
{
  size_t len;
  char *tmp;

  *dest = NULL;
  if ((wwwa == NULL) || (wwwa->auth_type == NULL))
    return -1;

  len = strlen (wwwa->auth_type) + 1;

  if (wwwa->realm != NULL)
    len = len + strlen (wwwa->realm) + 7;
  if (wwwa->nonce != NULL)
    len = len + strlen (wwwa->nonce) + 8;
  len = len + 2;
  if (wwwa->domain != NULL)
    len = len + strlen (wwwa->domain) + 9;
  if (wwwa->opaque != NULL)
    len = len + strlen (wwwa->opaque) + 9;
  if (wwwa->stale != NULL)
    len = len + strlen (wwwa->stale) + 8;
  if (wwwa->algorithm != NULL)
    len = len + strlen (wwwa->algorithm) + 12;
  if (wwwa->qop_options != NULL)
    len = len + strlen (wwwa->qop_options) + 6;

  tmp = (char *) osip_malloc (len);
  if (tmp == NULL)
    return -1;
  *dest = tmp;

  tmp = osip_str_append (tmp, wwwa->auth_type);

  if (wwwa->realm != NULL)
    {
      tmp = osip_strn_append (tmp, " realm=", 7);
      tmp = osip_str_append (tmp, wwwa->realm);
    }
  if (wwwa->domain != NULL)
    {
      tmp = osip_strn_append (tmp, ", domain=", 9);
      tmp = osip_str_append (tmp, wwwa->domain);
    }
  if (wwwa->nonce != NULL)
    {
      tmp = osip_strn_append (tmp, ", nonce=", 8);
      tmp = osip_str_append (tmp, wwwa->nonce);
    }
  if (wwwa->opaque != NULL)
    {
      tmp = osip_strn_append (tmp, ", opaque=", 9);
      tmp = osip_str_append (tmp, wwwa->opaque);
    }
  if (wwwa->stale != NULL)
    {
      tmp = osip_strn_append (tmp, ", stale=", 8);
      tmp = osip_str_append (tmp, wwwa->stale);
    }
  if (wwwa->algorithm != NULL)
    {
      tmp = osip_strn_append (tmp, ", algorithm=", 12);
      tmp = osip_str_append (tmp, wwwa->algorithm);
    }
  if (wwwa->qop_options != NULL)
    {
      tmp = osip_strn_append (tmp, ", qop=", 6);
      tmp = osip_str_append (tmp, wwwa->qop_options);
    }

  if (wwwa->realm == NULL)
    {
      /* remove comma */
      len = strlen (wwwa->auth_type);
      if ((*dest)[len] == ',')
        (*dest)[len] = ' ';
    }

  return 0;
}

/* deallocates a osip_www_authenticate_t structure.  */
/* INPUT : osip_www_authenticate_t *www_authenticate | www_authenticate. */
void
osip_www_authenticate_free (osip_www_authenticate_t * www_authenticate)
{
  if (www_authenticate == NULL)
    return;

  osip_free (www_authenticate->auth_type);
  osip_free (www_authenticate->realm);
  osip_free (www_authenticate->domain);
  osip_free (www_authenticate->nonce);
  osip_free (www_authenticate->opaque);
  osip_free (www_authenticate->stale);
  osip_free (www_authenticate->algorithm);
  osip_free (www_authenticate->qop_options);

  osip_free (www_authenticate);
}

int
osip_www_authenticate_clone (const osip_www_authenticate_t * wwwa,
                             osip_www_authenticate_t ** dest)
{
  int i;
  osip_www_authenticate_t *wa;

  *dest = NULL;
  if (wwwa == NULL)
    return -1;
  if (wwwa->auth_type == NULL)
    return -1;

  i = osip_www_authenticate_init (&wa);
  if (i == -1)                  /* allocation failed */
    return -1;
  wa->auth_type = osip_strdup (wwwa->auth_type);
  if (wwwa->realm != NULL)
    wa->realm = osip_strdup (wwwa->realm);
  if (wwwa->domain != NULL)
    wa->domain = osip_strdup (wwwa->domain);
  if (wwwa->nonce != NULL)
    wa->nonce = osip_strdup (wwwa->nonce);
  if (wwwa->opaque != NULL)
    wa->opaque = osip_strdup (wwwa->opaque);
  if (wwwa->stale != NULL)
    wa->stale = osip_strdup (wwwa->stale);
  if (wwwa->algorithm != NULL)
    wa->algorithm = osip_strdup (wwwa->algorithm);
  if (wwwa->qop_options != NULL)
    wa->qop_options = osip_strdup (wwwa->qop_options);

  *dest = wa;
  return 0;
}
