/* 
 * This is a minimal lua interpreter designed to be run as a CGI script from
 * a web server.  A global table named CGI is created. Then, we query the
 * environment for several CGI specific variables, and use them to set 
 * members in that table. (see below for the mapping). Once that is done, 
 * the query string is collected, either from the environment or from 
 * standard input (depending on form type) and parsed out into a series of 
 * key/value pairs, which are also stored in a key named parsedQueryString.
 *
 * This can handle both GET and POST type forms only. For POST forms, only
 * one particular encoding is supported. Things are easily extensible to
 * other encoding types, or special custom form types, if that sort of thing
 * is required.
 *
 * I should note that most of the CGI environment variables that I have 
 * included came from a CGI spec. The rest, like DOCUMENT_ROOT, I happened
 * to notice during testing, and they seemed interesting. So I'm not sure
 * if these are supposed to be set, or if this is just an apache-specific
 * thing. Also, when pulling in environment variables, variables that seem
 * to be set to nothing are skipped. So, if QUERY_STRING is "", then it
 * won't show up in the CGI table. I'm not sure if there's some valid reason
 * for QUERY_STRING to be set when there isn't any query string, since you
 * can't do anything, so I opted to skip that out. This is easily modifiable
 * if that's somehow taboo or something.
 *
 * Anyone is free to use, modify and distribute this source code, so long as
 * you don't use it as-is and pretend that you wrote it, or make minor
 * modifications and give yourself all the credit, or charge anyone money
 * to get a copy of it (short of a reasonable transfer fee like the cost of
 * a floppy disk), or get mad at me if it doesn't work for you or damages
 * something. Note this means you can use this without giving me any credit,
 * if that's your bag.
 *
 * Oh yeah, and it's written to use lua 4.0 from the brilliant lua team.
 * You can get your own copy of lua at http://www.lua.org. When I wrote this,
 * I slightly modified my version of lua 4.0 in the following ways:
 * 
 * - Removed the function tmpnam by commenting the relevant lines out of the
 *   relevant files. The god damn GNU linker is such a whiney bitch I couldn't 
 *   take it anymore. I didn't feel like modifying things to use the function
 *   it seems to want me to use. This is really a personal preference anyways.
 *
 * - I modified luaB__ALERT andd errormessage so that they print the string you 
 *   pass in to stdout instead of stderr. This allows you to capture scripting
 *   errors in your browser. If you don't do this, then your errors generally go
 *   to some server-specific place. For apache, this is the apache error_log file.
 *
 * - Merged the defintions of the "date" and "time" function from the 4.1
 *   version into the 4.0 version, because my web site requires them to work
 *   in the new way. If you're linking against the 4.1 version, this is of
 *   course no problem. (UPDATE: As of 1.0.3, these two functions have been placed
 *   into this file directly)
 *
 * These modifications aren't neccesary for this code to work, though.
 *
 * This really is just a minimal lua parser. Ideally, some other things added to
 * this would be nice. Among them are (in no particular order):
 *
 *   - Modify all error output so that it's generated as HTML and easier to read
 *
 *   - Add some HTML specific functions to make generation of things easier. An
 *     example would be to generate a select form tag from a table.
 *
 *   - Support cookies, by parsing and saving them the same sort of way that
 *     the query string is currently handled.
 *
 * These things, and maybe others, will be added if I ever need them, or find 
 * myself wanting to code them up, or if someone donates the code.
 *
 * The date and time functions below are taken from the 4.1 version of lua. These
 * aren't part of the standard lua 4 distribution, but some stuff I had used them.
 * I also got the jpeg size code from some place on the internet, though I can't 
 * remember where at the moment. I butchered it a bit to fit into this concept of a
 * single C file with no external headers needed. Anyone know where I got this from?
 * I claim no credit for anything but putting it in here so that my web pages would
 * load faster.
 *
 * To compile this, you'd first want to install lua, and then do something like:
 *     gcc lua-web.c -o lua-web -llua -llua-lib -lm
 *
 * Adjust accordingly if you need different libraries or to add lib or include paths.
 *
 * To use, you would put the generated executable into a cgi-bin directory, make it
 * executable, and then call it from a script. Here's what a sample might look like.
 * (Note that the script has to be set executable as well):
 *
 * ------ Start Test Script ------
 *
 * #!./lua-web
 *
 * foreach (CGI , print)
 * foreach (CGI.parsedQueryString , print)
 *
 * ------ Stop  Test Script ------
 *
 * Of course, you could put lua-web in it's own path and update the first line accordingly.
 * Also, unless the URL of the script is submitted with some sort of query (as a form, or something
 * like /myscript.cgi?arg1=1&arg2=2), the second line will cause an error, because parsedQueryString is
 * only set if there was a query string.
 *
 * That plus knowledge of lua should be enough to get you going.
 * 
 * Revision History
 *     July 22 2001 - Initial Revision - Terence Martin (poindexter@nurdz.com)
 *     Feb   9 2002 - Function to return the size of a jpeg - Terence Martin (poindexter@nurdz.com)
 *     Oct   8 2003 - Add in functions from lua-extended, to give dircontents, stat,etc. Also date
 *                    and time, in case this gets linked against normal lua4 libs. - Terence Martin (poindexter@nurdz.com)
 *     Nov  18 2003 - Modify header of this file to have more documentation for this, and initial posting to the web - Terence Martin (poindexter@nurdz.com)
 */

/* The version number of the code. This *MUST* be changed every time this code is altered! */
#define LUAWEB_VERSION "LuaWeb 1.0.3"
 
/* When this is defined, functions that allow you to get the contents of a directory, stat files, etc
 * are added into the interpreter. Turn this off if you're compiling this on a host that doesn't
 * have those functions. Or rewite this and let me know what you did, if you need to fix anything. This
 * has only been tested on linux.
 */
#define USE_EXTENSIONS
 
#ifdef USE_EXTENSIONS
#define __USE_BSD
#include <time.h>
#include <dirent.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>


/* These are used in the code that gets the size of a jpeg. */
#define readbyte(a,b) do if (((a) = getc((b))) == EOF) { fclose (b); return 0; } while (0)
#define readword(a,b) do { int cc_=0,dd_=0; \
                           if((cc_=getc((b))) == EOF || (dd_=getc((b))) == EOF) { fclose (b); return 0; } \
                           (a) = (cc_<<8) + (dd_); \
                         } while(0)

/* A simple function that tells you if there was no parameter given. */
#define lua_isnoneornil(L, n)   (lua_type(L,n) == LUA_TNIL || lua_type(L,n) == LUA_TNONE)

/* These define how big a key and a value in a Query String can be. Keys and 
 * values larger than these sizes get truncated during parsing. Generally
 * speaking, low values are good for keys because they're not likely to be
 * very long, while value should be fairly big because form fields like text
 * area objects can have a lot of text in them.
 *
 * The sizes here don't take NULL termination into account, so the value here
 * and the maxiumum length of the key/value is identical. */
#define MAX_KEY_LEN           256
#define MAX_VALUE_LEN (64 * 1024)

/* This is the only content type encoding we can handle for POST forms. */
#define VALID_ENCODING "application/x-www-form-urlencoded"
 
/* This defines all of the environment variables that will be pulled and
 * placed into the CGI table upon startup. The first is the actual name of
 * the environment variable, and the second is it's internal name in the
 * table. Variables that don't exist at the time the script executes will be
 * silently dropped. Terminate the list with NULL. 
 *
 * Others will perhaps want to modify this so that the key name and the
 * environment name are the same. I consider this a matter of personal
 * preference, and even though I think constant defines should have
 * CAPITAL_NAMES, it just seems wrong to do that in this context. */
const char *vars[] = {
    "AUTH_TYPE"            , "authType"           ,
    "CONTENT_LENGTH"       , "contentLength"      ,
    "CONTENT_TYPE"         , "contentType"        ,
    "DOCUMENT_ROOT"        , "documentRoot"       ,
    "GATEWAY_INTERFACE"    , "gatewayInterface"   ,
    "HTTP_ACCEPT"          , "httpAccept"         ,
    "HTTP_ACCEPT_ENCODING" , "httpAcceptEncoding" ,
    "HTTP_ACCEPT_LANGUAGE" , "httpAcceptLanguage" ,
    "HTTP_HOST"            , "httpHost"           ,
    "HTTP_USER_AGENT"      , "httpUserAgent"      ,
    "PATH_INFO"            , "pathInfo"           ,
    "PATH_TRANSLATED"      , "pathTranslated"     ,
    "QUERY_STRING"         , "queryString"        ,
    "REMOTE_ADDR"          , "remoteAddr"         ,
    "REMOTE_HOST"          , "remoteHost"         ,
    "REMOTE_IDENT"         , "remoteIdent"        ,
    "REMOTE_USER"          , "remoteUser"         ,
    "REQUEST_METHOD"       , "requestMethod"      ,
    "REQUEST_URI"          , "requestURI"         ,
    "SCRIPT_FILENAME"      , "scriptFilename"     ,
    "SCRIPT_NAME"          , "scriptName"         ,
    "SERVER_ADDR"          , "serverAddr"         ,
    "SERVER_ADMIN"         , "serverAdmin"        ,
    "SERVER_NAME"          , "serverName"         ,
    "SERVER_PORT"          , "serverPort"         ,
    "SERVER_PROTOCOL"      , "serverProtocol"     ,
    "SERVER_SIGNATURE"     , "serverSignature"    ,
    "SERVER_SOFTWARE"      , "serverSoftware"     ,
    NULL
}; 

/* Checks the size of a jpeg file, returning the dimensions in the values
 * given. If it works, 1 is returned, else 0 is returned. */
int jpeg_size (char *filename, int *image_width , int *image_height) 
{
    int marker=0;
    int dummy=0;
    FILE *infile;

    infile = fopen (filename , "rb");
    if (infile == NULL)
        return 0;
  
    /* Eat the file header. Leave if we don't get the bytes we think we
     * should. */
    if (getc (infile) != 0xFF || getc (infile) != 0xD8)
    {
        fclose (infile);
        return 0;
    }

    /* Loop forever reading bytes, because jpeg files store data in chunks,
     * like wave files, and the chunk with the dimensions aren't neccesarily
     * at the beginning. */
    while (1)
    {
        int discarded_bytes=0;
        
        /* Read a marker. */
        readbyte (marker,infile);

        /* If this marker isn't 0xFF, then throw away bytes until we find
         * one that is. */
        while (marker != 0xFF) 
        {
            discarded_bytes++;
            readbyte (marker,infile);
        }
        
        /* Now, read bytes until we hit a 0xFF again. */
        do 
            readbyte(marker,infile); 
        while (marker == 0xFF);

        /* If we had to discard any bytes to do that, then this isn't a valid
         * jpeg file. */
        if (discarded_bytes != 0) 
        {
            fclose (infile);
            return 0;
        }
   
        /* Check the marker to see what's what. */
        switch (marker) 
        {
            /* Chunk types that carry dimension information. */
            case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC5: case 0xC6:
            case 0xC7: case 0xC9: case 0xCA: case 0xCB: case 0xCD: case 0xCE:
            case 0xCF: 
            {
                /* Get chumk parameter length, a filler byte, then we can
                 * get the dimensions out, and return. */
                readword (dummy , infile);    
                readbyte (dummy , infile);
                readword ((*image_height) , infile);
                readword ((*image_width) , infile);
                fclose (infile);

                return 1;
            }

            /* If we hit these, that's it for us. I'm not sure, but I think that
             * they indicate the end of the file, and so if we've hit them,
             * we already know that we can't get any info out. */
            case 0xDA:
            case 0xD9:
                fclose (infile);
                return 0;

            /* All other chunk types. Just read and discard. */
            default: 
            {
                int length;
        
                /* Get chunk length. It includes itself, so it's always two
                 * bigger than you'd think. */
                readword (length,infile);

                /* Chunk has no length? Then leave. */
                if (length < 2)
                {
                    fclose (infile);
                    return 0;
                }

                /* Take the length of the length into account, then throw the
                 * rest of the bytes away. */
                length -= 2;
                while (length > 0) 
                {
                    readbyte (dummy, infile);
                    length--;
                }
            }
            break;
        }
    }
}

#ifdef USE_EXTENSIONS

#define LUAEXTENSIONS_VERSION "1.0.0"

/* A simple helper, it assumes the top of the stack is a table, and adds the
 * given number to the table with the given key name. */
static void helper_addnumber (lua_State *L , long value , char *keyName)
{
    /* Push the key and value, and then leave. */
    lua_pushstring (L , keyName);
    lua_pushnumber (L , value);
    lua_settable (L , -3);
}


/* dircontents ([dirname])
 *
 * Returns to you a table contains the contents of the passed in directory, or 
 * the contents of the current directory, if no directory name is given. If 
 * there is an error, nil and an error message are returned instead. */
static int ext_dircontents (lua_State *L) 
{
    const char *startDir = luaL_opt_lstr (L , 1 , "." , NULL);
    struct dirent **nameList;
    int retVal,i;

    /* Scan the directory given to attempt to get the contents */
    retVal = scandir (startDir , &nameList , NULL , alphasort);

    /* If the return value is -1, it means that something has gone
     * horribly wrong. So, return back nil and an error message. */
    if (retVal == -1)
    {
        lua_pushnil (L);
        lua_pushstring (L , strerror (errno));
        return 2;
    }

    /* We got something. It might be nothing, though. Put a table on the stack,
     * and then insert all the items. */
    lua_newtable (L);

    for (i = 0 ; i < retVal ; i++)
    {
        /* Push the name of this item onto the stack, then free it. Finally,
         * set it into the table at the current index. */
        lua_pushstring (L , nameList[i]->d_name);
        free (nameList[i]);
        lua_rawseti (L , -2 , i + 1);
    }

    /* Now that we're done, throw the name list away. */
    free (nameList);

    /* For a bit of a speedup, insert the total amount of entries as "n".
     * the function getn () checks for this value before counting. */
    lua_pushstring (L , "n");
    lua_pushnumber (L , retVal);
    lua_settable (L , -3);

    /* Return the table back */
    return 1;
}

/* This is a helper for ext_stat. It takes a time value and a key name,
 * assumes that the top of the stack is a table, and inserts into that
 * table a table with the given key. The inserted table is the date, broken
 * apart into constituent parts. */
static void helper_stattime (lua_State *L , time_t timeVal , char *keyName)
{
    /* Insert the key. */
    lua_pushstring (L , keyName);

    /* Call the date function, telling it to return a table for the date
     * that we were given. */
    lua_getglobal (L , "date");
    lua_pushstring (L , "*t");
    lua_pushnumber (L , timeVal);
    lua_call (L , 2 , 1);

    /* Now we can set this key into the table. */
    lua_settable (L , -3);
}

/* This is a helper for ext_stat. It takes a group ID and a key, and sets
 * into the table at the top of the stack a key/value pair that is the name
 * of that group. */
static void helper_groupid (lua_State *L , gid_t gid , char *keyName)
{
    struct group *grInfo;

    /* Push the key. */
    lua_pushstring (L , keyName);

    /* Look up the group name, if we can. If it works, push the group
     * name. If it doesn't, push the group number instead. */
    grInfo = getgrgid (gid);
    if (grInfo == NULL)
        lua_pushnumber (L , gid);
    else
        lua_pushstring (L , grInfo->gr_name);

    /* Go */
    lua_settable (L , -3);
}

/* This is a helper for ext_stat. It sets the given key with the type of
 * the file, as given by the passed in mode paramter. */
static void helper_filetype (lua_State *L , mode_t mode , char *keyName)
{
    /* Push the key */
    lua_pushstring (L , keyName);

    /* Push the type */
    if (S_ISREG  (mode)) lua_pushstring (L , "file"); else
    if (S_ISDIR  (mode)) lua_pushstring (L , "directory"); else
    if (S_ISCHR  (mode)) lua_pushstring (L , "charDevice"); else
    if (S_ISBLK  (mode)) lua_pushstring (L , "blockDevice"); else
    if (S_ISFIFO (mode)) lua_pushstring (L , "fifo"); else
    if (S_ISLNK  (mode)) lua_pushstring (L , "link"); else
    if (S_ISSOCK (mode)) lua_pushstring (L , "socket");  else
        lua_pushstring (L , "unknown");

    /* Go */
    lua_settable (L , -3);
}

/* This is a helper for ext_stat. It takes a user ID and a key, and sets
 * into the table at the top of the stack a key/value pair that is the name
 * of that user. */
static void helper_userid (lua_State *L , uid_t uid , char *keyName)
{
    struct passwd *usInfo;

    /* Push the key. */
    lua_pushstring (L , keyName);

    /* Look up the user name, if we can. If it works, push the user
     * name. If it doesn't, push the user number instead. */
    usInfo = getpwuid (uid);
    if (usInfo == NULL)
        lua_pushnumber (L , uid);
    else
        lua_pushstring (L , usInfo->pw_name);

    /* Go */
    lua_settable (L , -3);
}

/* This is a helper for ext_stat. It takes 3 bools that indicate read/write/exec
 * permissions, and then creates a table with the given keyname that has keys
 * for those three things. This table is set into the table on the top of the
 * stack. */
static void helper_permissions (lua_State *L , int r , int w , int x , char *keyName)
{
    /* Push the key, then add a new table. */
    lua_pushstring (L , keyName);
    lua_newtable (L);

    /* For each permission, add an entry to the table, if it is set. */
    if (r) helper_addnumber (L , 1 , "read");
    if (w) helper_addnumber (L , 1 , "write");
    if (x) helper_addnumber (L , 1 , "execute");

    /* Set it in */
    lua_settable (L , -3);
}

/* stat (filename)
 *
 * Returns back a table that contains information about the given file, or
 * nil and an error message, if something goes horribly wrong. */
static int ext_stat (lua_State *L)
{
    /* Get the filename that we will stat */
    const char *fname = luaL_check_lstr (L , 1 , NULL);
    int doLinkStat;
    int result;
    struct stat statBuf;

    /* Determine if we will lstat or not. The second paramter has to be
     * non-nil to lstat. */
    if (lua_isnoneornil (L , 2))
        doLinkStat = 0;
    else
        doLinkStat = 1;

    /* Attempt the stat. If it doesn't work, then bail out. */
    if (doLinkStat)
        result = lstat (fname , &statBuf);
    else
        result = stat (fname , &statBuf);

    if (result != 0)
    {
        lua_pushnil (L);
        lua_pushstring (L , strerror (errno));
        return 2;
    } 

    /* Must have worked. Put a table on the stack so we can set stuff
     * into it. */
    lua_newtable (L);

    /* Insert the stat type that we did. This is primarily for debugging, but
     * someone might want to know after the fact the type, since with a
     * non-lstat, you can never tell if anything is a link. */
    lua_pushstring (L , "statType");
    if (doLinkStat)
        lua_pushstring (L , "lstat");
    else
        lua_pushstring (L , "stat");
    lua_settable (L , -3);

    /* Insert the size of the file */
    helper_addnumber (L , statBuf.st_size , "size");

    /* Insert the user and group ID */
    helper_addnumber (L , statBuf.st_uid , "ownerUID");
    helper_userid    (L , statBuf.st_uid , "user");
    helper_addnumber (L , statBuf.st_gid , "ownerGID");
    helper_groupid   (L , statBuf.st_gid , "group");
   
    /* Insert the access time */
    helper_stattime (L , statBuf.st_atime , "accessTime");

    /* Insert the modification time */
    helper_stattime (L , statBuf.st_mtime , "modificationTime");

    /* Insert the change time */
    helper_stattime (L , statBuf.st_ctime , "changeTime");

    /* Insert the file type */
    helper_filetype (L , statBuf.st_mode , "type");

    /* Insert the owner permissions. */
    helper_permissions (L , statBuf.st_mode & S_IRUSR ,
                            statBuf.st_mode & S_IWUSR , 
                            statBuf.st_mode & S_IXUSR , "ownerPermissions");

    /* Insert the group permissions. */
    helper_permissions (L , statBuf.st_mode & S_IRGRP ,
                            statBuf.st_mode & S_IWGRP , 
                            statBuf.st_mode & S_IXGRP , "groupPermissions");
    
    /* Insert the owner permissions. */
    helper_permissions (L , statBuf.st_mode & S_IROTH ,
                            statBuf.st_mode & S_IWOTH , 
                            statBuf.st_mode & S_IXOTH , "otherPermissions");

    /* If the setUID bit is set, then add that in. Do the same for setGID
     * and sticky. */
    if (statBuf.st_mode & S_ISUID) helper_addnumber (L , 1 , "setUID");
    if (statBuf.st_mode & S_ISGID) helper_addnumber (L , 1 , "setGID");
    if (statBuf.st_mode & S_ISVTX) helper_addnumber (L , 1 , "isSticky");

    /* Return the table back */
    return 1;
}

/* readlink (filename)
 *
 * Assumes that the passed in filename is a symlink. This will query to see
 * where the link is pointing. That is returned. Otherwise, nil and an error
 * message is returned. */
static int ext_readlink (lua_State *L)
{
    /* Get the filename that we will check */
    const char *fname = luaL_check_lstr (L , 1 , NULL);
    char fbuffer[256];
    int result;

    /* Do the call, check for error. */
    if ((result = readlink (fname , fbuffer , sizeof (fbuffer) - 1)) == -1)
    {
        lua_pushnil (L);
        lua_pushstring (L , strerror (errno));
        return 2;
    }

    /* Put the string. Since it's not null terminated, tell the other side how
     * big it is. */
    lua_pushlstring (L , fbuffer , result);
    return 1;
}

/* dirname (filename)
 * 
 * Returns the path portion of the given filename. */
static int ext_dirname (lua_State *L)
{
    /* Get the filename that we will check */
    const char *fname = luaL_check_lstr (L , 1 , NULL);
    char buffer[256];

    /* Make a copy */
    strncpy (buffer , fname , sizeof (buffer) - 1);
    buffer[sizeof (buffer) - 1] = '\0';

    /* Do the job and return the string. */
    lua_pushstring (L , dirname (buffer));
    return 1;
}

/* basename (filename)
 *
 * Returns the filename portion of the given filename. */
static int ext_basename (lua_State *L)
{
    /* Get the filename that we will check */
    const char *fname = luaL_check_lstr (L , 1 , NULL);
    char buffer[256];

    /* Make a copy */
    strncpy (buffer , fname , sizeof (buffer) - 1);
    buffer[sizeof (buffer) - 1] = '\0';

    /* Do the job and return the string. */
    lua_pushstring (L , basename (buffer));
    return 1;
}

/*
** {======================================================
** Time/Date operations
** { year=%Y, month=%m, day=%d, hour=%H, min=%M, sec=%S,
**   wday=%w+1, yday=%j, isdst=? }
** =======================================================
*/

static void setfield (lua_State *L, const char *key, int value) {
  lua_pushstring(L, key);
  lua_pushnumber(L, value);
  lua_rawset(L, -3);
}


static int getfield (lua_State *L, const char *key, int d) {
  int res;
  lua_pushstring(L, key);
  lua_gettable(L, -2);
  if (lua_isnumber(L, -1))
    res = (int)(lua_tonumber(L, -1));
  else {
    if (d == -2)
    {
      luaL_verror(L, "field `%s' missing in date table", key);
      return 0;
    }
    res = d;
  }
  lua_pop(L, 1);
  return res;
}

static int io_date (lua_State *L) {
  const char *s = luaL_opt_string(L, 1, "%c");
  time_t t = (time_t)(luaL_opt_number(L, 2, -1));
  struct tm *stm;
  if (t == (time_t)(-1))  /* no time given? */
    t = time(NULL);  /* use current time */
  if (*s == '!') {  /* UTC? */
    stm = gmtime(&t);
    s++;  /* skip `!' */
  }
  else
    stm = localtime(&t);
  if (stm == NULL)  /* invalid date? */
    lua_pushnil(L);
  else if (strcmp(s, "*t") == 0) {
    lua_newtable(L);
    setfield(L, "sec", stm->tm_sec);
    setfield(L, "min", stm->tm_min);
    setfield(L, "hour", stm->tm_hour);
    setfield(L, "day", stm->tm_mday);
    setfield(L, "month", stm->tm_mon+1);
    setfield(L, "year", stm->tm_year+1900);
    setfield(L, "wday", stm->tm_wday+1);
    setfield(L, "yday", stm->tm_yday+1);
    setfield(L, "isdst", stm->tm_isdst);
  }
  else {
    char b[256];
    if (strftime(b, sizeof(b), s, stm))
      lua_pushstring(L, b);
    else
    {
      luaL_verror(L, "invalid `date' format");
      return 0;
    }
  }
  return 1;
}

static int io_time (lua_State *L) {
  if (lua_isnoneornil(L, 1))  /* called without args? */
    lua_pushnumber(L, time(NULL));  /* return current time */
  else {
    time_t t;
    struct tm ts;
    luaL_checktype(L, 1, LUA_TTABLE);
    lua_settop(L, 1);  /* make sure table is at the top */
    ts.tm_sec = getfield(L, "sec", 0);
    ts.tm_min = getfield(L, "min", 0);
    ts.tm_hour = getfield(L, "hour", 12);
    ts.tm_mday = getfield(L, "day", -2);
    ts.tm_mon = getfield(L, "month", -2)-1;
    ts.tm_year = getfield(L, "year", -2)-1900;
    ts.tm_isdst = getfield(L, "isdst", -1);
    t = mktime(&ts);
    if (t == (time_t)(-1))
      lua_pushnil(L);
    else
      lua_pushnumber(L, t);
  }
  return 1;
}


static int io_difftime (lua_State *L) {
  lua_pushnumber(L, difftime((time_t)(luaL_check_number(L, 1)),
                             (time_t)(luaL_opt_number(L, 2, 0))));
  return 1;
}


static const struct luaL_reg extendedlib[] = {
  {"dircontents",     ext_dircontents},
  {"stat",            ext_stat},
  {"readlink",        ext_readlink},
  {"dirname",         ext_dirname},
  {"basename",        ext_basename},
  {"date",            io_date},
  {"time",            io_time},
  {"difftime",        io_difftime},
};

LUALIB_API void lua_extendedlibopen(lua_State *L)
{
    /* Set a global that tells people what version of the extensions they have. */
    lua_pushstring (L , LUAEXTENSIONS_VERSION);
    lua_setglobal (L , "LUAEXTENSIONS_VERSION");

    /* Add our extension functions */
    luaL_openl (L, extendedlib);
}

#else

/* This is only compiled in if USE_EXTENSIONS is NOT defined. A simple stub. */
LUALIB_API void lua_extendedlibopen (lua_State *L)
{
}

#endif

/* This converts a hex character into an integer in the range of 0-15, unless
 * the character passed in isn't a valid hex character, in which case it 
 * returns -1 instead. */
int hexcharToDigit (char digit)
{
    if (digit >= '0' && digit <= '9')
        return digit - '0';
    else if (digit >= 'A' && digit <= 'F')
        return digit - 'A' + 10;
    else if (digit >= 'a' && digit <= 'f')
        return digit - 'a' + 10;
    else 
        return -1;
}

/* This takes two hex digits and turns them into their integer value. If
 * either one of the digits is not a valid hex digit, the integer value
 * for '!' is returns instead. This allows you to spot horribly broken
 * URL's easier. Generally this "shouldn't happen" and "is nothing to
 * worry about". */
int hexToDec (char digit1 , char digit2)
{
    /* Convert each digit to it's hex value. */
    digit1 = hexcharToDigit (digit1);
    digit2 = hexcharToDigit (digit2);

    /* If either is bad, return a static char. */
    if (digit1 == -1 || digit2 == -1)
        return '!';

    /* Convert to full hex value and return it. */
    return digit1 * 16 + digit2;
}

/* Copies a string from one place to another in a special way. string is the 
 * string we want to copy out of. buffer is where we want to copy to. maxLen is
 * the maximum length (not counting null byte) that we want to copy.
 * stopChar is the character we want to assume is the end of the string.
 * 
 * The string is copied until maxLen bytes have been copied, the end of the
 * string has been reached, or stopChar is hit. The buffer is always
 * null terminated, but stopChar will not be copied.
 *
 * During copying, sequences like %XX, where XX is a hex value, are
 * replaced with their ascii value. Decoding of + as a space is also done.
 * No other special URL decoding is done.
 *
 * The return value is a pointer inside string where we stopped. This would
 * be either pointing at stopChar or at the terminating null byte on the
 * string. */
char *copyStr (char *string , char *buffer , int maxLen , char stopChar)
{
    int strIndex=0,bufferIndex=0;

    /* Keep looping until the character we landed on is the stop
     * character or the end of the string. */
    while (string[strIndex] != stopChar && string[strIndex] != '\0')
    {
        /* If this character isn't a '%' character, then copy it.
         * Otherwise, expand it. */
        if (string[strIndex] == '%')
        {
            int c1,c2;

            /* The two characters after the % are hex digits. Get them out.
             * If there aren't two characters before we hit the null
             * terminator, break out leaving strIndex at the null, so our
             * return makes the caller stop. */
            strIndex++;
            c1 = string[strIndex++]; if (c1 == '\0') break;
            c2 = string[strIndex++]; if (c2 == '\0') break;
            buffer[bufferIndex++] = hexToDec (c1 , c2);
        }
        else if (string[strIndex] == '+')
        {
            /* Copy a + as a space, but still bump the str index. */
            buffer[bufferIndex++] = ' ';
            strIndex++;
        }
        else
            /* Just copy, it's not special. */
            buffer[bufferIndex++] = string[strIndex++];

        /* If we've now copied as many characters as we're allowed, then
         * break out now. */
        if (bufferIndex >= maxLen) 
            break;
    }

    /* Make sure strIndex is either the character we were supposed to stop
     * at, or the null byte at the end. This is to clean up if a key or
     * value is longer than we allow it to be, and we stop copying it in the
     * middle. */
    while (string[strIndex] != '\0' && string[strIndex] != stopChar)
        strIndex++;
    
    /* Null terminate our buffer. */
    buffer[bufferIndex] = '\0';

    /* Return where we stopped. */
    return string + strIndex;
}

/* This function is a helper for parseString. It assumes the lua stack in the
 * given state has a top element that is the table we're putting our parsed
 * values into. This will add the given value to the table under the
 * given key. If the key already exists, then it's value is turned into a
 * table. 
 *
 * On return, the stack is left in the same state as it was when we started. */
void addKeyValue (lua_State *L , char *key , char *value)
{
    char *oldVal;

    /* In order to properly add this value into the table, we first have to
     * query the table to see if it has a value for this key, and if so, what
     * it is. We push the key onto the stack and get the value for that key.
     * This will pop the key we push, and replace it with the current value of
     * that key. */
    lua_pushstring (L , key);
    lua_gettable (L , -2);

    /* Depending on what the type of the value at the top of the stack is,
     * we will do something different. */
    switch (lua_tag (L , -1))
    {
        /* When the type is nil, it means this key doesn't exist yet. So we
         * simply pop the nil off the stack to get rid of it, then set the 
         * key as normal. The settable will pop the key and value while
         * doing it's job. */
        case LUA_TNIL:
            lua_pop (L , 1);
            lua_pushstring (L , key);
            lua_pushstring (L , value);
            lua_settable (L , -3);
            break;
    
        /* When the type is string, it means we've already added a single
         * value for this key. In that case, we need to save the current
         * value, replace it with a table, then put the old value and the
         * new value back into the new table. */           
        case LUA_TSTRING:
            /* First of all, get the value of the string at the top of the
             * stack (the current value of the key we want to set). Once we
             * do that, we will pop that string off the stack so the top is
             * the parsedString table again. */
            oldVal = (char *) lua_tostring (L , -1);
            lua_pop (L , 1);

            /* Now, push the key we want to use onto the stack, followed by 
             * a brand new table. */
            lua_pushstring (L , key);
            lua_newtable (L);

            /* Now, we will set the original value into the table at
             * index 1. Note that rawseti pops the value from the stack. */
            lua_pushstring (L , oldVal);
            lua_rawseti (L , -2 , 1);

            /* Set the new value to index 2 in the same way. */
            lua_pushstring (L , value);
            lua_rawseti (L , -2 , 2);

            /* Lastly, do a settable to set this new table as the value for
             * the key, which will replace the original string that was in
             * there. It will also remove the key and inner table from the
             * stack. */
            lua_settable (L , -3);
            break;

        /* When the type is table, it means this is the 3rd or more time that
         * this key has appeared. The item at the top of the stack is the
         * table, so just add this new value to that table at the next
         * numeric index. */
        case LUA_TTABLE:
            /* Set this new value into the table at the next numerical index
             * in the table, as returned by getn. Remember that the top of the
             * stack is this table associated with the current key, because of
             * our earlier query for the current value of the key. We presume
             * that getn returns how many indexes there are so far, and add 1
             * to it to get the new one. */
            lua_pushstring (L , value);
            lua_rawseti (L , -2 , lua_getn (L , -2) + 1);

            /* Now that that's done, pop this sub-table, since we're done
             * fooling with it. */
            lua_pop (L , 1);
            break;
            
        /* This should never happen, since we are strictly in control of the
         * table at this point. Nevertheless, specifically do nothing if the
         * type of the object at the top of the stack has a weird type. */
        default:
            break;
    }
}

/* This parses a query string into a key/value paired table. The
 * return value is the number of key/value pairs that were parsed. 0 generally
 * indicates failure, or an empty query string.
 *
 * On return, if 1 or more pairs were parsed, a table will be left on the
 * top of the lua stack. If nothing was parsed, the lua stack is left as it was
 * on entry. 
 *
 * This intelligently handles multiple values for the same key (as would be
 * returned by a multi-select list, for example) by setting the value of that
 * key to a table with all the values in it. 
 * 
 * This allocates a buffer for the key and value, and ensures the parsed values 
 * don't overrun that buffer. So, no worries about tromping into places man
 * should be afraid to tread. */
int parseString (lua_State *L , char *cmdStr)
{
    char *key,*value;
    int keysParsed=0;

    /* Start by trying to allocate memory for our buffers. If either of these
     * don't work, we should just return out right now. Notice that on  a failure 
     * of the second allocation, we're careful not to leak the first one. */
    key = (char *) malloc (MAX_KEY_LEN + 1);
    if (key == NULL)
        return 0;
    
    value = (char *) malloc (MAX_VALUE_LEN + 1);
    if (value == NULL)
    {
        free (key);
        return 0;
    }

    /* Now, we can put an empty table on the top of the stack. We will be 
     * setting keys in this table to the values that we parse. */
    lua_newtable (L);

    /* Loop until we hit the end of the string */
    while (*cmdStr != '\0')
    {
        /* Fish out the key string. The return value is the postion
         * of what made us stop, either the = sign, or a null byte.
         * If we hit a null byte, this isn't a valid key, so we can
         * skip out right now. */
        cmdStr = copyStr (cmdStr , key , MAX_KEY_LEN , '=');
        if (*cmdStr == '\0') break;

        /* Do the same thing for the value. The difference here is that
         * we don't mind if we hit the null byte, that just tells us
         * there are no more key/value pairs left. Which means, we
         * examine the return value. If it's not a null byte, then we
         * assume there's more data and increment the cmdStr by 1 to
         * skip past the & that splits keys pairs, so the next iteration
         * starts at the next key. */
        cmdStr = copyStr (cmdStr + 1 , value , MAX_VALUE_LEN , '&');
        if (*cmdStr != '\0') cmdStr++;

        /* Do something with the key/value pair */
        addKeyValue (L , key , value);

        /* One more key parsed */
        keysParsed++;
    }
 
    /* Blow away the buffers, we're done now. */
    free (key);
    free (value);
   
    /* If we parsed no keys, then we have an empty table on the stack that we
     * want to throw away. */
    if (keysParsed == 0)
       lua_pop (L , 1);

    /* Return the number of keys we parsed */
    return keysParsed;
}

/* This attempts to return back the query string of the current request for
 * parsing. If this is a GET form, then this will return the value of the
 * QUERY_STRING environment variable. If this is a POST form, the data will
 * be read from stdin. If the form type is unknown, or the query string is
 * empty, then NULL will be returned instead. 
 *
 * Either way, the return value is a pointer to an allocated buffer holding
 * the data. The caller should free () this once they're done with it. */
char *queryString (void)
{
    char *string;
    char *returnValue;

    /* First thing we're going to do is attempt to get the request type
     * from the environment. From this, we will decide where to get the
     * query string from. If this doesn't work, then we will return out
     * right away. */
    string = getenv ("REQUEST_METHOD");
    if (string == NULL)
        return NULL;

    /* For a GET request, the query string is in an environment variable
     * named QUERY_STRING. */
    if (strcasecmp (string , "GET") == 0)
    {
        /* Try to get the query string from the environment. */
        string = getenv ("QUERY_STRING");
        if (string == NULL)
            return NULL;

        /* Duplicate the value of the variable and return that. */
        returnValue = (char *) malloc (strlen (string) + 1);
        strcpy (returnValue , string);
        return returnValue;
    }

    /* For a POST request, some data was put into our standard input. Here,
     * we need to query the environment for the content encoding and the
     * length of data written. */
    if (strcasecmp (string , "POST") == 0)
    {
        char *encode;
        int length;
        int bytesRead;

        /* First of all, get the encoding type. Fail out if we can't, or
         * if it's no good. */
        encode = getenv ("CONTENT_TYPE");
        if (encode == NULL || strcasecmp (encode , VALID_ENCODING) != 0)
            return NULL;

        /* Encoding type is good, so try to get the content length. Fail
         * out if we can't get it, or it's not valid. Reuse the encoding
         * variable since we don't need it any longer. */
        encode = getenv ("CONTENT_LENGTH");
        if (encode == NULL || (length = atoi (encode)) <= 0)
            return NULL;
        
        /* Allocate memory, read the data, then null terminate it. */
        returnValue = (char *) malloc (length + 1);
        bytesRead = fread (returnValue , 1 , length , stdin);
        returnValue[bytesRead] = '\0';
        return returnValue;
    }

    /* If we get here, then we don't know what the request type is. In that
     * case, we will just leave. */
    return NULL;
}

/* This sets up our whole CGI world. This pulls in environment variables and
 * sets members in a table named CGI to their values. We also parse any query
 * string we might have gotten as well. */
void setupCGI (lua_State *L)
{
    int varNum;
    char *queryStr;

    /* First thing to do is put a new empty table onto the stack. */
    lua_newtable (L);

    /* Now, we will scan through the list of environment variables, and
     * set any we find. We make sure not to set an empty variable. Hope
     * that's not bad? */
    for (varNum = 0 ; vars[varNum] != NULL ; varNum += 2)
    {
        /* Get the value of the variable */
        char *value = getenv (vars[varNum]);

        /* If we got a value, and it's not empty, put it into the
         * table. */
        if (value != NULL && value[0] != '\0')
        {
            /* Push first the key, then the value, then set it into the
             * table. The call to set the table pops the key and value 
             * from the stack. */
            lua_pushstring (L , vars[varNum + 1]);
            lua_pushstring (L , value);
            lua_settable (L , -3);
        }
    }

    /* Now we want to parse up the query string. First step, see if we can
     * even get one. */
    queryStr = queryString ();
    if (queryStr != NULL)
    {
        /* In preparation for setting up an entry in the CGI table for this
         * parsed info, push a key onto the stack. */
        lua_pushstring (L , "parsedQueryString");

        /* Now we will call the parse function. If it returns non-zero, then
         * it left a table on the stack, which we can set into this main table.
         * If it returns 0, then it took the table off the stack before it
         * returned, so we should pop the key we just put on to throw it
         * away. */
        if (parseString (L , queryStr) != 0)
            lua_settable (L , -3);
        else
            lua_pop (L , 1);
       
        /* With that out of the way, release our copy of the query string. */ 
        free (queryStr);
    }

    /* Now that we're all done, we need to set a global variable using
     * this table. This is, fortunately, easy. */
    lua_setglobal (L , "CGI");
}

/* A simple lua function that can tell you the size of a jpeg file. */
static int cgi_jpegsize (lua_State *L)
{
    int width,height,result;
    char *fname;

    /* Get the filename */
    fname = (char *) luaL_check_string (L , 1);

    /* Try to determine the size of that file. */
    if (jpeg_size (fname , &width , &height) == 0)
        return 0;

    lua_pushnumber (L , width);
    lua_pushnumber (L , height);
   
    return 2; 
}

/* Entry point of the program. Duh. */
int main (int argc , char **argv)
{
    lua_State *L;

    /* Check to see if we were called properly or not. */
    if (argc < 2)
    {
        printf ("Content-Type: text/html\n\n");
        printf ("You're supposed to pass me a script when you call me.\n");
        return 1;
    }
    
    /* We must be good to go. Firstly, open up a lua state. We specify 
     * default stacksize. */
    L = lua_open (0);

    /* Now we can initialize the state by registering the standard libraries
     * and such. Last step here is to do our web-specific initialization
     * stuff. */
    lua_baselibopen (L);
    lua_iolibopen (L);
    lua_strlibopen (L);
    lua_mathlibopen (L);
    lua_extendedlibopen (L);
    setupCGI (L);
    lua_pushcfunction (L , cgi_jpegsize);
    lua_setglobal (L , "jpeg_size");
    lua_pushstring (L , LUAWEB_VERSION);
    lua_setglobal (L , "luaweb_version");


    /* Now we can execute the script we were passed. */
    return lua_dofile (L , argv[1]);
}

