/*===========================================================================*
 *                                                                           *
 *  sflhttp.c - HTTP processing functions                                    *
 *                                                                           *
 *  Copyright (c) 1991-2010 iMatix Corporation                               *
 *                                                                           *
 *  ------------------ GPL Licensed Source Code ------------------           *
 *  iMatix makes this software available under the GNU General               *
 *  Public License (GPL) license for open source projects.  For              *
 *  details of the GPL license please see www.gnu.org or read the            *
 *  file license.gpl provided in this package.                               *
 *                                                                           *
 *  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 3 of           *
 *  the License, or (at your option) any later version.                      *
 *                                                                           *
 *  This program 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 this program in the file 'license.gpl'; if            *
 *  not, see <http://www.gnu.org/licenses/>.                                 *
 *                                                                           *
 *  You can also license this software under iMatix's General Terms          *
 *  of Business (GTB) for commercial projects.  If you have not              *
 *  explicitly licensed this software under the iMatix GTB you may           *
 *  only use it under the terms of the GNU General Public License.           *
 *                                                                           *
 *  For more information, send an email to info@imatix.com.                  *
 *  --------------------------------------------------------------           *
 *===========================================================================*/

#include "prelude.h"                    /*  Universal header file            */
#include "sflstr.h"                     /*  String functions                 */
#include "sflsymb.h"                    /*  Symbol functions                 */
#include "sflconv.h"                    /*  Convertion functions             */
#include "sfllist.h"                    /*  Linked-list functions            */
#include "sflmem.h"                     /*  Memory functions                 */
#include "sflfind.h"                    /*  Find text functions              */
#include "sflfile.h"                    /*  Files functions                  */
#include "sfldate.h"                    /*  Date/time functions              */
#include "sflprint.h"                   /*  snprintf functions               */
#include "sflhttp.h"                    /*  Prototypes for functions         */

/*  Constants -------------------------------------------------------------- */

/*  This is the maximum size of a stream of HTTP query data coming from a
 *  file.  Used by cgi_parse_file_vars ().
 */

#define CGI_QUERY_FILE_MAX      65535U
#define MULTI_BUFFER_SIZE       16384
#define REST_BUFFER_SIZE        20000


/*- Local functions ---------------------------------------------------------*/

static size_t http_escape_char        (char code, char *result, size_t outmax,
                                       Bool hex_spaces);
static size_t http_escape_byte        (char code, char *result, size_t outmax,
                                       Bool hex_spaces);
static int    decode_hex              (const char **input, size_t outmax);
static void   save_multipart_header   (SYMTAB *table, SYMTAB *header,
                                       char *data, char *tmp_name,
                                       const char *local_format);
static void   multipart_decode_header (char *header, SYMTAB *table);
static DESCR *http_multipart2url      (const SYMTAB *symtab);


/*  ---------------------------------------------------------------------[<]-
    Function: http_escape

    Synopsis: Performs HTTP escaping on a string.  This works as follows:
    all characters except alphanumerics and spaces are converted into the
    3-byte sequence "%xx" where xx is the character's hexadecimal value;
    spaces are replaced by '+'.  Line breaks are stored as "%0D%0A", where
    a 'line break' is any one of: "\n", "\r", "\n\r", or "\r\n".  If the
    result buffer is NULL, calculates the required size, allocates a block
    of memory, and returns that.  Otherwise, returns result, which must be
    large enough for the escaping operation (see http_escape_size()).
    When you all http_escape() with a null target block, you must free the
    returned block using mem_free().  Returns NULL if it could not allocate
    a target block as required.  If outmax is non-zero then no more than
    outmax characters (including the NULL terminator) are stored.
    ---------------------------------------------------------------------[>]-*/

char *
http_escape (
    const char *string,
    char *result,
    size_t outmax)
{
    char
        *target;                        /*  Where we store the result        */
    size_t
        length;                         /*  of escaped character             */

    ASSERT (string);
    if (outmax == 0)                    /*  If no fixed length, get total len*/
        outmax = http_escape_size (string);

    if (result == NULL)
        if ((result = mem_alloc (outmax)) == NULL)
            return (NULL);              /*  Could not allocate a block       */

    if (outmax > 1)
        outmax -= 1;                    /*  Leave space for NULL terminator  */
    else
    if (outmax == 1)                    /*  Only room for terminator         */
      {
        *result = '\0';
        return (result);
      }
    target = result;
    while (*string)
      {
        length = http_escape_char (*string, target, outmax, FALSE);
        if (length == 0)
            break;

        target += length;
        outmax -= length;
        if (*string == '\n' || *string == '\r')
          {
            if ((string [1] == '\n' || string [1] == '\r')
            &&  (string [1] != *string))
                string++;
          }
        string++;
      }
    *target = '\0';                     /*  Terminate target string          */
    return (result);
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_escape_bin

    Synopsis: Performs HTTP escaping on a binary string. This works as follows:
    all characters except alphanumerics and spaces are converted into the
    3-byte sequence "%xx" where xx is the character's hexadecimal value;
    spaces are replaced by '%20'. If the
    result buffer is NULL, calculates the required size, allocates a block
    of memory, and returns that.  Otherwise, returns result, which must be
    large enough for the escaping operation (see http_escape_size()).
    When you all http_escape() with a null target block, you must free the
    returned block using mem_free().  Returns NULL if it could not allocate
    a target block as required.  If outmax is non-zero then no more than
    outmax characters (including the NULL terminator) are stored.
    ---------------------------------------------------------------------[>]-*/

char *
http_escape_bin (
    const byte *string,
    size_t      string_size,
    char       *result,
    size_t      outmax)
{
    char
        *target;                        /*  Where we store the result        */
    size_t
        length;                         /*  of escaped character             */
    byte
        *end;

    ASSERT (string);

    if (outmax == 0)                    /*  If no fixed length, get total len*/
        outmax = http_escape_bin_size (string, string_size);

    if (result == NULL)
        if ((result = mem_alloc (outmax)) == NULL)
            return (NULL);              /*  Could not allocate a block       */

    if (outmax > 1)
        outmax -= 1;                    /*  Leave space for NULL terminator  */
    else
    if (outmax == 1)                    /*  Only room for terminator         */
      {
        *result = '\0';
        return (result);
      }
    target = result;
    end    = (byte *)string + string_size;
    while (string < end)
      {
        length = http_escape_byte (*string, target, outmax, TRUE);
        if (length == 0)
            break;

        target += length;
        outmax -= length;
        string++;
      }
    *target = '\0';                     /*  Terminate target string          */
    return (result);
}


/*---------------------------------------------------------------------------
 *  http_escape_char -- local
 *
 *  Performs HTTP escaping on a character.
 *---------------------------------------------------------------------------*/

static size_t
http_escape_char (
    char   code,
    char  *result,
    size_t outmax,
    Bool   hex_spaces)
{
    static char
        hex_char [] = "0123456789ABCDEF";

    if (isalnum (code))                 /*  Don't escape letters or digits   */
      {
        if (outmax < 1)
            return (0);
        *result = code;
        return 1;
      }
    else
    if (code == ' ' && !hex_spaces)     /*  Spaces are replaced by '+'       */
      {
        if (outmax < 1)
            return (0);
        *result = '+';
        return 1;
      }
    if (code == '\n' || code == '\r')
      {
        if (outmax < 6)
            return 0;
        *result++ = '%';                /*  New line becomes %0A%0D          */
        *result++ = '0';
        *result++ = 'A';
        *result++ = '%';
        *result++ = '0';
        *result++ = 'D';
        return (6);
      }
    else
      {
        if (outmax < 3)
            return 0;
        *result++ = '%';                /*  Some other escaped character     */
        *result++ = hex_char [(byte) code >> 4];
        *result++ = hex_char [(byte) code & 15];
        return (3);
      }
}


/*---------------------------------------------------------------------------
 *  http_escape_char -- local
 *
 *  Performs HTTP escaping on a character.
 *---------------------------------------------------------------------------*/

static size_t
http_escape_byte (
    char   code,
    char  *result,
    size_t outmax,
    Bool   hex_spaces)
{
    static char
        hex_char [] = "0123456789ABCDEF";

    if (isalnum (code))                 /*  Don't escape letters or digits   */
      {
        if (outmax < 1)
            return (0);
        *result = code;
        return 1;
      }
    else
    if (code == ' ' && !hex_spaces)     /*  Spaces are replaced by '+'       */
      {
        if (outmax < 1)
            return (0);
        *result = '+';
        return 1;
      }
    if (outmax < 3)
        return 0;
    *result++ = '%';                /*  Some other escaped character     */
    *result++ = hex_char [(byte) code >> 4];
    *result++ = hex_char [(byte) code & 15];
    return (3);
}

/*  ---------------------------------------------------------------------[<]-
    Function: http_escape_size

    Synopsis: Returns the size of a string after HTTP escaping.  See the
    http_escape() function for details of the escaping algorithm.  Includes
    the null terminator in the returned size.
    ---------------------------------------------------------------------[>]-*/

size_t
http_escape_size (
    const char *string)
{
    size_t
        result_size = 1;                /*  Allow for null terminator        */

    ASSERT (string);
    while (*string)
      {
        if (isalnum (*string))          /*  Don't escape letters or digits   */
            result_size++;
        else
        if (*string == '\n' || *string == '\r')
          {
            if ((string [1] == '\n' || string [1] == '\r')
            &&  (string [1] != *string))
                string++;
            result_size += 6;           /*  Line ending becomes %0D%0A       */
          }
        else
            result_size += 3;           /*  Some other escaped character     */

        string++;
      }
    return (result_size);
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_escape_bin_size

    Synopsis: Returns the size of a binary string after HTTP escaping.  See the
    http_escape_bin () function for details of the escaping algorithm.  Includes
    the null terminator in the returned size.
    ---------------------------------------------------------------------[>]-*/

size_t
http_escape_bin_size (
    const byte *string, size_t size)
{
    size_t
        result_size = 1;                /*  Allow for null terminator        */
    byte
        *end;

    end = (byte *)string + size;

    ASSERT (string);

    while (string < end)
      {
        if (isalnum (*string))          /*  Don't escape letters or digits   */
            result_size++;
        else
            result_size += 3;           /*  Some other escaped character     */

        string++;
      }
    return (result_size);
}


/*  -------------------------------------------------------------------------
    Function: decode_hex

    Synopsis: Decodes a hexadecimal string.  Stops after outmax characters
    or when an invalid hex character is reached.  Sets the input pointer
    to the first unprocessed character.  Returns the result.
    -------------------------------------------------------------------------*/

static int
decode_hex (
    const char   **input,
    size_t         outmax)
{
    static char
        hex_to_bin [128] = {
           -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,    /*            */
           -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,    /*            */
           -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,    /*            */
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,    /*   0..9     */
           -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,    /*   A..F     */
           -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,    /*            */
           -1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,    /*   a..f     */
           -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 };  /*            */
    int
        nextch;
    size_t
        index,
        result;

    ASSERT (input);
    ASSERT (*input);

    index  = 0;
    result = 0;
    while (outmax == 0 || index < outmax)
      {
        nextch = (*input) [index] & 127;
        if (nextch && hex_to_bin [nextch] != -1)
          {
            result = result * 16 + hex_to_bin [nextch];
            index++;
          }
        else
            break;
      }
    (*input) += index;
    return (result);
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_escape_hex

    Synopsis: Works as http_escape(), but converts spaces to %20 instead of
    '+'.  This encoding is standard for filenames and URL requests.
    ---------------------------------------------------------------------[>]-*/

char *
http_escape_hex (
    const char *string,
    char  *result,
    size_t outmax)
{
    char
        *target;                        /*  Where we store the result        */
    size_t
        length;                         /*  of escaped character             */

    ASSERT (string);
    if (outmax == 0)                    /*  If no fixed length, get total len*/
        outmax = http_escape_size (string);

    if (result == NULL)
        if ((result = mem_alloc (outmax)) == NULL)
            return (NULL);              /*  Could not allocate a block       */

    if (outmax > 1)
        outmax -= 1;                    /*  Leave space for NULL terminator  */
    else
    if (outmax == 1)                    /*  Only room for terminator         */
      {
        *result = '\0';
        return (result);
      }
    target = result;
    while (*string)
      {
        length = http_escape_char (*string, target, outmax, TRUE);
        if (length == 0)
            break;

        target += length;
        outmax -= length;
        if (*string == '\n' || *string == '\r')
          {
            if ((string [1] == '\n' || string [1] == '\r')
            &&  (string [1] != *string))
                string++;
          }
        string++;
      }
    *target = '\0';                     /*  Terminate target string          */
    return (result);
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_unescape

    Synopsis: Removes HTTP escaping from a string.  See http_escape() for
    details of the escaping algorithm.  If the result string is NULL,
    modifies the source string in place, else fills-in the result string.
    Returns the resulting string.  End-of-line sequences (%0A%0D) are
    stored as a single new-line character, i.e. carriage-returns (%0D) are
    not stored.
    ---------------------------------------------------------------------[>]-*/

char *
http_unescape (
    char *string,
    char *result)
{
    char
        *target;                        /*  Where we store the result        */

    ASSERT (string);
    if (!result)                        /*  If result string is null,        */
        result = string;                /*    modify in place                */
    target = result;

    while (*string)
      {
        if (*string == '%'              /*  Unescape %xx sequence            */
        &&   string [1] && string [2])
          {
            string++;
            *target = decode_hex ((const char **) &string, 2);
            if (*target != '\r')
                target++;               /*  We do not store CRs              */
          }
        else
          {
            if (*string == '+')         /*  Spaces are escaped as '+'        */
                *target++ = ' ';
            else
                *target++ = *string;    /*  Otherwise just copy              */

            string++;
          }
      }
    *target = '\0';                     /*  Terminate target string          */
    return (result);
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_unescape_hex

    Synopsis: Removes HTTP hex escaping from a URL string, by expanding any
    sequences of characters %xx.
    ---------------------------------------------------------------------[>]-*/

char *
http_unescape_hex (
    char *string,
    char *result)
{
    char
        *target;                        /*  Where we store the result        */

    ASSERT (string);
    if (!result)                        /*  If result string is null,        */
        result = string;                /*    modify in place                */
    target = result;

    while (*string)
      {
        if (*string == '%'              /*  Unescape %xx sequence            */
        &&   string [1] && string [2])
          {
            string++;
            *target = decode_hex ((const char **) &string, 2);
            target++;
          }
        else
          {
            *target++ = *string;        /*  Otherwise just copy              */
            string++;
          }
      }
    *target = '\0';                     /*  Terminate target string          */
    return (result);
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_query2strt

    Synopsis: Parses a HTTP query string, building an array of strings of
    the format "name=value".  The query string is assumed to be in escaped
    format, so http_unescape() is always applied to the query string.
    Within the query string, field=value pairs are delimited by & or ;.
    Returns a pointer to the array.  The array is allocated dynamically.
    The array ends with a NULL string.  To free the table, call strtfree().
    If there was not enough memory to allocate the table, returns NULL.
    ---------------------------------------------------------------------[>]-*/

char **
http_query2strt (
    const char *original_query)
{
    char
        *query,                         /*  Local copy of query string       */
        *query_ptr,                     /*  Pointer into query string        */
        *query_next,                    /*  Pointer to next query chunk      */
        **strings;                      /*  Returned string array            */
    int
        char_nbr,                       /*  Index into query string          */
        string_count,                   /*  Size of string table             */
        string_nbr;                     /*  Index into string table          */

    ASSERT (original_query);

    if (*original_query == '&')         /*  Skip leading & if present        */
        original_query++;

    if ((query = mem_strdup (original_query)) == NULL)
        return (NULL);                  /*  Could not allocate memory        */

    /*  Break query string at & and ; delimiters and count strt size         */
    string_count = 1;                   /*  Last string has no delimiter     */
    for (char_nbr = 0; original_query [char_nbr]; char_nbr++)
        if (query [char_nbr] == '&' || query [char_nbr] == ';')
          {
            query [char_nbr] = '\0';
            string_count++;
          }

    /*  Allocate the array of pointers with one slot for the final NULL      */
    if ((strings = mem_alloc (sizeof (char *) * (string_count + 1))) == NULL)
      {
        mem_free (query);
        return (NULL);                  /*  Could not allocate memory        */
      }

    /*  Query string now consists of a series of substrings, each ending in
     *  a null character.  We have to unescape each substring, which we do
     *  in-place: the unescaped string is never larger than the original
     *  string.
     */
    query_ptr = query;
    for (string_nbr = 0; string_nbr < string_count; string_nbr++)
      {
        /*  Unescape next query string component                             */
        query_next = query_ptr + strlen (query_ptr) + 1;
        http_unescape (query_ptr, NULL);

        /*  Allocate space for "name=value" plus final null char             */
        strings [string_nbr] = mem_strdup (query_ptr);
        query_ptr = query_next;
      }
    strings [string_nbr] = NULL;        /*  Store final null pointer         */
    mem_free (query);                   /*  Release temporary memory         */
    return (strings);
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_query2symb

    Synopsis: Parses a HTTP query string, and populates a symbol table with
    the resulting field values.  The query string is assumed to be escaped,
    so http_unescape() is always applied to the query string.  Within the
    query string, field=value pairs are delimited by & or ;.  Returns a
    SYMTAB pointer to the new table.  If there was not enough memory to
    allocate the table, returns NULL.
    ---------------------------------------------------------------------[>]-*/

SYMTAB *
http_query2symb (
    const char *query)
{
    char
        **strings;                      /*  Formatted string array           */
    SYMTAB
        *symtab;                        /*  Returned symbol table            */

    strings = http_query2strt (query);
    if (strings)
      {
        symtab = strt2symb (strings);
        strtfree (strings);
        return (symtab);
      }
    else
        return (NULL);                  /*  Couldn't create string table     */
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_query2descr

    Synopsis: Parses a HTTP query string, and returns the values as a DESCR
    block, composed of null-delimited strings with an empty string at the
    end.  See strt2descr() and http_query2symb() for more details.  Returns
    the address of the allocated descriptor, or NULL if there was not
    enough memory.
    ---------------------------------------------------------------------[>]-*/

DESCR *
http_query2descr (
    const char *query)
{
    char
        **strings;                      /*  Formatted string array           */
    DESCR
        *descr;                         /*  Returned descriptor              */

    strings = http_query2strt (query);
    if (strings)
      {
        descr = strt2descr (strings);
        strtfree (strings);
        return (descr);
      }
    else
        return (NULL);                  /*  Couldn't create string table     */
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_encode_meta

    Synopsis: Translates special characters into HTML/SGML metacharacters.
    The input buffer is not modified; you supply an output buffer and specify
    the maximum size of this buffer.  The input buffer must end in a null.
    After calling, the input pointer is set to the character after the last
    encoded character.  Returns the final size of the translated data
    excluding the final null byte.  If the resulting output data would be
    too long, translations stops.  If html is TRUE then the metacharacters
    amp, lt, gt and quot are not translated - this allows you to encode
    markup characters within HTML; otherwise they are translated and the
    output doesn't look like HTML.
    ---------------------------------------------------------------------[>]-*/

size_t
http_encode_meta (
    char    *output,
    char    **input,
    size_t  outmax,
    Bool    html)
{
    size_t
        space_left,                     /*  Space left in destination        */
        length;
    char
        *dest;                          /*  Pointer to result string         */

    ASSERT (input);
    ASSERT (*input);
    ASSERT (output);

    if (outmax == 0)                    /*  Special case for zero space      */
        return (0);

    space_left = outmax - 1;            /*  Allow for final null byte        */
    dest = output;
    while (**input && space_left > 0)
      {
        length = encode_meta_char (dest, **input, space_left, html);
        if (length)
          {
            space_left -= length;
            dest += length;
            (*input) ++;
          }
        else
            break;
      }
    *dest = '\0';
    return ((size_t) (dest - output));
}


/*  ---------------------------------------------------------------------[<]-
    Function: encode_meta_char

    Synopsis: Translates one character into HTML/SGML metacharacters.  You
    supply an output buffer and specify the maximum size of this buffer.
    Returns the final size of the translated data excluding the final null
    byte.  If the resulting data is too long, translation does not occur
    and the returned value is zero.  If html is TRUE then the metacharacters
    cr, amp, lt, gt and quot are not translated - this allows you to encode
    accented characters within HTML; otherwise they are translated and the
    output doesn't look like HTML.
    ---------------------------------------------------------------------[>]-*/

size_t
encode_meta_char (
    char  *output,
    char   code,
    size_t outmax,
    Bool   html)
{
    static char
        *meta [256];                    /*  Metacharacter translation table  */
    static Bool
        first_time = TRUE;              /*  First time flag                  */
    size_t
        length;                         /*  Length of translation            */
    char
        *meta_char,                     /*  Pointer through metachar string  */
        buffer [10];                    /*  Buffer for conversions           */

    /*  Initialise translation table first time through                      */
    if (first_time)
      {
        first_time = FALSE;
        memset (meta, 0, sizeof (meta));

#if (defined (__UNIX__) || defined (__WINDOWS__))
        /*  UNIX and Windows generally use ISO-8859-1 (Latin-1)              */
        meta [0x91] = "lsquo";
        meta [0x92] = "rsquo";
        meta [0xA1] = "iexcl";
        meta [0xA2] = "cent";
        meta [0xA3] = "pound";
        meta [0xA4] = "curren";
        meta [0xA5] = "yen";
        meta [0xA6] = "brvbar";
        meta [0xA7] = "sect";
        meta [0xA8] = "uml";
        meta [0xA9] = "copy";
        meta [0xAA] = "ordf";
        meta [0xAB] = "laquo";
        meta [0xAC] = "not";
        meta [0xAD] = "shy";
        meta [0xAE] = "reg";
        meta [0xAF] = "macr";
        meta [0xB0] = "deg";
        meta [0xB1] = "plusmn";
        meta [0xB2] = "sup2";
        meta [0xB3] = "sup3";
        meta [0xB4] = "acute";
        meta [0xB5] = "micro";
        meta [0xB6] = "para";
        meta [0xB7] = "middot";
        meta [0xB8] = "cedil";
        meta [0xB9] = "sup1";
        meta [0xBA] = "ordm";
        meta [0xBB] = "raquo";
        meta [0xBC] = "frac14";
        meta [0xBD] = "frac12";
        meta [0xBE] = "frac34";
        meta [0xBF] = "iquest";
        meta [0xC0] = "Agrave";
        meta [0xC1] = "Aacute";
        meta [0xC2] = "Acirc";
        meta [0xC3] = "Atilde";
        meta [0xC4] = "Auml";
        meta [0xC5] = "Aring";
        meta [0xC6] = "AElig";
        meta [0xC7] = "Ccedil";
        meta [0xC8] = "Egrave";
        meta [0xC9] = "Eacute";
        meta [0xCA] = "Ecirc";
        meta [0xCB] = "Euml";
        meta [0xCC] = "Igrave";
        meta [0xCD] = "Iacute";
        meta [0xCE] = "Icirc";
        meta [0xCF] = "Iuml";
        meta [0xD0] = "ETH";
        meta [0xD1] = "Ntilde";
        meta [0xD2] = "Ograve";
        meta [0xD3] = "Oacute";
        meta [0xD4] = "Ocirc";
        meta [0xD5] = "Otilde";
        meta [0xD6] = "Ouml";
        meta [0xD7] = "times";
        meta [0xD8] = "Oslash";
        meta [0xD9] = "Ugrave";
        meta [0xDA] = "Uacute";
        meta [0xDB] = "Ucirc";
        meta [0xDC] = "Uuml";
        meta [0xDD] = "Yacute";
        meta [0xDE] = "THORN";
        meta [0xDF] = "szlig";
        meta [0xE0] = "agrave";
        meta [0xE1] = "aacute";
        meta [0xE2] = "acirc";
        meta [0xE3] = "atilde";
        meta [0xE4] = "auml";
        meta [0xE5] = "aring";
        meta [0xE6] = "aelig";
        meta [0xE7] = "ccedil";
        meta [0xE8] = "egrave";
        meta [0xE9] = "eacute";
        meta [0xEA] = "ecirc";
        meta [0xEB] = "euml";
        meta [0xEC] = "igrave";
        meta [0xED] = "iacute";
        meta [0xEE] = "icirc";
        meta [0xEF] = "iuml";
        meta [0xF0] = "eth";
        meta [0xF1] = "ntilde";
        meta [0xF2] = "ograve";
        meta [0xF3] = "oacute";
        meta [0xF4] = "ocirc";
        meta [0xF5] = "otilde";
        meta [0xF6] = "ouml";
        meta [0xF7] = "divide";
        meta [0xF8] = "oslash";
        meta [0xF9] = "ugrave";
        meta [0xFA] = "uacute";
        meta [0xFB] = "ucirc";
        meta [0xFC] = "uuml";
        meta [0xFD] = "yacute";
        meta [0xFE] = "thorn";
        meta [0xFF] = "yuml";

#elif (defined (__MSDOS__))
        /*  DOS generally uses the PC-1 alphabet                             */
        meta [0x80] = "Ccedil";
        meta [0x81] = "uuml";
        meta [0x82] = "eacute";
        meta [0x83] = "acirc";
        meta [0x84] = "auml";
        meta [0x85] = "agrave";
        meta [0x86] = "aring";
        meta [0x87] = "ccedil";
        meta [0x88] = "ecirc";
        meta [0x89] = "euml";
        meta [0x8B] = "iuml";
        meta [0x8C] = "icirc";
        meta [0x8D] = "igrave";
        meta [0x8E] = "Auml";
        meta [0x8F] = "Aring";
        meta [0x90] = "Eacute";
        meta [0x91] = "aelig";
        meta [0x92] = "AElig";
        meta [0x93] = "ocirc";
        meta [0x94] = "ouml";
        meta [0x95] = "ograve";
        meta [0x96] = "ucirc";
        meta [0x97] = "ugrave";
        meta [0x98] = "yuml";
        meta [0x99] = "Ouml";
        meta [0x9A] = "Uuml";
        meta [0x9B] = "oslash";
        meta [0x9C] = "pound";
        meta [0x9D] = "Oslash";
        meta [0x9E] = "times";
        meta [0xA0] = "aacute";
        meta [0xA1] = "iacute";
        meta [0xA2] = "otilde";
        meta [0xA3] = "uacute";
        meta [0xA4] = "ntilde";
        meta [0xA5] = "Ntilde";
        meta [0xA6] = "ordf";
        meta [0xA7] = "ordm";
        meta [0xA8] = "iquest";
        meta [0xA9] = "reg";
        meta [0xAA] = "not";
        meta [0xAB] = "frac14";
        meta [0xAC] = "frac12";
        meta [0xAD] = "iexcl";
        meta [0xAE] = "laquo";
        meta [0xAF] = "raquo";
        meta [0xB0] = "shy";
        meta [0xB5] = "Aacute";
        meta [0xB6] = "Acirc";
        meta [0xB7] = "Agrave";
        meta [0xB8] = "copy";
        meta [0xC6] = "atilde";
        meta [0xC7] = "Atilde";
        meta [0xCA] = "egrave";
        meta [0xCF] = "curren";
        meta [0xD1] = "ETH";
        meta [0xD2] = "Ecirc";
        meta [0xD3] = "Euml";
        meta [0xD4] = "Egrave";
        meta [0xD6] = "Iacute";
        meta [0xD7] = "Icirc";
        meta [0xD8] = "Iuml";
        meta [0xDD] = "brvbar";
        meta [0xDE] = "Igrave";
        meta [0xE0] = "Oacute";
        meta [0xE1] = "szlig";
        meta [0xE2] = "Ocirc";
        meta [0xE3] = "Ograve";
        meta [0xE5] = "Otilde";
        meta [0xE6] = "THORN";
        meta [0xE9] = "Uacute";
        meta [0xEA] = "Ucirc";
        meta [0xEB] = "Ugrave";
        meta [0xEC] = "yacute";
        meta [0xED] = "Yacute";
        meta [0xEE] = "macr";
        meta [0xEF] = "acute";
        meta [0xF1] = "plusmn";
        meta [0xF3] = "frac34";
        meta [0xF4] = "para";
        meta [0xF5] = "sect";
        meta [0xF6] = "divide";
        meta [0xF8] = "deg";
        meta [0xFA] = "middot";
        meta [0xFB] = "sup1";
        meta [0xFC] = "sup3";
        meta [0xFD] = "sup2";
#endif
      }

    meta_char = meta [(int) code & 255];
    if (meta_char == 0)
      {
        if (html)
          {
            output [0] = code;
            return (1);
          }
        else
          {
            switch (code)
              {
                case  '&':
                    meta_char = "amp";
                    break;
                case  '<':
                    meta_char = "lt";
                    break;
                case  '>':
                    meta_char = "gt";
                    break;
                case  '"':
                    meta_char = "quot";
                    break;
                default:
                    output [0] = code;
                    return (1);
              }
          }
      }
    snprintf (buffer, sizeof (buffer), "&%s;", meta_char);
    length = strlen (buffer);
    if (length < outmax)
      {
        strncpy (output, buffer, outmax);
        return length;
      }
    else
      {
        output [0] = 0;
        return (0);
      }
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_decode_meta

    Synopsis: Translates special characters from HTML/SGML
    metacharacters.  The input buffer is not modified; you supply an
    output buffer and specify the maximum size of this buffer.  The input
    buffer must end in a null.  The two buffers may be the same, since
    decoding reduces the size of the data.  Returns the final size of the
    translated data excluding the final null byte.  If the resulting data
    is too long, it is brutally truncated.  Invalid meta-characters are
    left as-is.  Normally the input pointer points to the final null;
    however if the input string runs out in the middle of a
    meta-character, the input pointer is left pointing at the start of
    that meta-character (the '&').  If the two buffers are the same, it
    may be the case that this character has been overwritten with the
    terminating null.
    ---------------------------------------------------------------------[>]-*/

size_t
http_decode_meta (
    char  *output,
    char **input,
    size_t outmax)
{
    char
        *dest,                          /*  Pointer to result string         */
        *end,                           /*  Next character after meta-char   */
        code;                           /*  Decoded metachar string          */
    size_t
        space_left;                     /*  Space left in destination        */

    ASSERT (input);
    ASSERT (*input);
    ASSERT (output);

    if (outmax == 0)                    /*  Special case for zero space      */
        return (0);

    space_left = outmax - 1;            /*  Allow for final null byte        */
    dest = output;
    for (; **input; (*input)++)
      {
        if (**input == '&')
          {
            end = strchr (*input, ';');
            if (end)
              {
                code = decode_meta_charn ((*input) + 1, end - (*input) - 1);
                if (code)
                    *input = end;       /*  Skip past decoded metachar       */
                else
                    code = **input;     /*  Ignore the &, no valid metachar  */
              }
            else                        /*  The string ends before the ';'   */
            if (strlen (*input) > 10)
                code = **input;         /*  Ignore the &, no valid metachar  */
            else
                break;
          }
        else
            code = **input;

        if (space_left > 0)
          {
            *dest++ = code;
            space_left --;
          }
      }
    *dest = '\0';
    return ((size_t) (dest - output));
}


/*  ---------------------------------------------------------------------[<]-
    Function: decode_meta_charn

    Synopsis: Decodes a single meta-character (the piece from the character
    after the '&' up to but not including the ';'.  If the meta-character
    is valid, returns the character; otherwise returns zero.  Decodes both
    named and numeric meta-characters.  Use the macro decode_meta_char if
    the input has a terminating zero.
    ---------------------------------------------------------------------[>]-*/

char
decode_meta_charn (const char *input, size_t length)
{
#define META_COUNT 102

    static char
        *meta [META_COUNT];             /*  Metacharacter translation tables */
    static byte
        code [META_COUNT];
    static Bool
        first_time = TRUE;              /*  First time flag                  */
    int
        char_index,                     /*  Index into translation table     */
        min,                            /*  Pointers for binary chop         */
        max,
        cmp,
        num;

    /*  Initialise translation table first time through                      */
    if (first_time)
      {
        first_time = FALSE;

        /*  The meta-characters must be in alphabetical order so we can use  */
        /*  a binary chop.                                                   */
#if (defined (__UNIX__) || defined (__WINDOWS__))
        /*  UNIX and Windows generally use ISO-8859-1 (Latin-1)              */
        code [  0] = 0xC6;  meta [  0] = "AElig";
        code [  1] = 0xC1;  meta [  1] = "Aacute";
        code [  2] = 0xC2;  meta [  2] = "Acirc";
        code [  3] = 0xC0;  meta [  3] = "Agrave";
        code [  4] = 0xC5;  meta [  4] = "Aring";
        code [  5] = 0xC3;  meta [  5] = "Atilde";
        code [  6] = 0xC4;  meta [  6] = "Auml";
        code [  7] = 0xC7;  meta [  7] = "Ccedil";
        code [  8] = 0xD0;  meta [  8] = "ETH";
        code [  9] = 0xC9;  meta [  9] = "Eacute";
        code [ 10] = 0xCA;  meta [ 10] = "Ecirc";
        code [ 11] = 0xC8;  meta [ 11] = "Egrave";
        code [ 12] = 0xCB;  meta [ 12] = "Euml";
        code [ 13] = 0xCD;  meta [ 13] = "Iacute";
        code [ 14] = 0xCE;  meta [ 14] = "Icirc";
        code [ 15] = 0xCC;  meta [ 15] = "Igrave";
        code [ 16] = 0xCF;  meta [ 16] = "Iuml";
        code [ 17] = 0xD1;  meta [ 17] = "Ntilde";
        code [ 18] = 0xD3;  meta [ 18] = "Oacute";
        code [ 19] = 0xD4;  meta [ 19] = "Ocirc";
        code [ 20] = 0xD2;  meta [ 20] = "Ograve";
        code [ 21] = 0xD8;  meta [ 21] = "Oslash";
        code [ 22] = 0xD5;  meta [ 22] = "Otilde";
        code [ 23] = 0xD6;  meta [ 23] = "Ouml";
        code [ 24] = 0xDE;  meta [ 24] = "THORN";
        code [ 25] = 0xDA;  meta [ 25] = "Uacute";
        code [ 26] = 0xDB;  meta [ 26] = "Ucirc";
        code [ 27] = 0xD9;  meta [ 27] = "Ugrave";
        code [ 28] = 0xDC;  meta [ 28] = "Uuml";
        code [ 29] = 0xDD;  meta [ 29] = "Yacute";
        code [ 30] = 0xE1;  meta [ 30] = "aacute";
        code [ 31] = 0xE2;  meta [ 31] = "acirc";
        code [ 32] = 0xB4;  meta [ 32] = "acute";
        code [ 33] = 0xE6;  meta [ 33] = "aelig";
        code [ 34] = 0xE0;  meta [ 34] = "agrave";
        code [ 35] = '&';   meta [ 35] = "amp";
        code [ 36] = 0xE5;  meta [ 36] = "aring";
        code [ 37] = 0xE3;  meta [ 37] = "atilde";
        code [ 38] = 0xE4;  meta [ 38] = "auml";
        code [ 39] = 0xA6;  meta [ 39] = "brvbar";
        code [ 40] = 0xE7;  meta [ 40] = "ccedil";
        code [ 41] = 0xB8;  meta [ 41] = "cedil";
        code [ 42] = 0xA2;  meta [ 42] = "cent";
        code [ 43] = 0xA9;  meta [ 43] = "copy";
        code [ 44] = 0xA4;  meta [ 44] = "curren";
        code [ 45] = 0xB0;  meta [ 45] = "deg";
        code [ 46] = 0xF7;  meta [ 46] = "divide";
        code [ 47] = 0xE9;  meta [ 47] = "eacute";
        code [ 48] = 0xEA;  meta [ 48] = "ecirc";
        code [ 49] = 0xE8;  meta [ 49] = "egrave";
        code [ 50] = 0xF0;  meta [ 50] = "eth";
        code [ 51] = 0xEB;  meta [ 51] = "euml";
        code [ 52] = 0xBD;  meta [ 52] = "frac12";
        code [ 53] = 0xBC;  meta [ 53] = "frac14";
        code [ 54] = 0xBE;  meta [ 54] = "frac34";
        code [ 55] = '>';   meta [ 55] = "gt";
        code [ 56] = 0xED;  meta [ 56] = "iacute";
        code [ 57] = 0xEE;  meta [ 57] = "icirc";
        code [ 58] = 0xA1;  meta [ 58] = "iexcl";
        code [ 59] = 0xEC;  meta [ 59] = "igrave";
        code [ 60] = 0xBF;  meta [ 60] = "iquest";
        code [ 61] = 0xEF;  meta [ 61] = "iuml";
        code [ 62] = 0xAB;  meta [ 62] = "laquo";
        code [ 63] = 0x91;  meta [ 63] = "lsquo";
        code [ 64] = '<';   meta [ 64] = "lt";
        code [ 65] = 0xAF;  meta [ 65] = "macr";
        code [ 66] = 0xB5;  meta [ 66] = "micro";
        code [ 67] = 0xB7;  meta [ 67] = "middot";
        code [ 68] = ' ';   meta [ 68] = "nbsp";
        code [ 69] = 0xAC;  meta [ 69] = "not";
        code [ 70] = 0xF1;  meta [ 70] = "ntilde";
        code [ 71] = 0xF3;  meta [ 71] = "oacute";
        code [ 72] = 0xF4;  meta [ 72] = "ocirc";
        code [ 73] = 0xF2;  meta [ 73] = "ograve";
        code [ 74] = 0xAA;  meta [ 74] = "ordf";
        code [ 75] = 0xBA;  meta [ 75] = "ordm";
        code [ 76] = 0xF8;  meta [ 76] = "oslash";
        code [ 77] = 0xF5;  meta [ 77] = "otilde";
        code [ 78] = 0xF6;  meta [ 78] = "ouml";
        code [ 79] = 0xB6;  meta [ 79] = "para";
        code [ 80] = 0xB1;  meta [ 80] = "plusmn";
        code [ 81] = 0xA3;  meta [ 81] = "pound";
        code [ 82] = '"';   meta [ 82] = "quot";
        code [ 83] = 0xBB;  meta [ 83] = "raquo";
        code [ 84] = 0xAE;  meta [ 84] = "reg";
        code [ 85] = 0x92;  meta [ 85] = "rsquo";
        code [ 86] = 0xA7;  meta [ 86] = "sect";
        code [ 87] = 0xAD;  meta [ 87] = "shy";
        code [ 88] = 0xB9;  meta [ 88] = "sup1";
        code [ 89] = 0xB2;  meta [ 89] = "sup2";
        code [ 90] = 0xB3;  meta [ 90] = "sup3";
        code [ 91] = 0xDF;  meta [ 91] = "szlig";
        code [ 92] = 0xFE;  meta [ 92] = "thorn";
        code [ 93] = 0xD7;  meta [ 93] = "times";
        code [ 94] = 0xFA;  meta [ 94] = "uacute";
        code [ 95] = 0xFB;  meta [ 95] = "ucirc";
        code [ 96] = 0xF9;  meta [ 96] = "ugrave";
        code [ 97] = 0xA8;  meta [ 97] = "uml";
        code [ 98] = 0xFC;  meta [ 98] = "uuml";
        code [ 99] = 0xFD;  meta [ 99] = "yacute";
        code [100] = 0xA5;  meta [100] = "yen";
        code [101] = 0xFF;  meta [101] = "yuml";
#elif (defined (__MSDOS__))
        code [  0] = 0x92;  meta [  0] = "AElig";
        code [  1] = 0xB5;  meta [  1] = "Aacute";
        code [  2] = 0xB6;  meta [  2] = "Acirc";
        code [  3] = 0xB7;  meta [  3] = "Agrave";
        code [  4] = 0x8F;  meta [  4] = "Aring";
        code [  5] = 0xC7;  meta [  5] = "Atilde";
        code [  6] = 0x8E;  meta [  6] = "Auml";
        code [  7] = 0x80;  meta [  7] = "Ccedil";
        code [  8] = 0xD1;  meta [  8] = "ETH";
        code [  9] = 0x90;  meta [  9] = "Eacute";
        code [ 10] = 0xD2;  meta [ 10] = "Ecirc";
        code [ 11] = 0xD4;  meta [ 11] = "Egrave";
        code [ 12] = 0xD3;  meta [ 12] = "Euml";
        code [ 13] = 0xD6;  meta [ 13] = "Iacute";
        code [ 14] = 0xD7;  meta [ 14] = "Icirc";
        code [ 15] = 0xDE;  meta [ 15] = "Igrave";
        code [ 16] = 0xD8;  meta [ 16] = "Iuml";
        code [ 17] = 0xA5;  meta [ 17] = "Ntilde";
        code [ 18] = 0xE0;  meta [ 18] = "Oacute";
        code [ 19] = 0xE2;  meta [ 19] = "Ocirc";
        code [ 20] = 0xE3;  meta [ 20] = "Ograve";
        code [ 21] = 0x9D;  meta [ 21] = "Oslash";
        code [ 22] = 0xE5;  meta [ 22] = "Otilde";
        code [ 23] = 0x99;  meta [ 23] = "Ouml";
        code [ 24] = 0xE6;  meta [ 24] = "THORN";
        code [ 25] = 0xE9;  meta [ 25] = "Uacute";
        code [ 26] = 0xEA;  meta [ 26] = "Ucirc";
        code [ 27] = 0xEB;  meta [ 27] = "Ugrave";
        code [ 28] = 0x9A;  meta [ 28] = "Uuml";
        code [ 29] = 0xED;  meta [ 29] = "Yacute";
        code [ 30] = 0xA0;  meta [ 30] = "aacute";
        code [ 31] = 0x83;  meta [ 31] = "acirc";
        code [ 32] = 0xEF;  meta [ 32] = "acute";
        code [ 33] = 0x91;  meta [ 33] = "aelig";
        code [ 34] = 0x85;  meta [ 34] = "agrave";
        code [ 35] = '&';   meta [ 35] = "amp";
        code [ 36] = 0x86;  meta [ 36] = "aring";
        code [ 37] = 0xC6;  meta [ 37] = "atilde";
        code [ 38] = 0x84;  meta [ 38] = "auml";
        code [ 39] = 0xDD;  meta [ 39] = "brvbar";
        code [ 40] = 0x87;  meta [ 40] = "ccedil";
        code [ 41] = 0xB8;  meta [ 41] = "cedil";
        code [ 42] = 0xA2;  meta [ 42] = "cent";
        code [ 43] = 0xB8;  meta [ 43] = "copy";
        code [ 44] = 0xCF;  meta [ 44] = "curren";
        code [ 45] = 0xF8;  meta [ 45] = "deg";
        code [ 46] = 0xF6;  meta [ 46] = "divide";
        code [ 47] = 0x82;  meta [ 47] = "eacute";
        code [ 48] = 0x88;  meta [ 48] = "ecirc";
        code [ 49] = 0xCA;  meta [ 49] = "egrave";
        code [ 50] = 0xF0;  meta [ 50] = "eth";
        code [ 51] = 0x89;  meta [ 51] = "euml";
        code [ 52] = 0xAC;  meta [ 52] = "frac12";
        code [ 53] = 0xAB;  meta [ 53] = "frac14";
        code [ 54] = 0xF3;  meta [ 54] = "frac34";
        code [ 55] = '>';   meta [ 55] = "gt";
        code [ 56] = 0xA1;  meta [ 56] = "iacute";
        code [ 57] = 0x8C;  meta [ 57] = "icirc";
        code [ 58] = 0xAD;  meta [ 58] = "iexcl";
        code [ 59] = 0x8D;  meta [ 59] = "igrave";
        code [ 60] = 0xA8;  meta [ 60] = "iquest";
        code [ 61] = 0x8B;  meta [ 61] = "iuml";
        code [ 62] = 0xAE;  meta [ 62] = "laquo";
        code [ 63] = 0x91;  meta [ 63] = "lsquo";
        code [ 64] = '<';   meta [ 64] = "lt";
        code [ 65] = 0xEE;  meta [ 65] = "macr";
        code [ 66] = 0xB5;  meta [ 66] = "micro";
        code [ 67] = 0xFA;  meta [ 67] = "middot";
        code [ 68] = ' ';   meta [ 68] = "nbsp";
        code [ 69] = 0xAA;  meta [ 69] = "not";
        code [ 70] = 0xA4;  meta [ 70] = "ntilde";
        code [ 71] = 0xF3;  meta [ 71] = "oacute";
        code [ 72] = 0x93;  meta [ 72] = "ocirc";
        code [ 73] = 0x95;  meta [ 73] = "ograve";
        code [ 74] = 0xA6;  meta [ 74] = "ordf";
        code [ 75] = 0xA7;  meta [ 75] = "ordm";
        code [ 76] = 0x9B;  meta [ 76] = "oslash";
        code [ 77] = 0xA2;  meta [ 77] = "otilde";
        code [ 78] = 0x94;  meta [ 78] = "ouml";
        code [ 79] = 0xF4;  meta [ 79] = "para";
        code [ 80] = 0xF1;  meta [ 80] = "plusmn";
        code [ 81] = 0x9C;  meta [ 81] = "pound";
        code [ 82] = '"';   meta [ 82] = "quot";
        code [ 83] = 0xAF;  meta [ 83] = "raquo";
        code [ 84] = 0xA9;  meta [ 84] = "reg";
        code [ 85] = 0x92;  meta [ 85] = "rsquo";
        code [ 86] = 0xF5;  meta [ 86] = "sect";
        code [ 87] = 0xB0;  meta [ 87] = "shy";
        code [ 88] = 0xFB;  meta [ 88] = "sup1";
        code [ 89] = 0xFD;  meta [ 89] = "sup2";
        code [ 90] = 0xFC;  meta [ 90] = "sup3";
        code [ 91] = 0xE1;  meta [ 91] = "szlig";
        code [ 92] = 0xFE;  meta [ 92] = "thorn";
        code [ 93] = 0x9E;  meta [ 93] = "times";
        code [ 94] = 0xA3;  meta [ 94] = "uacute";
        code [ 95] = 0x96;  meta [ 95] = "ucirc";
        code [ 96] = 0x97;  meta [ 96] = "ugrave";
        code [ 97] = 0xA8;  meta [ 97] = "uml";
        code [ 98] = 0x81;  meta [ 98] = "uuml";
        code [ 99] = 0xEC;  meta [ 99] = "yacute";
        code [100] = 0xA5;  meta [100] = "yen";
        code [101] = 0x98;  meta [101] = "yuml";
#endif
      }

    if (*input == '#')    /*  Numeric translation  */
      {
        input++;
        num = 0;
        if (*input == 'x')  /*  Hex  */
          {
            input++;
            num = decode_hex (&input, 0);
            input++;
          }
        else
            FOREVER
              {
                if ((*input >= '0') && (*input <= '9'))
                    num = (num * 10) + *input - '0';
                else
                    break;

                input++;
              }

        if (*input != ';')
            num = 0;

        return num;
      }
    else  /*  Lookup meta-character  */
      {
        min = 0;
        max = META_COUNT;
        while (max > min)
          {
            char_index = (max + min) / 2;
            cmp = strncmp (input, meta [char_index], length);
            if (cmp == 0)
                return code [char_index];

            if (cmp > 0)
                min = char_index + 1;
            else
                max = char_index;
          }

        return 0;
      }
}


/*  ---------------------------------------------------------------------[<]-
    Function: cgi_parse_query_vars

    Synopsis: Parses a CGI query string and loads the resulting variables
    into an existing symbol table, optionally prefixing each name with a
    string.  Returns the number of variables loaded.  The prefix can be
    NULL or empty if not required.
    ---------------------------------------------------------------------[>]-*/

int
cgi_parse_query_vars (
    SYMTAB *symtab,
    const char *query,
    const char *prefix)
{
    char
        *query_var,                     /*  Query variable name              */
        **query_vars,                   /*  Query as string table            */
        *equals;                        /*  Equal sign in variable           */
    int
        string_nbr,                     /*  Index into string table          */
        variables = 0;                  /*  Number of variables loaded       */

    ASSERT (symtab);
    if ((query_vars = http_query2strt (query)) == NULL)
        return (0);                     /*  Not enough memory                */

    for (string_nbr = 0; query_vars [string_nbr]; string_nbr++)
      {
        equals = strchr (query_vars [string_nbr], '=');
        if (equals)
          {
            *equals = '\0';             /*  Cut into two strings             */
            if (prefix == NULL)
                prefix = "";            /*  Make safe for xstrcpy()          */
            query_var = xstrcpy (NULL, prefix, query_vars [string_nbr], NULL);
            sym_assume_symbol (symtab, query_var, equals + 1);
            mem_strfree (&query_var);
            *equals = '=';              /*  Restore previous state           */
            variables++;                /*  Count this variable              */
          }
      }
    strtfree (query_vars);
    return (variables);
}


/*  ---------------------------------------------------------------------[<]-
    Function: cgi_parse_file_vars

    Synopsis: Parses a CGI query string stored in a file, and loads the
    resulting variables into an existing symbol table, optionally
    prefixing each name with a string.  Returns the number of variables
    loaded.  The prefix can be NULL or empty if not required.  The
    file data is assumed to be escaped (see http_escape()); the data
    should not contain line breaks, spaces, or other unescaped chars.
    The file should already have been opened: a typical use for this
    function is to parse the values supplied in stdin.  The maximum size
    for the file is CGI_QUERY_FILE_MAX characters.
    ---------------------------------------------------------------------[>]-*/

int
cgi_parse_file_vars (
    SYMTAB *symtab,
    FILE   *file,
    const char *prefix,
    size_t size)
{
    /*  Maximum size of a stream of HTTP query data coming from a file       */
#   define CGI_QUERY_FILE_MAX  65535U
    char
        *query;                         /*  Data loaded from file            */
    size_t
        read_size;                      /*  Amount of data read from file    */
    int
        variables = 0;                  /*  Number of variables loaded       */

    ASSERT (file);
    ASSERT (symtab);
    ASSERT (size <= CGI_QUERY_FILE_MAX);

    if ((query = mem_alloc (size + 1)) == NULL)
        return (0);

    read_size = fread (query, 1, size, file);
    query [read_size] = '\0';
    variables = cgi_parse_query_vars (symtab, query, prefix);
    mem_free (query);
    return (variables);
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_multipart_decode

    Synopsis: Parses a multipart-encoded file (as received by a web server as
    POST data) and returns a HTTP-encoded string containing the field data,
    in the format: "name=value&name=value&name=value...".  For each field that
    refers to an uploaded file (INPUT field with type FILE), creates a
    temporary file holding the data.  The name of this temporary file is put
    into a generated variable, whose name is built by using the local format
    string (ex: '%s_tmp').  The actual uploaded file is stored in
    a temporary file whose name is generated by the SFL get_tmp_file_name()
    function.  So, data for a file upload field called "doc" will be stored in
    a temporary file called (eg) "temp1234.tmp", and a field "doc_tmp" will be
    added, with the value "temp1234.tmp".  The HTTP-encoded string is returned
    as a DESCR block, which you can decode using http_query2strt(), passing the
    descriptor data.  You must free the descriptor using mem_free() when you're
    finished with it.
    ---------------------------------------------------------------------[>]-*/

DESCR *
http_multipart_decode (const char *mime_file, const char *store_path,
                       const char *local_format)
{
    FILE
        *f_source,
        *f_tmp = NULL;
    char
        *tmp_name = NULL,
        *p_head,
        *p_data,
        *p_next,
        *buffer;
    int
        offset,
        read_size,
        rest_read_size;
    static char
        separator [80 + 1];
    static int
        sep_size;
    SYMTAB
        *table,
        *header_tab;
    qbyte
        tmp_index = 1;
    DESCR
        *descr = NULL;

    ASSERT (local_format);

    if (strstr (local_format, "%s") == NULL)
        return (NULL);

    buffer = mem_alloc (MULTI_BUFFER_SIZE + REST_BUFFER_SIZE + 1);
    if (buffer == NULL)
        return (NULL);

    table = sym_create_table ();
    if (table == NULL)
      {
        mem_free (buffer);
        return (NULL);
      }

    header_tab = sym_create_table ();
    if (header_tab == NULL)
      {
        mem_free (buffer);
        sym_delete_table (table);
        return (NULL);
      }

    f_source = fopen (mime_file, "rb");
    if (f_source == NULL)
      {
        mem_free (buffer);
        sym_delete_table (table);
        sym_delete_table (header_tab);
        return (NULL);
      }

    memset (separator, 0, sizeof (separator));
    separator [0] = 0x0D;
    separator [1] = 0x0A;
    ASSERT (fgets (&separator [2], 78, f_source));
    strconvch (&separator [2] , '\r', '\0');
    strconvch (&separator [2] , '\n', '\0');
    sep_size  = strlen (separator);

    read_size = fread (buffer, 1, MULTI_BUFFER_SIZE, f_source);
    p_next = buffer;
    while (read_size > 0)
      {
        sym_empty_table (header_tab);
        p_head = p_next;
        p_data = (char *) memfind ((byte *) p_head,
                          MULTI_BUFFER_SIZE + REST_BUFFER_SIZE - (p_head - buffer),
                          (byte *) "\r\n\r\n", 4, FALSE);
        if (p_data)
          {
            *p_data = '\0';
            p_data += 4;
          }
        if (p_head)
          {
            multipart_decode_header (p_head, header_tab);
            if (sym_lookup_symbol (header_tab, "filename") != NULL)
              {
                if (f_tmp != NULL)
                  {
                    ASSERT (tmp_name != NULL);
                    fclose (f_tmp);
                    f_tmp = NULL;
                    if (get_file_size (tmp_name) == 0)
                        file_delete (tmp_name);
                  }
                if (tmp_name != NULL)
                    mem_free (tmp_name);
                tmp_name = get_tmp_file_name (store_path, &tmp_index, "tmp");
                f_tmp = fopen (tmp_name, "wb");
              }
          }
        p_next = (char *) memfind ((byte *) p_data,
                          /*  read_size                                      */
                          MULTI_BUFFER_SIZE + REST_BUFFER_SIZE - (p_data - buffer),
                          (byte *) separator, sep_size, FALSE);

        if (p_next != NULL)
          {
            /*  The next attribute-value pair was found                      */
            *p_next = '\0';
            save_multipart_header (table, header_tab, p_data, tmp_name,
                                   local_format);
            if (f_tmp)
              {
                /* Special case:
                   The next attribute value is found AND "f_tmp" is opened.
                   That means that the download file is completely in the
                   buffer and so we can save it to disk.
                 */
                ASSERT (fwrite (p_data, p_next - p_data, 1, f_tmp));
                fclose (f_tmp);
                f_tmp = NULL;
                if (get_file_size (tmp_name) == 0)
                    file_delete (tmp_name);

                /* Now we need to check the buffer.  If the buffer is full
                   then we must read the rest of "f_source". The rest will
                   be written into the the buffer at REST_BUFFER_SIZE.
                 */
                if (read_size == MULTI_BUFFER_SIZE)
                    rest_read_size = fread (&buffer [MULTI_BUFFER_SIZE], 1,
                                            REST_BUFFER_SIZE, f_source);
              }
            p_next += sep_size;

            /*  Found end of file marker                                     */
            if (*p_next == '-' && *(p_next + 1) == '-')
              {
                if (f_tmp)
                  {
                    fclose (f_tmp);
                    f_tmp = NULL;
                    if (get_file_size (tmp_name) == 0)
                        file_delete (tmp_name);
                  }
                break;
              }
            else
                while (*p_next == '\r' || *p_next == '\n')
                    p_next++;
          }
        else
          {
            if (f_tmp) {
                ASSERT (fwrite (p_data, &buffer [read_size - sep_size ] - p_data, 1, f_tmp));
            }
            offset = 0;
            while (read_size > 0 && p_next == NULL)
              {
                memmove (buffer, &buffer [read_size - sep_size + offset ],
                                 sep_size);
                read_size = fread (&buffer [sep_size], 1,
                                   MULTI_BUFFER_SIZE - sep_size, f_source);
                p_next = (char *) memfind ((byte *) buffer,
                                  read_size + sep_size,
                                  (byte *) separator, sep_size, FALSE);
                if (p_next != NULL)
                  {
                    /*  We need to test if the buffer is large enough.  If
                        not, we use the additional buffer size starting at
                        position REST_BUFFER_SIZE.
                     */
                    if (read_size == (MULTI_BUFFER_SIZE - sep_size))
                      {
                        /* Attach the rest of the file */
                        rest_read_size = fread (&buffer [MULTI_BUFFER_SIZE], 1,
                                                REST_BUFFER_SIZE, f_source);
                      }

                    *p_next = '\0';
                    save_multipart_header (table, header_tab,
                                           p_data, tmp_name, local_format);
                    if (f_tmp)
                      {
                        ASSERT (fwrite (buffer, p_next - buffer, 1, f_tmp));
                        fclose (f_tmp);
                        f_tmp = NULL;
                        if (get_file_size (tmp_name) == 0)
                            file_delete (tmp_name);
                      }
                    p_next += sep_size;

                   /*  Found end of file marker                              */
                   if (*p_next == '-' && *(p_next + 1) == '-')
                     {
                       read_size = 0;
                       break;
                     }
                   else
                       while (*p_next == '\r' || *p_next == '\n')
                           p_next++;
                   read_size += sep_size;
                  }
                else
                  {
                    /*  We did not find the next separator, so we write the
                        file and continue.
                     */
                    if (f_tmp) {
                        ASSERT (fwrite (buffer, read_size, 1, f_tmp));
                    }
                    offset = sep_size;
                  }

              }
          }
      }
    if (f_tmp)
      {
        fclose (f_tmp);
        if (get_file_size (tmp_name) == 0)
            file_delete (tmp_name);
      }
    sym_delete_table (header_tab);
    fclose (f_source);
    mem_free (buffer);

    descr = http_multipart2url (table);
    sym_delete_table (table);

    if (tmp_name != NULL)
        mem_free (tmp_name);
    return (descr);
}


/*  -------------------------------------------------------------------------
    Function: http_multipart2url

    Synopsis: Convert a symbol table to a string in format :
              name=value&name=value&name=value ....
    -------------------------------------------------------------------------*/

static DESCR *
http_multipart2url  (const SYMTAB *symtab)
{
    DESCR
        *descr;                         /*  Formatted descriptor             */
    SYMBOL
        *symbol;                        /*  Pointer to symbol                */
    char
        *p_char,
        *p_val;
    qbyte
        buffer_length = 0;

    if (!symtab)
        return (NULL);                  /*  Return NULL if argument is null  */

    /*  Calculate length of buffer                                           */
    for (symbol = symtab-> symbols; symbol; symbol = symbol-> next)
        buffer_length += strlen (symbol-> name) + strlen (symbol-> value) + 2;

    /*  On 16-bit systems, check that buffer will fit into max. block size   */
#   if (UINT_MAX == 0xFFFFU)
    if (buffer_length > UINT_MAX - 32)
        return (NULL);
#   endif

    /*  Now allocate the descriptor                                          */
    descr = mem_descr (NULL, (size_t) buffer_length + 1);
    if (descr == NULL)
        return (NULL);

    p_char = (char *) descr-> data;
    for (symbol = symtab-> symbols; symbol; symbol = symbol-> next)
      {
        if (symbol != symtab-> symbols)
            *p_char++ = '&';
        p_val = symbol-> name;
        while (*p_val)
            *p_char++ = *p_val++;
        *p_char++ = '=';
        p_val = symbol-> value;
        while (*p_val)
            *p_char++ = *p_val++;
      }
    *p_char = '\0';

    return (descr);
}


/*  -------------------------------------------------------------------------
    Function: save_multipart_header

    Synopsis: Store field name and value in symbol table.
    -------------------------------------------------------------------------*/

static void
save_multipart_header (SYMTAB *table, SYMTAB *header, char *data,
                       char *tmp_name, const char *local_format)
{
    SYMBOL
        *sym_filename,
        *symbol;
    char
        *copyfmt,
        *percent,
        *tmp_val,
        *value;

    /* Check if is a file uploaded or form field                             */
    if ((sym_filename = sym_lookup_symbol (header, "filename")) != NULL)
      {
        symbol = sym_lookup_symbol (header, "name");
        if (symbol)
          {
            value = http_escape (sym_filename-> value, NULL, 0);
            if (value)
              {
                sym_assume_symbol (table, symbol-> value, value);
                mem_free (value);
              }
            /*  Construct temporary name by replacing %s in the format string
             *  by the variable name.  We don't use sprintf because it's too
             *  dangerous here: the format string can cause havoc.
             */
            copyfmt = mem_strdup (local_format);
            percent = strchr (copyfmt, '%');
            if (percent && percent [1] == 's')
              {
                percent [0] = '\0';
                tmp_val = xstrcpy (NULL, copyfmt, symbol-> value, percent + 2, NULL);
              }
            else
                tmp_val = xstrcpy (NULL, symbol-> value, "_tmp", NULL);

            value = http_escape (tmp_name, NULL, 0);
            if (value)
              {
                sym_assume_symbol (table, tmp_val, value);
                mem_free (value);
              }
            mem_strfree (&tmp_val);
            mem_strfree (&copyfmt);
          }
      }
    else
      {
        symbol = sym_lookup_symbol (header, "name");
        if (symbol)
          {
            value = http_escape (data, NULL, 0);
            if (value)
              {
                sym_assume_symbol (table, symbol-> value, value);
                mem_free (value);
              }
          }
      }
}


/*  -------------------------------------------------------------------------
    Function: multipart_decode_header

    Synopsis: Decode mime header of a part.
    -------------------------------------------------------------------------*/

static void
multipart_decode_header (char *header, SYMTAB *table)
{
    char
        *p,
        *variable,
        *value = "";

    ASSERT (header);
    ASSERT (table);

    while ( *header == ' ' )
        header++;

    p        = header;
    variable = header;
    while (*p)
      {
        if ((*p == ':' && *(p + 1) == ' ')
        ||   *p == '=')
          {
            *p++ = '\0';
            if (*p == ' ' || *p == '"')
                p++;
            value = p;
          }
        else
        if (*p == ';' || *p == '\r'||  *p == '\n')
          {
            if (*(p - 1) == '"')
                *(p - 1) = '\0';
            else
                *p = '\0';
            http_unescape (value, NULL);
            sym_assume_symbol (table, variable, value);
            p++;
            while (*p == ' ' || *p == '\r' || *p == '\n' || *p == '\t')
                p++;
            variable = p;
          }
        p++;
      }
    if (p != header && *(p - 1) == '"')
        *(p - 1) = '\0';
    http_unescape (value, NULL);
    sym_assume_symbol (table, variable, value);
}


/*  ---------------------------------------------------------------------[<]-
    Function: is_full_url

    Synopsis: If the specified URI string starts with a URL scheme, returns
    TRUE, else returns FALSE.  A schema is one or more of [A-Za-z0-9+-.]
    followed by a ':'.
    ---------------------------------------------------------------------[>]-*/

Bool
is_full_url (const char *string)
{
    Bool
        scheme_size = 0;

    ASSERT (string);
    while (*string)
      {
        if (isalpha (*string)
        ||  isdigit (*string)
        ||  *string == '+'
        ||  *string == '-'
        ||  *string == '.')
            scheme_size++;              /*  So far, a valid scheme name      */
        else
        if (*string == ':')
            return (scheme_size > 0);   /*  Okay if ':' was not first char   */
        else
            return (FALSE);             /*  No scheme name found             */
        string++;
      }
    return (FALSE);                     /*  End of string but no scheme      */
}


/*  ---------------------------------------------------------------------[<]-
    Function: build_full_url

    Synopsis: If the specified URI string does not start with a URL schema
    or a directory delimiter, we add the path of the base URI, and return
    the resulting full URI.  This is provided as a freshly-allocated string
    which the caller must free using mem_free() when finished.  If the
    specified URI is already a full URI, returns a fresh copy of that URI.
    ---------------------------------------------------------------------[>]-*/

char *
build_full_url (const char *uri, const char *base_uri)
{
    char
        *full_uri,                      /*  Formatted full URI               */
        *slash;                         /*  Find delimiter in path           */
    int
        base_len,                       /*  Length of base part of URI       */
        uri_len;                        /*     and the rest of uri           */

    if (is_full_url (uri) || uri [0] == '/')
        return (mem_strdup (uri));
    else
      {
        /*  Find last slash in base URI                                      */
        slash = strrchr (base_uri, '/');
        if (slash)
          {
            uri_len  = strlen (uri);
            base_len = ++slash - base_uri;
            full_uri = mem_alloc (base_len + uri_len + 1);
            if (full_uri)
              {
                memcpy (full_uri, base_uri, base_len);
                strncpy (full_uri + base_len, uri, (uri_len + 1));
              }
          }
        else
            full_uri = xstrcpy (NULL, "/", uri, NULL);

        return (full_uri);
      }
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_time_str

    Synopsis: Returns the current date and time formatted using the HTTP
    standard format for log files: "DD/Mon/YYYY:hh:mm:ss".  The formatted
    time is in a static string that each call overwrites.
    ---------------------------------------------------------------------[>]-*/

char *
http_time_str (void)
{
    static char
        formatted_time [30],
        *months = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
    time_t
        time_secs;
    struct tm
        *time_struct;

    time_secs   = time (NULL);
    time_struct = safe_localtime (&time_secs);

    snprintf (formatted_time, sizeof (formatted_time),
                              "%02d/%s/%04d:%02d:%02d:%02d %s",
                              time_struct-> tm_mday,
                              months + time_struct-> tm_mon * 4,
                              time_struct-> tm_year + 1900,
                              time_struct-> tm_hour,
                              time_struct-> tm_min,
                              time_struct-> tm_sec,
                              timezone_string ());
    return (formatted_time);
}


/*  ---------------------------------------------------------------------[<]-
    Function: alloc_upload

    Synopsis: Alloc a upload structure to store a file uploaded into memory.
    ---------------------------------------------------------------------[>]-*/

UPLOADFILE *
alloc_upload (char *file_name, char *mime_type, long size, byte *data)
{
    UPLOADFILE
        *file = NULL;

    file = mem_alloc (sizeof (UPLOADFILE));
    if (file)
      {
        memset (file, 0, sizeof (UPLOADFILE));
        if (file_name)
            strcpy (file-> file_name, file_name);
        if (mime_type)
            strcpy (file-> mime_type, mime_type);
        file-> size = size;
        if (size > 0 && data)
          {
            file-> data = mem_alloc (size + 1);
            if (file-> data)
              {
                 memcpy (file-> data, data, size);
                 file-> data [size] = '\0';
              }
            else
                file-> size = 0;
          }
      }
    return (file);
}


/*  ---------------------------------------------------------------------[<]-
    Function: free_upload

    Synopsis:
    ---------------------------------------------------------------------[>]-*/

void
free_upload (UPLOADFILE *file_uploaded)
{
    if (file_uploaded)
      {
        if (file_uploaded-> data)
            mem_free (file_uploaded-> data);
         mem_free (file_uploaded);
      }
}


/*  ---------------------------------------------------------------------[<]-
    Function: http_multipart_mem

    Synopsis: Decode a multipart encoded POST from a browser.
    (Same as htt_multipart_decode).
    In this version, the data is in a input buffer, and the decoded data stored
    into two symbol table. The fist contain all form fiel (name of the field for
    key value). The posted file(s) are stored into the symbol table 'files',
    the file name is the key. All file data are stored into a 'UPLOADFILE'
    structure attached at field 'data' of the symbol.
    ---------------------------------------------------------------------[>]-*/

int
http_multipart_mem (const byte *buffer, const long buffer_size,
                    SYMTAB ** form_field, SYMTAB **files)
{
    static char
        separator [80 + 1];
    static int
        sep_size;
    char
        *p_end,
        *tmp_name = NULL,
        *p_head,
        *p_data,
        *p_next;
    SYMTAB
        *header_tab,
        *head;
    SYMBOL
        *symbol;
    int
        nb_files = 0;
    UPLOADFILE
        *fupload = NULL;

    if (form_field  == NULL
    ||  files       == NULL
    ||  buffer      == NULL
    ||  buffer_size == 0)
        return (0);

    header_tab = sym_create_table ();
    if (header_tab == NULL)
        return (0);

    if (*form_field)
        sym_delete_table (*form_field);
    if (*files)
        sym_delete_table (*files);

    head       = *form_field = sym_create_table ();
    *files     =               sym_create_table ();

    memset (separator, 0, sizeof (separator));
    separator [0] = 0x0D;
    separator [1] = 0x0A;

    p_next = strchr ((const char *)buffer, '\n');
    if (p_next)
      {
        *p_next++ = '\0';
        strcpy (&separator [2], (const char *)buffer);
      }
    strconvch (&separator [2] , '\r', '\0');
    strconvch (&separator [2] , '\n', '\0');
    sep_size  = strlen (separator);
    p_end = (char *)buffer + buffer_size;

    while (p_next && p_next < p_end)
      {
        sym_empty_table (header_tab);
        p_head = p_next;
        p_data = (char *) memfind ((byte *) p_head, p_end - p_head,
                          (byte *) "\r\n\r\n", 4, FALSE);
        if (p_data)
          {
            *p_data = '\0';
            p_data += 4;
          }
        if (p_head)
            multipart_decode_header (p_head, header_tab);

        p_next = (char *) memfind ((byte *) p_data, p_end - p_data,
                          (byte *) separator, sep_size, FALSE);
        if (p_next != NULL)
          {
            /*  The next attribute-value pair was found                      */
            *p_next = '\0';

            /* If file name                                                  */
            symbol = sym_lookup_symbol (header_tab, "filename");
            if (symbol && p_next - p_data > 0)
              {
                tmp_name = strip_file_path (symbol-> value);
                symbol = sym_assume_symbol (*files, tmp_name, "");
                if (symbol)
                  {
                    fupload = alloc_upload (symbol-> name, NULL,
                                            p_next - p_data, (byte *)p_data);
                    if (fupload)
                      {
                        symbol-> data = fupload;
                        nb_files++;
                      }
                  }
                p_data = tmp_name;
                if ( (symbol = sym_lookup_symbol (header_tab, "name")) != NULL )
                    sym_assume_symbol (head, strlwc (symbol-> value), p_data);
              }
            else
            /* If form                                                       */
            if ( (symbol = sym_lookup_symbol (header_tab, "name")) != NULL )
                sym_assume_symbol (head, strlwc (symbol-> value), p_data);
            else
              {
                /* we assume this is a body message                          */
                sym_assume_symbol (head, "bodymessage", p_data);
              }

            p_next += sep_size;

            /*  Found end of file marker                                     */
            if (*p_next == '-' && *(p_next + 1) == '-')
                break;
            else
                while (*p_next == '\r' || *p_next == '\n')
                    p_next++;
          }
      }

    sym_delete_table (header_tab);

    return (nb_files);
}


/*  ---------------------------------------------------------------------[<]-
    Function: url_http_new

    Synopsis: Create a new, empty url_http structure.
    ---------------------------------------------------------------------[>]-*/

URL_HTTP *url_http_new (void)
{
    URL_HTTP
        *http = NULL;

    http = mem_alloc (sizeof (URL_HTTP));
    if (http == NULL)
        return (NULL);
    http-> host = NULL;
    http-> port = 0;

    return (http);
}


/*  ---------------------------------------------------------------------[<]-
    Function: url_http_free

    Synopsis: Free the supplied url_http structure.
    ---------------------------------------------------------------------[>]-*/

void url_http_free (URL_HTTP *http)
{
    if (http) {
        if (http-> host)
            mem_free (http-> host);
        mem_free (http);
    }
}


/*  ---------------------------------------------------------------------[<]-
    Function: url_new

    Synopsis: Create a new, empty url structure.
    ---------------------------------------------------------------------[>]-*/

URL *url_new (void)
{
    URL
        *url = NULL;

    url = mem_alloc (sizeof (URL));
    if (url == NULL)
        return (NULL);
    url-> scheme    = NULL;
    url-> authority = NULL;
    url-> path      = NULL;
    url-> query     = NULL;
    url-> fragment  = NULL;
    url-> http      = NULL;

    return (url);
}


/*  ---------------------------------------------------------------------[<]-
    Function: url_free

    Synopsis: Free the supplied url structures, and any members if present.
    ---------------------------------------------------------------------[>]-*/

void url_free (URL *url)
{
    if (url) {
        if (url-> scheme)
            mem_free (url-> scheme);
        if (url-> authority)
            mem_free (url-> authority);
        if (url-> path)
            mem_free (url-> path);
        if (url-> query)
            mem_free (url-> query);
        if (url-> fragment)
            mem_free (url-> fragment);
        if (url-> http)
            url_http_free (url-> http);
        mem_free (url);
    }
}


/*  ---------------------------------------------------------------------[<]-
    Function: url_from_string

    Synopsis: Split up a URL into it's component parts.  RFC 2396 gives the
    following regular expression for parsing URLs:

    ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
     12            3  4          5       6  7        8 9
    scheme    = $2
    authority = $4
    path      = $5
    query     = $7
    fragment  = $9

    query and fragment can be considered optional.  Scheme and authority could
    also be optional in the presence of a base url, but we mandate that these
    are required.  Returns a new url_struct representing the url, or NULL if
    an error occured.
    ---------------------------------------------------------------------[>]-*/

URL *url_from_string (const char *string)
{
    const char
        *token_start = NULL,            /*  Start of token                   */
        *token_end   = NULL;            /*  End of token                     */
    URL
        *url       = NULL;              /*  Handle to url structure          */

    ASSERT (string);

    url = url_new ();                   /*  Create url structure             */
    if (url == NULL) {
        mem_free (url);
        return (NULL);
    }

    token_start = string;
    token_end = strpbrk (token_start, ":/?#");
    if (token_end && *token_end == ':') {
                                        /*  End of scheme                    */
        url-> scheme = mem_strndup (token_start, token_end - token_start);
        if (url-> scheme == NULL || !strused (url-> scheme)) {
            url_free (url);
            return (NULL);
        }
    } else {
        url_free (url);                 /*  Missing scheme                   */
        return (NULL);
    }

    token_end ++;
    if (token_end [0] == '/' && token_end [1] == '/') {
        token_start = token_end + 2;    /*  Start of authority               */
        token_end = strpbrk (token_start, "/?#");
        if (token_end && *token_end == '/') {
                                        /*  End of authority                 */
            url-> authority = mem_strndup (token_start,
                                           token_end - token_start);
            if (url-> authority == NULL || !strused (url-> authority)) {
                url_free (url);
                return (NULL);
            }
            token_start = token_end;    /*  Start of path                    */
        } else {
            url_free (url);             /*  Missing path                     */
            return (NULL);
        }
    } else {
        url_free (url);                 /*  Missing authority                */
        return (NULL);
    }

    token_end = strpbrk (token_start, "?#");
    if (token_end) {                    /*  End of path                      */
        url-> path = mem_strndup (token_start, (token_end - token_start));
        if (url-> path == NULL || !strused (url-> path)) {
            url_free (url);
            return (NULL);
        }
        if (token_end && *token_end == '?') {
            token_start = token_end + 1;/*  Start of query                   */
            token_end = strpbrk (token_start, "#");
            if (token_end && *token_end == '#')
                                        /*  End of query, start of fragment  */
                url->query = mem_strndup (token_start, token_end - token_start);
            else                        /*  Fragment not present             */
                url-> query = mem_strdup (token_start);
            if (url-> query == NULL) {
                url_free (url);
                return (NULL);

            }
        }
        if (token_end && *token_end == '#') {
            token_start = token_end + 1;/*  Start of fragment                */
            url-> fragment = mem_strdup (token_start);
            if (url-> fragment == NULL) {
                url_free (url);
                return (NULL);
            }
        }
    } else {                            /*  No query or fragment             */
        url-> path = mem_strdup (token_start);
        if (url-> path == NULL || !strused (url-> path)) {
            url_free (url);
            return (NULL);
        }
    }

    /*  If this is a http or https URL, we can parse the authority           */
    if ((strncmp (url-> scheme, "http", 4)  == 0) ||
        (strncmp (url-> scheme, "https", 5) == 0)) {
        url-> http = url_http_new ();
        if (url-> http == NULL) {
            url_free (url);
            return (NULL);
        }
        token_end = strpbrk (url-> authority, ":");
        if (token_end) {                /*  Port present                     */
            token_start = token_end + 1;
            url-> http-> port = atoi (token_start);
            url-> http-> host = mem_strndup (url-> authority,
                                             token_end - url-> authority);
        } else {                        /*  No port                          */
            url-> http-> host = mem_strdup (url-> authority);
        }
        if (url-> http-> host == NULL) {
            url_free (url);
            return (NULL);
        }
    }

    return (url);
}


/*  ---------------------------------------------------------------------[<]-
    Function: url_to_string

    Synopsis: Return the url passed in represented as a string.  The caller
    is responsible for freeing the string after use.
    ---------------------------------------------------------------------[>]-*/

char *url_to_string (const URL *url)
{
    const
        int s_size = 8192;              /*  XXX Arbitrary buffer length      */
    char
        *string = NULL;

    ASSERT (url);

    /*  Check if URL looks complete                                          */
    if (!(url-> scheme && (url-> authority || url-> http) && url-> path))
        return (NULL);

    string = mem_alloc (s_size);
    if (string == NULL)
        return (NULL);

    xstrlcpy (string, url-> scheme, s_size);
    xstrlcat (string, "://", s_size);
    /*  Use http authority components if present                             */
    if (url-> http) {
        ASSERT (url-> http-> host);
        xstrlcat (string, url-> http-> host, s_size);
        if (url-> http-> port)
            xstrlcat (string, strprintf (":%d", url-> http-> port), s_size);
    } else {
        xstrlcat (string, url-> authority, s_size);
    }
    xstrlcat (string, url-> path, s_size);
    if (url-> query) {
        xstrlcat (string, "?", s_size);
        xstrlcat (string, url-> query, s_size);
    }
    if (url-> fragment) {
        xstrlcat (string, "#", s_size);
        xstrlcat (string, url-> fragment, s_size);
    }

    /*  Shrink down to real size                                             */
    string = mem_realloc (string, strlen (string));
    return (string);
}

