/*  url.cpp
    Parse a Uniform Resource Locator, according to RFC 1738

    13.08.2001: tk, initial implementation, based on url.c
                    from the PHP 4.06 source code.

    Copyright (c)2000 by Thomas Kindler, thomas.kindler@gmx.de

    This program 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 2 of
    the License, or (at your option) any later version. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files ----------
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <misc.h>
#include <ctype.h>
#include "inet/url.h"


typedef struct {
  char    *scheme;
  unsigned port;
} scheme_t;


static scheme_t schemes[] = {
  {"ftp",      DEFAULT_FTP_PORT},
  {"telnet",   DEFAULT_TELNET_PORT},
  {"mailto",   DEFAULT_SMTP_PORT},
  {"gopher",   DEFAULT_GOPHER_PORT},
  {"http",     DEFAULT_HTTP_PORT},
  {"nntp",     DEFAULT_NNTP_PORT},
  {"news",     DEFAULT_NNTP_PORT},
  {"wais",     DEFAULT_WAIS_PORT},
  {"https",    DEFAULT_HTTPS_PORT},
  {"snews",    DEFAULT_SNEWS_PORT},
  { NULL, 0 }  // unknown port
};


/**
 * Return the default port for a given scheme.
 *
 * \param   scheme  Pointer to scheme string
 * \return  default port for scheme, or 0 if unknown.
 * \todo    is it a good idea to return 0 for unknown
 *          schemes?! [isn't 0 a regular port, too?]
 */
static unsigned port_for_scheme(char *scheme)
{
  scheme_t *s = schemes;
  while (s->scheme) {
    if (!stricmp(s->scheme, scheme))
      return s->port;
    s++;
  }
  return 0;
}


static unsigned char hexchars[] = "0123456789ABCDEF";


/**
 * Convert a 2-digit hexadecimal string to an int.
 *
 * \param  s pointer to a string containing the digits
 * \return converted integer value
 */
static int htoi(char *s)
{
  int value;
  int c;

  c = s[0];
  if (isupper(c))
    c = tolower(c);
  value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16;

  c = s[1];
  if (isupper(c))
    c = tolower(c);
  value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;

  return (value);
}


/**
 * Free an URL structure. Use this function to free all memory
 * allocated by an URL structure, that was previously allocated
 * by parse_url(). All strings and the structure itself are
 * free'd.
 *
 * \param url  Pointer to URL structure to free.
 */
void free_url(URL *url)
{
  if (url->scheme)   free(url->scheme);
  if (url->user)     free(url->user);
  if (url->pass)     free(url->pass);
  if (url->host)     free(url->host);
  if (url->path)     free(url->path);
  if (url->query)    free(url->query);
  if (url->fragment) free(url->fragment);
  free(url);
}


/**
 * Parse an URL and return its components. This function returns
 * an URL data structure, returning any of the various components
 * of the URL that are present. This includes the scheme, host,
 * port, user, pass, path, query, and fragment.
 *
 * The parsing is done according to RFC 1738 (atleast, i hope so,
 * send me a mail, if you find an error!) If the URL contains a
 * scheme but no port, parse_url() tries to find out the default
 * port for this scheme.
 *
 * \param   urlString  URL string to parse
 * \return  pointer to an URL structure containing all components,
 *          or NULL if out of memory. This has to be freed by a call
 *          to free_url() after use.
 * \todo    perhaps we should detect some syntax errors..
 */
URL *parse_url(const char *urlString)
{
  // make a working copy of the URL string
  char *urlStringCopy = strdup(urlString);
  if (!urlStringCopy)
    return NULL;

  // allocate and clear an URL structure
  URL *url = (URL*)malloc(sizeof(URL));
  if (!url) {
    free(urlStringCopy);
    return NULL;
  }
  memset(url, 0, sizeof(URL));

  char *s = urlStringCopy;
  char *p1, *p2;

  // get scheme component (before first ':')
  p1 = strchr(s, ':');
  if (p1) {
    *p1 = '\0';
    url->scheme = strdup(s);
    s = p1+1;

    // skip the "//" for http, ftp, etc..
    if (*s == '/') s++;
    if (*s == '/') s++;
  }

  // get username and password ( user:pass@ )
  p1 = strchr(s, '@');
  if (p1) {
    *p1 = '\0';
    p2 = strchr(s, ':');
    if (p2) {
      *p2 = '\0';
      url->pass = strdup(p2+1);
    }
    url->user = strdup(s);
    s = p1+1;
  }

  // find end of hostname (find next '\0' or '/')
  p1 = strchr(s, '/');
  if (p1)
    *p1 = '\0';

  // look for a port number (host:port)
  p2 = strchr(s, ':');
  if (p2) {
    *p2 = '\0';
    sscanf(p2+1, "%u", &url->port);
  }

  // copy remaining string as hostname
  url->host = strdup(s);
  if (p1)
    s = p1+1;

  // check for path if there was a '/' after hostname
  if (p1) {
    // look for fragment at the end of the path
    p1 = strchr(s, '#');
    if (p1) {
      *p1 = '\0';
      url->fragment = strdup(p1+1);
    }

    // look for query at the end of the path
    p1 = strchr(s, '?');
    if (p1) {
      *p1 = '\0';
      url->query = strdup(p1+1);
    }

    // copy remaining string as path
    url->path = strdup(s);
  }
  free(urlStringCopy);

  if (!url->port)
    url->port = port_for_scheme(url->scheme);

  return url;
}


/**
 * URL-encode according to RFC1738.
 * Returns a string in which all non-alphanumeric characters except -_. have
 * been replaced with a percent (%) sign followed by two hex digits. This is
 * the encoding described in RFC1738 for protecting literal characters from
 * being interpreted as special URL delimiters, and for protecting URL's from
 * being mangled by transmission media with character conversions (like some
 * email systems).
 *
 * \param s    String to encode
 * \param len  Number of string characters to encode
 * \return     Pointer to encoded string. (has to be free'd after use)
 */
char *url_encode(char *s, unsigned len)
{
  char *str = (char *)malloc(3*len + 1);
  if (!str)
    return NULL;

  int x,y;
  for (x=0, y=0; len--; x++, y++) {
    str[y]=(char)s[x];
    if (
      (str[y] < '0' && str[y] != '-' && str[y] != '.') ||
      (str[y] < 'A' && str[y] > '9') ||
      (str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||
      (str[y] > 'z')
    ) {
      str[y++] = '%';
      str[y++] = hexchars[(unsigned char) s[x] >> 4];
      str[y]   = hexchars[(unsigned char) s[x] & 15];
    }
  }
  str[y] = '\0';
  return str;
}


/**
 * Decode URL-encoded data.
 *
 * Returns a string in which the sequences with percent (%) signs followed by
 * two hex digits have been replaced with literal characters.
 * For example, the string "foo%20bar%40baz" decodes into "foo bar@baz".
 *
 * \param  str  Data to decode. The data is decoded in-place, and
 *              also functions as the target buffer.
 * \param  len  number of encoded characters to decode.
 * \return Number of decoded characters.
 */
unsigned url_decode(char *str, unsigned len)
{
  char *dst = str;
  char *src = str;

  while (len--) {
    if (  *src == '%' &&  len  >=   2 &&
          isxdigit(src[1]) &&  isxdigit(src[2])  )
    {
      *dst++ = (char)htoi(src + 1);
      src   += 2;
      len   -= 2;
    } else {
      *dst++ = *src++;
    }
  }
  *dst = '\0';

  return (unsigned)(dst-str);
}

