/* This file is part of GNU Pies
   Copyright (C) 2009-2026 Sergey Poznyakoff
  
   GNU Pies is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   GNU Pies 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include "libpies.h"
#include <wordsplit.h>

/* scheme://[user[:password]@][host[:port]/]path[;arg=str[;arg=str...] 
*/

static int
alloc_string_len (char **sptr, const char *start, size_t len)
{
  *sptr = malloc (len + 1);
  if (!*sptr)
    return 1;
  memcpy (*sptr, start, len);
  (*sptr)[len] = 0;
  return 0;
}

static int
alloc_string (char **sptr, const char *start, const char *end)
{
  size_t len = end ? end - start : strlen (start);
  return alloc_string_len (sptr, start, len);
}

static int
url_parse_args (struct pies_url *url, char **str)
{
  struct wordsplit ws;

  ws.ws_delim = ";";
  if (wordsplit (*str, &ws, WRDSF_NOVAR | WRDSF_NOCMD | WRDSF_DELIM))
    return 1;
  url->argc = ws.ws_wordc;
  url->argv = ws.ws_wordv;
  return 0;
}

static int
url_parse_path (struct pies_url *url, char **str)
{
  char *p;

  p = strchr (*str, ';');
  if (!p)
    p = *str + strlen (*str);
  if (alloc_string (&url->path, *str, p))
    return 1;
  *str = p;
  if (*p)
    ++*str;
  return url_parse_args (url, str);
}

/* On input str points at the beginning of host part */
static int
url_parse_host (struct pies_url *url, char **str)
{
  char *s = *str;
  size_t len = strcspn (s, "/:");

  if (s[len] == ':')
    {
      char *start = s + len + 1;
      char *q;
      unsigned long n = strtoul (start, &q, 10);
      if (n > USHRT_MAX)
	return 1;
      if ((*q && !strchr ("/;", *q)))
	{
	  char *proto = url->proto_s ? url->proto_s : "tcp";
	  size_t size = strcspn (start, "/;");
	  struct servent *serv;
	  
	  alloc_string_len (&url->port_s, start, size);
	  serv = getservbyname (url->port_s, proto);
	  if (!serv)
	    return 1;
	  url->port = ntohs (serv->s_port);
	  *str = start + size;
	}
      else
	{
	  alloc_string_len (&url->port_s, start, q - start);
	  url->port = n;
	  *str = q;
	}
    }
  else
    *str = s + len;
  if (alloc_string_len (&url->host, s, len))
    return 1;
  if (**str)
    {
      if (*(*str)++ == '/')
	return url_parse_path (url, str);
      else
	return url_parse_args (url, str);
    }
  return 0;
}

/* On input str points past the mech:// part */
static int
url_parse_user (struct pies_url *url, char **str)
{
  if (**str == '/')
    return url_parse_path (url, str);
  else
    {
      size_t len = strcspn (*str, ":;@/");
      char *p = *str + len;
      
      switch (*p)
	{
	case ';':
	case ':':
	  len = strcspn (p + 1, "@/:");
	  if (p[len + 1] == '@')
	    {
	      if (alloc_string_len (&url->passwd, p + 1, len))
		return 1;
	      if (alloc_string (&url->user, *str, p))
		return 1;
	      *str = p + len + 2;
	    }
	  break;
	  
	case '@':
	  if (alloc_string (&url->user, *str, p))
	    return 1;
	  url->passwd = NULL;
	  *str = p + 1;
	}
      return url_parse_host (url, str);
    }
}

static int
url_parse_scheme (struct pies_url *url, const char *str)
{
  size_t len;
  char *p;
  
  if (!str)
    {
      errno = EINVAL;
      return 1;
    }

  len = strcspn (str, ":+");
  if (!str[len])
    {
      errno = EINVAL;
      return 1;
    }

  alloc_string_len (&url->scheme, str, len);

  str += len;
  
  if (*str == '+')
    {
      struct protoent *proto;

      len = strcspn (++str, ":");
      if (str[len] == 0)
	{
	  errno = EINVAL;
	  return 1;
	}
      alloc_string_len (&url->proto_s, str, len);
      str += len;
      proto = getprotobyname (url->proto_s);
      if (!proto)
	{
	  errno = EINVAL;
	  return 1;
	}
      url->proto = proto->p_proto;
    }
  else
    url->proto = 0;
  
  /* Skip slashes */
  p = (char*) str + 1;
  if (memcmp (p, "//", 2))
    {
      errno = EINVAL;
      return 1;
    }
  p += 2;
  return url_parse_user (url, &p);
}

void
pies_url_free_user (struct pies_url *url)
{
  if (url->user)
    {
      free (url->user);
      url->user = NULL;
    }
}

void
pies_url_free_passwd (struct pies_url *url)
{
  if (url->passwd)
    {
      memset (url->passwd, 0, strlen (url->passwd));
      free (url->passwd);
      url->passwd = NULL;
    }
}

void
pies_url_destroy (struct pies_url **purl)
{
  int i;
  if (purl && *purl)
    {
      struct pies_url *url = *purl;

      free (url->string);
      free (url->scheme);
      free (url->host);
      free (url->port_s);
      free (url->proto_s);
      free (url->path);
      pies_url_free_user (url);
      pies_url_free_passwd (url);
      for (i = 0; i < url->argc; i++)
	free (url->argv[i]);
      free (url->argv);
      free (url);
      *purl = NULL;
    }
}

static int
strasgn (char **dst, char *src)
{
  if (!src)
    *dst = NULL;
  else
    {
      *dst = strdup (src);
      if (!*dst)
	return 1;
    }
  return 0;
}
  
static int
do_url_copy (struct pies_url *dest, struct pies_url *src)
{
#define STRASGN(a,b) if (strasgn (a, b)) return -1;
  size_t i;
  
  STRASGN (&dest->string, src->string);
  STRASGN (&dest->scheme, src->scheme);
  STRASGN (&dest->host, src->host);
  STRASGN (&dest->port_s, src->port_s);
  dest->port = src->port;
  STRASGN (&dest->proto_s, src->proto_s);
  dest->proto = src->proto;
  STRASGN (&dest->path, src->path);
  STRASGN (&dest->user, src->user);
  STRASGN (&dest->passwd, src->passwd);
  dest->argv = calloc (src->argc + 1, sizeof (dest->argv[0]));
  if (!dest->argv)
    return -1;
  for (i = 0; i < src->argc; i++)
    {
      if (!(dest->argv[i] = strdup (src->argv[0])))
	{
	  dest->argc = i;
	  return -1;
	}
    }
  dest->argv[i] = NULL;
  dest->argc = i;
  return 0;
#undef STRASGN
}

int
pies_url_copy (struct pies_url **purl, struct pies_url *src)
{
  struct pies_url *dest = malloc (sizeof (*dest));
  
  if (!dest)
    return -1;
  memset (dest, 0, sizeof (*dest));
  if (do_url_copy (dest, src))
    {
      pies_url_destroy (&dest);
      return -1;
    }
  *purl = dest;
  return 0;
}

int
pies_basic_url_create (struct pies_url **purl, const char *str)
{
  int rc;
  struct pies_url *url;

  url = malloc (sizeof (*url));
  if (!url)
    return 1;
  memset (url, 0, sizeof (*url));
  rc = url_parse_scheme (url, str);
  if (rc)
    pies_url_destroy (&url);
  else
    {
      url->string = strdup (str);
      *purl = url;
    }
  return rc;
}

extern char **environ;

int
pies_url_create (struct pies_url **purl, const char *str)
{
  struct wordsplit ws;
  int rc;
  
  ws.ws_env = (const char**) environ;
  if (wordsplit (str, &ws,
		 WRDSF_NOCMD | WRDSF_QUOTE | WRDSF_NOSPLIT | WRDSF_ENV))
    return -1;
  rc = pies_basic_url_create (purl, ws.ws_wordv[0]);
  wordsplit_free (&ws);
  return rc;
}

const char *
pies_url_get_arg (struct pies_url *url, const char *argname)
{
  int i;
  size_t arglen = strlen (argname);
  for (i = 0; i < url->argc; i++)
    {
      size_t len = strcspn (url->argv[i], "=");
      if (len == arglen && memcmp (url->argv[i], argname, arglen) == 0)
	return url->argv[i] + len + 1;
    }
  return NULL;
}
