/*
 * Copyright (c) 2000-2006  Motoyuki Kasahara
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/*
 * Character type tests and conversions.
 */
#define ASCII_ISDIGIT(c) ('0' <= (c) && (c) <= '9')
#define ASCII_ISUPPER(c) ('A' <= (c) && (c) <= 'Z')
#define ASCII_ISLOWER(c) ('a' <= (c) && (c) <= 'z')
#define ASCII_ISALPHA(c) \
 (ASCII_ISUPPER(c) || ASCII_ISLOWER(c))
#define ASCII_ISALNUM(c) \
 (ASCII_ISUPPER(c) || ASCII_ISLOWER(c) || ASCII_ISDIGIT(c))
#define ASCII_ISXDIGIT(c) \
 (ASCII_ISDIGIT(c) || ('A' <= (c) && (c) <= 'F') || ('a' <= (c) && (c) <= 'f'))
#define ASCII_TOUPPER(c) (('a' <= (c) && (c) <= 'z') ? (c) - 0x20 : (c))
#define ASCII_TOLOWER(c) (('A' <= (c) && (c) <= 'Z') ? (c) + 0x20 : (c))

#include "urlparts.h"

/*
 * Unexported functions.
 */
static void url_parts_canonicalize_path(char *path);
static void url_parts_expand_hex(char *string);
static void url_parts_convert_to_lower(char *string);


/*
 * Initialize a query.
 */
void
url_parts_initialize(URL_Parts *parts)
{
  /*
   * Set all URL parts and whole URL to NULL.
   */
  parts->url    = NULL;
  parts->scheme   = NULL;
  parts->user   = NULL;
  parts->password = NULL;
  parts->host   = NULL;
  parts->port   = NULL;
  parts->path   = NULL;
  parts->params   = NULL;
  parts->query  = NULL;
  parts->fragment = NULL;

  parts->buffer   = NULL;
}


/*
 * Finalize a query.
 * All allocated memories in `queue' are also disposed.
 */
void
url_parts_finalize(URL_Parts *parts)
{
  /*
   * Set all URL parts and whole URL to NULL.
   */
  parts->scheme   = NULL;
  parts->user   = NULL;
  parts->password = NULL;
  parts->host   = NULL;
  parts->port   = NULL;
  parts->path   = NULL;
  parts->params   = NULL;
  parts->query  = NULL;
  parts->fragment = NULL;

  /*
   * Dispose memories assigned to `parts->url' and `parts->buffer'.
   */
  if (parts->url != NULL) {
    free(parts->url);
    parts->url = NULL;
  }

  if (parts->buffer != NULL) {
    free(parts->buffer);
    parts->buffer = NULL;
  }
}


/*
 * Return whole URL of a parsed URL.
 */
const char *
url_parts_url(URL_Parts *parts)
{
  return parts->url;
}


/*
 * Return scheme part of a parsed URL.
 */
const char *
url_parts_scheme(URL_Parts *parts)
{
  return parts->scheme;
}


/*
 * Return user part of a parsed URL.
 */
const char *
url_parts_user(URL_Parts *parts)
{
  return parts->user;
}


/*
 * Return password part of a parsed URL.
 */
const char *
url_parts_password(URL_Parts *parts)
{
  return parts->password;
}


/*
 * Return host part of a parsed URL.
 */
const char *
url_parts_host(URL_Parts *parts)
{
  return parts->host;
}


/*
 * Return port part of a parsed URL.
 */
const char *
url_parts_port(URL_Parts *parts)
{
  return parts->port;
}


/*
 * Return path part of a parsed URL.
 */
const char *
url_parts_path(URL_Parts *parts)
{
  return parts->path;
}


/*
 * Return params part of a parsed URL.
 */
const char *
url_parts_params(URL_Parts *parts)
{
  return parts->params;
}


/*
 * Return query part of a parsed URL.
 */
const char *
url_parts_query(URL_Parts *parts)
{
  return parts->query;
}


/*
 * Return fragment part of a parsed URL.
 */
const char *
url_parts_fragment(URL_Parts *parts)
{
  return parts->fragment;
}


/*
 * Parse an URL and fragment.
 *
 * `url' is an absolute or relative URL and optional fragment.
 * The function resolves the `url' into 9 parts; scheme, user, password,
 * host, port, path, params, query and fragment.
 *
 * [scheme ":"] ["//" [<user>][":" password] "@"]["host" [":" port]]
 *   ["/" path] [";" params] ["?" query] ["#" fragment]
 *
 * The result is put into `parts'.  This functions fails only when
 * memory is exhausted.  The function returns -1 in this case.
 * It returns 0, upon successful.
 */
int
url_parts_parse(URL_Parts *parts, const char *url)
{
  char *separator;
  char *url_p;
  size_t url_length;
  char *right_bracket;

  /*
   * Re-initialize if `url' has already been used.
   */
  if (parts->buffer != NULL) {
    url_parts_finalize(parts);
    url_parts_initialize(parts);
  }

  /*
   * Copy `url' to `parts->url' and `parts->buffer'.
   */
  url_length = strlen(url);
  parts->url = (char *)malloc(url_length + 1);
  if (parts->url == NULL)
    goto failed;
  memcpy(parts->url, url, url_length + 1);

  parts->buffer = (char *)malloc(url_length + 1);
  if (parts->buffer == NULL)
    goto failed;
  memcpy(parts->buffer, url, url_length + 1);

  /*
   * Get a fragment.
   */
  url_p = parts->buffer;
  separator = strchr(url_p, '#');
  if (separator != NULL) {
    if (*(separator + 1) != '\0')
      parts->fragment = separator + 1;
    *separator = '\0';
  }

  /*
   * Get a scheme name.
   */
  separator = strchr(url_p, ':');
  if (separator != NULL) {
    char *p;

    for (p = url_p; *p != ':'; p++) {
      if (!ASCII_ISALNUM(*p) && *p != '+' && *p != '.' && *p != '-')
        break;
    }
    if (*p == ':') {
      parts->scheme = url_p;
      *p = '\0';
      url_p = p + 1;
    }
  }

  /*
   * Get a network location.
   */
  if (*url_p == '/' && *(url_p + 1) == '/') {
    char *netloc = NULL;
    char *hostport = NULL;
    char *userpass = NULL;
    char *p1, *p2;

    /*
     * Shift left 2 characters to insert a terminator ('\0') for
     * the network location, and a missed slash (`/').
     *
     * "//www.foo.co.jp/path..." --> "www.foo.co.jp\0\0/path..."
     * "//www.foo.co.jp\0" --> "www.foo.co.jp\0/\0"
     */
    netloc = url_p;
    for (p1 = url_p + 2, p2 = url_p;
       *p1 != '/' && *p1 != '\0'; p1++, p2++)
      *p2 = *p1;
    *p2++ = '\0';
    if (*p1 != '\0')
      url_p = p1;
    else {
      *p2 = '/';
      url_p = p2;
    }

    /*
     * Separate `netloc' into "<user>:<password>" and "<host>:<port>".
     */
    separator = strchr(netloc, '@');
    if (separator != NULL) {
      if (separator != netloc)
        userpass = netloc;
      if (*(separator + 1) != '\0')
        hostport = separator + 1;
      *separator = '\0';
    } else {
      hostport = netloc;
      userpass = NULL;
    }

    /*
     * Get user and password.
     */
    if (userpass != NULL) {
      separator = strchr(userpass, ':');
      if (separator != NULL) {
        if (separator != userpass)
          parts->user = userpass;
        if (*(separator + 1) != '\0')
          parts->password = separator + 1;
        *separator = '\0';
      } else {
        parts->user = userpass;
      }
    }

    /*
     * Get host and port.
     * IPv6 address is enclosed in `[' and `]'.
     */
    if (*hostport == '[') {
      right_bracket = strchr(hostport + 1, ']');
      if (right_bracket == NULL)
        separator = NULL;
      else {
        if (*(right_bracket + 1) == ':'
          || *(right_bracket + 1) == '\0') {
          hostport++;
          *right_bracket = '\0';
        }
        separator = strchr(right_bracket + 1, ':');
      }
    } else {
      separator = strchr(hostport, ':');
    }

    if (separator != NULL) {
      if (separator != hostport)
        parts->host = hostport;
      if (*(separator + 1) != '\0')
        parts->port = separator + 1;
      *separator = '\0';
    } else {
      parts->host = hostport;
    }
  }

  /*
   * Parse query.
   */
  separator = strchr(url_p, '?');
  if (separator != NULL) {
    if (*(separator + 1) != '\0')
      parts->query = separator + 1;
    *separator = '\0';
  }

  /*
   * Parse parameters.
   * The part is deleted if present.
   */
  separator = strchr(url_p, ';');
  if (separator != NULL) {
    if (*(separator + 1) != '\0')
      parts->params = separator + 1;
    *separator = '\0';
  }

  /*
   * Parse path.
   */
  if (*url_p != '\0')
    parts->path = url_p;

  /*
   * Normalize parts.
   */
  if (parts->scheme != NULL) {
    url_parts_expand_hex(parts->scheme);
    url_parts_convert_to_lower(parts->scheme);
  }
  if (parts->user != NULL) {
    url_parts_expand_hex(parts->user);
  }
  if (parts->password != NULL) {
    url_parts_expand_hex(parts->password);
  }
  if (parts->host != NULL) {
    url_parts_expand_hex(parts->host);
    url_parts_convert_to_lower(parts->host);
  }
  if (parts->port != NULL) {
    url_parts_expand_hex(parts->port);
  }
  if (parts->path != NULL) {
    url_parts_expand_hex(parts->path);
    url_parts_canonicalize_path(parts->path);
  }
  if (parts->params != NULL) {
    url_parts_expand_hex(parts->params);
  }
  if (parts->query != NULL) {
    url_parts_expand_hex(parts->query);
  }
  if (parts->fragment != NULL) {
    url_parts_expand_hex(parts->fragment);
  }

  return 0;

  /*
   * An error occurs...
   */
  failed:
  url_parts_finalize(parts);
  return -1;
}


/*
 * Canonicalize the path in URL.
 *
 * Replace "." and ".." segments in the path.
 * All occurrences of "." segment are removed.
 * All occurrences of "<segment>/.." segments where <segment> is not
 * equal to "..", are removed.  This process is repeated until no
 * match pattern remains.
 *
 * For example:
 *     "/A/./a.html"      --->  "/A/a.html"
 *     "/A/../a.html"     --->  "/a.html"
 *     "/A/B/C/../../a.html"  --->  "/A/a.html"
 *     "/../a.html"       --->  "/../a.html" (*1)
 *
 * (*1) If a correspondig parent segment is not exist in the path,
 * we don't remove "/..".
 */
static void
url_parts_canonicalize_path(char *path)
{
  char *source = path;
  char *destination = path;
  char *unfixed_root = path;
  char *slash;

  while (*source != '\0') {
    if (*source != '/') {
      *destination++ = *source++;
      continue;
    }

    /*
     * `*source' is slash (`/').
     */
    if (*(source + 1) == '/') {
      /*
       * "//" -- Ignore 2nd slash ("/").
       */
      source++;
    } else if (*(source + 1) == '.'
      && *(source + 2) == '/') {
      /*
       * "/./" -- Current segment itself.  Removed.
       */
      if (unfixed_root != destination) {
        source += 2;
      } else {
        *destination++ = *source++;
        *destination++ = *source++;
        unfixed_root += 2;
      }
    } else if (*(source + 1) == '.' && *(source + 2) == '.'
      && *(source + 3) == '/') {
      /*
       * "/../" -- Back to the parent segment.
       */
      if (unfixed_root != destination) {
        source += 3;
        *destination = '\0';
        slash = strrchr(unfixed_root, '/');
        if (slash != NULL)
          destination = slash;
        else
          destination = path;
      } else {
        *destination++ = *source++;
        *destination++ = *source++;
        *destination++ = *source++;
        unfixed_root += 3;
      }
    } else {
      /*
       * "/" -- Single "/".
       */
      *destination++ = *source++;
    }
  }
  *destination = '\0';

  slash = strrchr(unfixed_root, '/');
  if (slash != NULL
    && slash != unfixed_root
    && *(slash + 1) == '.' && *(slash + 2) == '\0') {
    /*
     * The path is end with "/.",  Remove ".".
     * After the canonicalization, the path is end with "/".
     */
    *(slash + 1) = '\0';
  } else if (slash != NULL
    && slash != unfixed_root
    && *(slash + 1) == '.' && *(slash + 2) == '.'
    && *(slash + 3) == '\0') {
    /*
     * The path is end with "/..",  Back to the parent segment.
     * After the canonicalization, the path is end with "/".
     */
    *slash = '\0';
    slash = strrchr(unfixed_root, '/');
    if (slash == NULL) {
      *path = '/';
      *(path + 1) = '\0';
    } else
      *(slash + 1) = '\0';
  }
}

/*
 * All characters except for the followings are expandable characters.
 * Converting "%<HEX><HEX>" to that character doesn't affect URL parsing.
 *
 *    unsafe   ::= CTL | SP | <"> | "#" | "%" | "<" | ">"
 *    reserved ::= ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"
 *
 * `expandable_hex_table[]' is used to examine whether a character is
 * expandable or not.
 */
static const char expandable_hex_table[] = {
  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0, /* 0x00 ... 0x0f  */
  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0, /* 0x10 ... 0x1f  */
  0, 1, 0, 0,  1, 0, 0, 1,  1, 1, 1, 0,  1, 1, 1, 0, /*  !"#$%&'()*+,-./ */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 0, 0,  0, 0, 0, 0, /* 0123456789:;<=>? */
  0, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* @ABCDEFGHIJKLMNO */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* PQRSTUVWXYZ[\]^_ */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* `abcdefghijklmno */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* pqrstuvwxyz{|}~  */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* 0x80 ... 0x8f  */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* 0x90 ... 0x9f  */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* 0xa0 ... 0xaf  */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* 0xb0 ... 0xbf  */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* 0xc0 ... 0xcf  */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* 0xd0 ... 0xdf  */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* 0xe0 ... 0xef  */
  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1, /* 0xf0 ... 0xff  */
};

/*
 * Expand all "%<HEX><HEX>" in an URL part to original characters.
 */
static void
url_parts_expand_hex(char *string)
{
  char *source = string;
  char *destination = string;
  int hex1;
  int hex2;
  int c;

  while (*source != '\0') {
    /*
     * "%<HEX><HEX>" occurs.
     * Unescape it if the character is not unsafe or reserved.
     */
    if (*source == '%'
      && ASCII_ISXDIGIT(*(source + 1))
      && ASCII_ISXDIGIT(*(source + 2))) {
      hex1 = *(source + 1);
      hex2 = *(source + 2);
      c = 0;

      if ('0' <= hex1 && hex1 <= '9')
        c += (hex1 - '0') << 4;
      else if ('A' <= hex1 && hex1 <= 'F')
        c += (hex1 - 'A' + 0x0a) << 4;
      else if ('a' <= hex1 && hex1 <= 'f')
        c += (hex1 - 'a' + 0x0a) << 4;

      if ('0' <= hex2 && hex2 <= '9')
        c += (hex2 - '0');
      else if ('A' <= hex2 && hex2 <= 'F')
        c += (hex2 - 'A' + 0x0a);
      else if ('a' <= hex2 && hex2 <= 'f')
        c += (hex2 - 'a' + 0x0a);

      if (expandable_hex_table[c])
        *(unsigned char *)destination++ = c;
      else {
        *destination++ = '%';
        *destination++ = hex1;
        *destination++ = hex2;
      }
      source += 3;

    } else
      *destination++ = *source++;
  }

  *destination = '\0';
}


/*
 * Convert upper case letters in a stinrg `string' to lower case.
 * This function is for the `scheme' and `host' parts in an URL,
 * since they are case insensitive,
 */
static void
url_parts_convert_to_lower(char *string)
{
  char *p;

  for (p = string; *p != '\0'; p++) {
    if (ASCII_ISUPPER(*p))
      *p = 'a' + (*p - 'A');
  }
}


void
url_parts_print(URL_Parts *parts)
{
  printf("url parts = {\n");
  if (parts->scheme != NULL)
    printf("  scheme = %s\n", parts->scheme);
  if (parts->user != NULL)
    printf("  user = %s\n", parts->user);
  if (parts->password != NULL)
    printf("  password = %s\n", parts->password);
  if (parts->host != NULL)
    printf("  host = %s\n", parts->host);
  if (parts->port != NULL)
    printf("  port = %s\n", parts->port);
  if (parts->path != NULL)
    printf("  path = %s\n", parts->path);
  if (parts->params != NULL)
    printf("  params = %s\n", parts->params);
  if (parts->query != NULL)
    printf("  query = %s\n", parts->query);
  if (parts->fragment != NULL)
    printf("  fragment = %s\n", parts->fragment);
  printf("}\n");
  fflush(stdout);
}

/*
 * Main for test.
 */
#ifdef TEST

int
main(int argc, char *argv[])
{
  URL_Parts url;
  int i;

  if (argc < 2) {
    fprintf(stderr, "usage: %s URL\n", argv[0]);
    exit(1);
  }

  url_parts_initialize(&url);
  url_parts_parse(&url, argv[1]);
  url_parts_print(&url);
  fflush(stdout);
  url_parts_finalize(&url);
}

#endif
