Terminate a freshly allocated string as well.
[infodrom/cgilib] / cgi.c
diff --git a/cgi.c b/cgi.c
index 3565a6a..e390b37 100644 (file)
--- a/cgi.c
+++ b/cgi.c
@@ -1,6 +1,6 @@
 /*
-    cgi.c - Some simple routines for cgi programming
-    Copyright (c) 1996-8  Martin Schulze <joey@infodrom.north.de>
+    cgi.c - Some simple routines for CGI programming
+    Copyright (c) 1996-9,2007,8  Martin Schulze <joey@infodrom.org>
 
     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
     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+    along with this program; if not, write to the Free Software Foundation
+    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#define _GNU_SOURCE 1
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <strings.h>
 #include <ctype.h>
-#include <malloc.h>
+#include <syslog.h>
 #include <cgi.h>
+#include "aux.h"
+
+#ifndef HAVE_STRNDUP
+char *strndup(const char *s, size_t n);
+#endif
+
+char *cgiHeaderString = NULL;
+char *cgiType = NULL;
+
+extern s_cookie **cgiReadCookies();
+
+int cgiSetHeader (const char *name, const char *value)
+{
+    const char *cp, *vp;
+    char *pivot;
+    int len;
+
+    if (!name || !strlen (name) || !value || !strlen (value))
+       return 0;
+    
+    for (cp=name;*cp && *cp!=' ' && *cp!='\r' && *cp!='\n' && *cp!=':';cp++);
+    for (vp=value;*vp && *vp!='\r' && *vp!='\n';vp++);
+
+    if (cp-name == 0 || vp-value == 0)
+       return 0;
+
+    if (cgiHeaderString) {
+       len = (strlen (cgiHeaderString) + cp-name + vp-value + 4) * sizeof (char);
+       if ((pivot = (char *)realloc (cgiHeaderString,len)) == NULL)
+           return 0;
+       cgiHeaderString = pivot;
+       pivot += strlen (cgiHeaderString);
+    } else {
+       len = (cp-name + vp-value + 5) * sizeof (char);
+       if ((cgiHeaderString = (char *)malloc (len)) == NULL)
+           return 0;
+       pivot = cgiHeaderString;
+       *pivot = '\0';
+    }
+    memset(pivot+1, 0, (cp-name + vp-value + 4));
+
+    strncpy (pivot, name, cp-name);
+    strncat (pivot, ": ", 2);
+    strncat (pivot, value, vp-value);
+    strncat (pivot, "\r\n", 2);
 
-int cgiDebugLevel = 0;
-int cgiDebugStderr = 1;
+    return 1;
+}
+
+int cgiSetType (const char *type)
+{
+    int len;
+    const char *cp;
+
+    if (!type || !strlen (type))
+       return 0;
+    if (cgiType)
+       free (cgiType);
+
+    for (cp=type;*cp && *cp!='\r' && *cp!='\n';cp++);
+
+    len = (cp-type+1) * sizeof (char);
+    if ((cgiType = (char *)malloc (len+20)) == NULL)
+       return 0;
+    memset (cgiType, 0, len);
+    strncpy (cgiType, type, cp-type);
+    
+    return 1;
+}
 
 void cgiHeader ()
 {
-    printf ("Content-type: text/html\n\n");
+    if (cgiType)
+       printf ("Content-type: %s\r\n", cgiType);
+    else
+       printf ("Content-type: text/html\r\n");
+    if (cgiHeaderString)
+       printf ("%s", cgiHeaderString);
+    printf ("\r\n");
 }
 
 void cgiDebug (int level, int where)
@@ -39,10 +114,14 @@ void cgiDebug (int level, int where)
        cgiDebugLevel = level;
     else
        cgiDebugLevel = 0;
-    if (where)
-       cgiDebugStderr = 0;
-    else
-       cgiDebugStderr = 1;
+    if (where > 0) {
+       if (where < 3) {
+           cgiDebugType = where;
+           if (where == 2)
+               openlog("cgilib", LOG_PID, LOG_USER);
+       } else
+           cgiDebugType = 0;
+    }
 }
 
 char *cgiDecodeString (char *text)
@@ -65,31 +144,380 @@ char *cgiDecodeString (char *text)
            *(xp++) = *cp;
        }
     }
-    bzero(xp, cp-xp);
+    memset(xp, 0, cp-xp);
     return text;
 }
 
-/*  cgiInit()
+/* cgiReadFile()
+ *
+ * Read and save a file fro a multipart request
+ */
+#include <errno.h>
+char *cgiReadFile (FILE *stream, char *boundary)
+{
+    char *crlfboundary, *buf;
+    size_t boundarylen;
+    int c;
+    unsigned int pivot;
+    char *cp;
+    char template[]= "/tmp/cgilibXXXXXX";
+    FILE *tmpfile;
+    int fd;
+
+    boundarylen = strlen(boundary)+3;
+    if ((crlfboundary = (char *)malloc (boundarylen)) == NULL)
+       return NULL;
+    sprintf (crlfboundary, "\r\n%s", boundary);
+
+    if ((buf = (char *)malloc (boundarylen)) == NULL) {
+       free (crlfboundary);
+       return NULL;
+    }
+    memset (buf, 0, boundarylen);
+    pivot = 0;
+
+    if ((fd = mkstemp (template)) == -1) {
+       free (crlfboundary);
+       free (buf);
+       return NULL;
+    }
+
+    if ((tmpfile = fdopen (fd, "w")) == NULL) {
+       free (crlfboundary);
+       free (buf);
+       unlink (template);
+       return NULL;
+    }
+    
+    while (!feof (stream)) {
+       c = fgetc (stream);
+
+       if (c == 0) {
+           if (strlen (buf)) {
+               for (cp=buf; *cp; cp++)
+                   putc (*cp, tmpfile);
+               memset (buf, 0, boundarylen);
+               pivot = 0;
+           }
+           putc (c, tmpfile);
+           continue;
+       }
+
+       if (strlen (buf)) {
+           if (crlfboundary[pivot+1] == c) {
+               buf[++pivot] = c;
+
+               if (strlen (buf) == strlen (crlfboundary))
+                   break;
+               else
+                   continue;
+           } else {
+               for (cp=buf; *cp; cp++)
+                    putc (*cp, tmpfile);
+                memset (buf, 0, boundarylen);
+                pivot = 0;
+           }
+       }
+
+       if (crlfboundary[0] == c) {
+           buf[0] = c;
+       } else {
+           fputc (c, tmpfile);
+       }
+    }
+
+    if (!feof (stream))
+       fgets (buf, boundarylen, stream);
+
+    fclose (tmpfile);
+
+    free (crlfboundary);
+    free (buf);
+
+    return strdup (template);
+}
+
+/* cgiReadMultipart()
+ *
+ * Decode multipart/form-data
+ */
+#define MULTIPART_DELTA 5
+s_cgi *cgiReadMultipart (char *boundary)
+{
+    char *line;
+    char *cp, *xp;
+    char *name = NULL, *type = NULL;
+    char *fname = NULL;
+    char *tmpfile;
+    int header = 1;
+    s_var **result = NULL;
+    s_var **tmp;
+    int numresults = 0, current = 0;
+    s_file **files = NULL;
+    s_file **tmpf;
+    s_file *file;
+    int index = 0;
+    size_t len;
+    s_cgi *res;
+    
+    while ((line = cgiGetLine (stdin)) != NULL) {
+
+       if (!strncmp (line, boundary, strlen(boundary))) {
+           header = 1;
+       } else if (!strncasecmp (line, "Content-Disposition: form-data; ", 32)) {
+           if (!name) {
+               if ((cp = strstr (line, "name=\"")) == NULL)
+                   continue;
+               cp += 6;
+               if ((xp = strchr (cp, '\"')) == NULL)
+                   continue;
+               name = strndup (cp, xp-cp);
+               cgiDecodeString (name);
+               cgiDebugOutput (2, "Found field name %s", name);
+
+               if ((cp = strstr (line, "filename=\"")) == NULL)
+                   continue;
+               cp += 10;
+               if ((xp = strchr (cp, '\"')) == NULL)
+                   continue;
+               fname = strndup (cp, xp-cp);
+               cgiDecodeString (fname);
+               cgiDebugOutput (2, "Found filename %s", fname);
+           }
+       } else if (!strncasecmp (line, "Content-Type: ", 14)) {
+           if (!type) {
+               cp = line + 14;
+               type = strdup (cp);
+               cgiDebugOutput (2, "Found mime type %s", type);
+           }
+       } else if (header) {
+           if (!strlen(line)) {
+               header = 0;
+
+               if (fname) {
+                   header = 1;
+                   tmpfile = cgiReadFile (stdin, boundary);
+
+                   if (!tmpfile) {
+                       free (name);
+                       free (fname);
+                       if (type)
+                           free (type);
+                       name = fname = type = NULL;
+                   }
+
+                   cgiDebugOutput (2, "Wrote %s (%s) to file: %s", name, fname, tmpfile);
+
+                   if (!strlen (fname)) {
+                       cgiDebugOutput (3, "Found empty filename, removing");
+                       unlink (tmpfile);
+                       free (tmpfile);
+                       free (name);
+                       free (fname);
+                       if (type)
+                           free (type);
+                       name = fname = type = NULL;
+                   } else {
+                       if ((file = (s_file *)malloc (sizeof (s_file))) == NULL) {
+                           cgiDebugOutput (3, "malloc failed, ignoring %s=%s", name, fname);
+                           unlink (tmpfile);
+                           free (tmpfile);
+                           free (name);
+                           free (fname);
+                           if (type)
+                               free (type);
+                           name = fname = type = NULL;
+                           continue;
+                       }
+
+                       file->name = name;
+                       file->type = type;
+                       file->tmpfile = tmpfile;
+                       if ((cp = rindex (fname, '/')) == NULL)
+                           file->filename = fname;
+                       else {
+                           file->filename = strdup (++cp);
+                           free (fname);
+                       }
+                       name = type = fname = NULL;
+
+                       if (!files) {
+                           if ((files = (s_file **)malloc(2*sizeof (s_file *))) == NULL) {
+                               cgiDebugOutput (3, "malloc failed, ignoring %s=%s", name, fname);
+                               unlink (tmpfile);
+                               free (tmpfile);
+                               free (name);
+                               name = NULL;
+                               if (type) {
+                                   free (type);
+                                   type = NULL;
+                               }
+                               free (file->filename);
+                               free (file);
+                               continue;
+                           }
+                           memset (files, 0, 2*sizeof (s_file *));
+                           index = 0;
+                       } else {
+                           for (index=0; files[index]; index++);
+                           if ((tmpf = (s_file **)realloc(files, (index+2)*sizeof (s_file *))) == NULL) {
+                               cgiDebugOutput (3, "realloc failed, ignoring %s=%s", name, fname);
+                               unlink (tmpfile);
+                               free (tmpfile);
+                               free (name);
+                               if (type)
+                                   free (type);
+                               free (file->filename);
+                               free (file);
+                               name = type = fname = NULL;
+                               continue;
+                           }
+                           files = tmpf;
+                           memset (files + index, 0, 2*sizeof (s_file *));
+                       }
+                       files[index] = file;
+                   }
+               }
+           }
+       } else {
+           if (name) {
+
+               /* try to find out if there's already such a variable */
+               for (index=0; index<current && strcmp (result[index]->name,name); index++);
+
+               if (index == current) {
+                   if (!result) {
+                       len = MULTIPART_DELTA * sizeof (s_var *);
+                       if ((result = (s_var **)malloc (len)) == NULL) {
+                           free (name);
+                           if (type)
+                               free (type);
+                           return NULL;
+                       }
+                       numresults = MULTIPART_DELTA;
+                       memset (result, 0, len);
+                       current = 0;
+                   } else {
+                       if (current+2 > numresults) {
+                           len = (numresults + MULTIPART_DELTA) * sizeof(s_var *);
+                           if ((tmp = (s_var **)realloc (result, len)) == NULL) {
+                               for (index=0; result[index]; index++)
+                                   free (result[index]);
+                               free (result);
+                               free (name);
+                               if (type)
+                                   free (type);
+                               return NULL;
+                           }
+                           result = tmp;
+                           memset (result + numresults, 0, len - numresults*sizeof(s_var *));
+                           numresults += MULTIPART_DELTA;
+                       }
+                   }
+                   if ((result[current] = (s_var *)malloc(sizeof(s_var))) == NULL) {
+                       for (index=0; result[index]; index++)
+                           free (result[index]);
+                       free (result);
+                       free (name);
+                       if (type)
+                           free (type);
+                       return NULL;
+                   }
+                   current++;
+                   cgiDebugOutput (3, "Set #%d to %s=%s", index, name, line);
+                   result[index]->name = name; name = NULL;
+                   result[index]->value = strdup (line);
+                   cgiDecodeString (result[index]->value);
+                   if (type) {
+                       free (type);
+                       type = NULL;
+                   }
+               } else {
+                   cgiDebugOutput (3, "Set #%d to %s=%s", index, name, line);
+                   free (name);
+                   name = NULL;
+                   if ((cp = (char *)realloc (result[index]->value, strlen(result[index]->value)+strlen(line)+2)) != NULL) {
+                       strcat(cp, "\n");
+                       strcat(cp, line);
+                       result[index]->value = cp;
+                       if (type) {
+                           free (type);
+                           type = NULL;
+                       }
+                   }
+               }
+           } else {
+               if (index > 0) {
+                   xp = strdup (line);
+                   cgiDecodeString (xp);
+
+                   if ((name = (char *)malloc (strlen(result[index]->value)+strlen(xp)+3)) == NULL) {
+                       for (index=0; result[index]; index++)
+                           free (result[index]);
+                       free (result);
+                       free (xp);
+                       return NULL;
+                   }
+                   sprintf (name, "%s\r\n%s", result[index]->value, xp);
+                   free (result[index]->value);
+                   result[index]->value = name;
+                   name = NULL;
+                   free (xp);
+               }
+           }
+       }
+    }
+
+    if ((res = (s_cgi *)malloc (sizeof (s_cgi))) == NULL)
+       return NULL;
+
+    res->vars = result;
+    res->cookies = NULL;
+    res->files = files;
+
+    return res;
+}
+
+/*  cgiReadVariables()
  *
  *  Read from stdin if no string is provided via CGI.  Variables that
  *  doesn't have a value associated with it doesn't get stored.
  */
-s_cgi **cgiInit ()
+s_cgi *cgiReadVariables ()
 {
     int length;
     char *line = NULL;
     int numargs;
     char *cp, *ip, *esp, *sptr;
-    s_cgi **result;
-    int i, k;
+    s_var **result;
+    int i, k, len;
     char tmp[101];
+    s_cgi *res;
+
+    cp = getenv("CONTENT_TYPE");
+    if (cp)
+       cgiDebugOutput (2, "Content-Type: %s", cp);
+    if (cp && strstr(cp, "multipart/form-data") && strstr(cp, "boundary=")) {
+       cp = strstr(cp, "boundary=") + strlen ("boundary=") - 2;
+       *cp = *(cp+1) = '-';
+       return cgiReadMultipart (cp);
+    }
+
+    if ((res = (s_cgi *)malloc (sizeof (s_cgi))) == NULL)
+       return NULL;
 
     cp = getenv("REQUEST_METHOD");
+    if (cp)
+       cgiDebugOutput (2, "REQUEST_METHOD: %s", cp);
     ip = getenv("CONTENT_LENGTH");
+    if (ip)
+       cgiDebugOutput (2, "CONTENT_LENGTH: %s", ip);
 
     if (cp && !strcmp(cp, "POST")) {
        if (ip) {
            length = atoi(ip);
+           if (length <= 0)
+               return NULL;
            if ((line = (char *)malloc (length+2)) == NULL)
                return NULL;
            fgets(line, length+1, stdin);
@@ -104,29 +532,33 @@ s_cgi **cgiInit ()
        } else
            return NULL;
     } else {
-       length = 0;
+        length = 0;
        printf ("(offline mode: enter name=value pairs on standard input)\n");
-       for (cp = fgets(tmp, 100, stdin); cp != NULL;
-            cp = fgets(tmp, 100, stdin) ) {
+       memset (tmp, 0, sizeof(tmp));
+       while((cp = fgets (tmp, 100, stdin)) != NULL) {
            if (strlen(tmp)) {
-               length += strlen(tmp);
-               if ((ip = (char *)malloc (length * sizeof(char))) == NULL)
-                   return NULL;
-               bzero(ip, length);
-               if (line) {
-                   if (line[strlen(line)-1] == '\n')
-                       line[strlen(line)-1] = '&';
-                   strcpy(ip, line);
+               if (tmp[strlen(tmp)-1] == '\n')
+                   tmp[strlen(tmp)-1] = '&';
+               if (length) {
+                   length += strlen(tmp);
+                   len = (length+1) * sizeof(char);
+                   if ((line = (char *)realloc (line, len)) == NULL)
+                       return NULL;
+                   strcat (line, tmp);
+               } else {
+                   length = strlen(tmp);
+                   len = (length+1) * sizeof(char);
+                   if ((line = (char *)malloc (len)) == NULL)
+                       return NULL;
+                   memset (line, 0, len);
+                   strcpy (line, tmp);
                }
-               ip = strcat(ip, tmp);
-               if (line)
-                   free (line);
-               line = ip;
            }
+           memset (tmp, 0, sizeof(tmp));
        }
        if (!line)
            return NULL;
-       if (line[strlen(line)-1] == '\n')
+       if (line[strlen(line)-1] == '&')
            line[strlen(line)-1] = '\0';
     }
 
@@ -135,11 +567,7 @@ s_cgi **cgiInit ()
      *  and look like  foo=bar&foobar=barfoo&foofoo=
      */
 
-    if (cgiDebugLevel > 0)
-       if (cgiDebugStderr)
-           fprintf (stderr, "Received cgi input: %s\n", line);
-       else
-           printf ("<b>Received cgi input</b><br>\n<pre>\n--\n%s\n--\n</pre>\n\n", line);
+    cgiDebugOutput (1, "Received CGI input: %s", line);
 
     for (cp=line; *cp; cp++)
        if (*cp == '+')
@@ -147,28 +575,27 @@ s_cgi **cgiInit ()
 
     if (strlen(line)) {
        for (numargs=1,cp=line; *cp; cp++)
-           if (*cp == '&') numargs++;
+           if (*cp == '&' || *cp == ';' ) numargs++;
     } else
        numargs = 0;
-    if (cgiDebugLevel > 0)
-       if (cgiDebugStderr)
-           fprintf (stderr, "%d cgi variables found.\n", numargs);
-       else
-           printf ("%d cgi variables found.<br>\n", numargs);
+    cgiDebugOutput (1, "%d cgi variables found.", numargs);
 
-    if ((result = (s_cgi **)malloc((numargs+1) * sizeof(s_cgi *))) == NULL)
+    len = (numargs+1) * sizeof(s_var *);
+    if ((result = (s_var **)malloc (len)) == NULL)
        return NULL;
-    bzero (result, (numargs+1) * sizeof(s_cgi *));
+    memset (result, 0, len);
 
     cp = line;
     i=0;
     while (*cp) {
-       if ((ip = (char *)index(cp, '&')) != NULL) {
+       if ((ip = (char *)strchr(cp, '&')) != NULL) {
            *ip = '\0';
-       }else
+       } else if ((ip = (char *)strchr(cp, ';')) != NULL) {
+           *ip = '\0';
+       } else
            ip = cp + strlen(cp);
 
-       if ((esp=(char *)index(cp, '=')) == NULL) {
+       if ((esp=(char *)strchr(cp, '=')) == NULL) {
            cp = ++ip;
            continue;
        }
@@ -179,78 +606,279 @@ s_cgi **cgiInit ()
        }
 
        if (i<numargs) {
+           char *name;
+           char *value;
+
+           if ((name = (char *)malloc((esp-cp+1) * sizeof (char))) == NULL)
+               return NULL;
+           strncpy(name, cp, esp-cp);
+           name[esp-cp] = '\0';
+           cgiDecodeString (name);
+
+           cp = ++esp;
+
+           if ((value = (char *)malloc((ip-esp+1) * sizeof (char))) == NULL) {
+               free (name);
+               return NULL;
+           }
+           strncpy(value, cp, ip-esp);
+           value[ip-esp] = '\0';
+           cgiDecodeString (value);
 
-           for (k=0; k<i && (strncmp(result[k]->name,cp, esp-cp)); k++);
            /* try to find out if there's already such a variable */
+           for (k=0; k<i && strcmp (result[k]->name, name); k++);
+
            if (k == i) {       /* No such variable yet */
-               if ((result[i] = (s_cgi *)malloc(sizeof(s_cgi))) == NULL)
+               if ((result[i] = (s_var *)malloc(sizeof(s_var))) == NULL)
                    return NULL;
-               if ((result[i]->name = (char *)malloc((esp-cp+1) * sizeof(char))) == NULL)
-                   return NULL;
-               bzero (result[i]->name, esp-cp+1);
-               strncpy(result[i]->name, cp, esp-cp);
-               cp = ++esp;
-               if ((result[i]->value = (char *)malloc((ip-esp+1) * sizeof(char))) == NULL)
-                   return NULL;
-               bzero (result[i]->value, ip-esp+1);
-               strncpy(result[i]->value, cp, ip-esp);
-               result[i]->value = cgiDecodeString(result[i]->value);
-               if (cgiDebugLevel) {
-                   if (cgiDebugStderr)
-                       fprintf (stderr, "%s: %s\n", result[i]->name, result[i]->value);
-                   else
-                       printf ("<h3>Variable %s</h3>\n<pre>\n%s\n</pre>\n\n", result[i]->name, result[i]->value);
-               }
+               result[i]->name = name;
+               result[i]->value = value;
+               cgiDebugOutput (1, "%s: %s", result[i]->name, result[i]->value);
                i++;
            } else {    /* There is already such a name, suppose a mutiple field */
-               if ((sptr = (char *)malloc((strlen(result[k]->value)+(ip-esp)+2)* sizeof(char))) == NULL)
+               free (name);
+               len = (strlen(result[k]->value)+strlen(value)+2) * sizeof (char);
+               if ((sptr = (char *)malloc(len)) == NULL) {
+                   free (value);
                    return NULL;
-               bzero (sptr, strlen(result[k]->value)+(ip-esp)+2);
-               sprintf (sptr, "%s\n", result[k]->value);
-               cp = ++esp;
-               strncat(sptr, cp, ip-esp);
-               free(result[k]->value);
+               }
+               memset (sptr, 0, len);
+               sprintf (sptr, "%s\n%s", result[k]->value, value);
+               free (result[k]->value);
+               free (value);
                result[k]->value = sptr;
+               cgiDebugOutput (1, "%s: %s", result[i]->name, result[i]->value);
            }
        }
        cp = ++ip;
     }
-    return result;
+
+    res->vars = result;
+    res->cookies = NULL;
+    res->files = NULL;
+
+    return res;
 }
 
-char *cgiGetValue(s_cgi **parms, const char *var)
+/*  cgiInit()
+ *
+ *  Read from stdin if no string is provided via CGI.  Variables that
+ *  doesn't have a value associated with it doesn't get stored.
+ */
+s_cgi *cgiInit()
 {
-    int i;
+    s_cgi *res;
 
-    if (parms)
-       for (i=0;parms[i]; i++)
-           if (!strcmp(var,parms[i]->name)) {
-               if (cgiDebugLevel > 0)
-                   if (cgiDebugStderr)
-                       fprintf (stderr, "%s found as %s\n", var, parms[i]->value);
-                   else
-                       printf ("%s found as %s<br>\n", var, parms[i]->value);
-               return parms[i]->value;
+    res = cgiReadVariables ();
+    if (res)
+       res->cookies = cgiReadCookies ();
+    else
+       {
+           /* In some cases, we might have no other CGI results.
+              But we still have cookies! */
+           s_cookie **cookies;
+           cookies = cgiReadCookies();
+           if (cookies) {
+               /* We need to create a s_cgi structure. */
+               if ((res = (s_cgi *)malloc (sizeof (s_cgi))) == NULL)
+                   return NULL;
+               res->vars = NULL;
+               res->cookies = cookies;
+               res->files = NULL;
+               
+           } else {
+               return NULL;
            }
-    if (cgiDebugLevel)
-       if (cgiDebugStderr)
-           fprintf (stderr, "%s not found\n", var);
-       else
-           printf ("%s not found<br>\n", var);
+       }
+
+    if (!res->vars && !res->cookies && !res->files) {
+       free (res);
+       return NULL;
+    }
+
+    return res;
+}
+
+char *cgiGetValue (s_cgi *parms, const char *name)
+{
+    int i;
+
+    if (!parms || !parms->vars)
+       return NULL;
+    for (i=0;parms->vars[i]; i++)
+       if (!strcmp(name,parms->vars[i]->name)) {
+           cgiDebugOutput (1, "%s found as %s", name, parms->vars[i]->value);
+           if (strlen(parms->vars[i]->value) > 0)
+               return parms->vars[i]->value;
+           else
+               return NULL;
+       }
+    cgiDebugOutput (1, "%s not found", name);
+    return NULL;
+}
+
+char **cgiGetVariables (s_cgi *parms)
+{
+    int i;
+    char **res = NULL;
+    int len;
+
+    if (!parms || !parms->vars)
+       return NULL;
+    for (i=0;parms->vars[i]; i++);
+    len = sizeof (char *) * ++i;
+    if ((res = (char **)malloc (len)) == NULL)
+       return NULL;
+    memset (res, 0, len);
+    for (i=0;parms->vars[i]; i++) {
+       len = strlen (parms->vars[i]->name) +1;
+       if ((res[i] = (char *)malloc (len)) == NULL)
+           return NULL;
+       memset (res[i], 0, len);
+       strcpy (res[i], parms->vars[i]->name);
+    }
+    return res;
+}
+
+/* cgiGetFiles
+ *
+ * Returns a list of names of all files.
+ */
+char **cgiGetFiles (s_cgi *parms)
+{
+    int i;
+    char **res = NULL;
+    int len;
+
+    if (!parms || !parms->files)
+       return NULL;
 
+    for (i=0;parms->files[i]; i++);
+    len = sizeof (char *) * ++i;
+    if ((res = (char **)malloc (len)) == NULL)
+       return NULL;
+    memset (res, 0, len);
+    for (i=0;parms->files[i]; i++) {
+       len = strlen (parms->files[i]->name) +1;
+       if ((res[i] = (char *)malloc (len)) == NULL)
+           return NULL;
+       memset (res[i], 0, len);
+       strcpy (res[i], parms->files[i]->name);
+    }
+    return res;
+}
+
+/* cgiGetFile
+ *
+ * Return data structure for CGI file variable
+ */
+s_file *cgiGetFile (s_cgi *parms, const char *name)
+{
+    int i;
+
+    if (!parms || !parms->files)
+       return NULL;
+
+    for (i=0;parms->files[i]; i++)
+       if (!strcmp(name,parms->files[i]->name)) {
+           cgiDebugOutput (1, "%s found as %s", name, parms->files[i]->filename);
+           return parms->files[i];
+       }
+    cgiDebugOutput (1, "%s not found", name);
     return NULL;
 }
 
 void cgiRedirect (const char *url)
 {
     if (url && strlen(url)) {
-       printf ("Content-type: text/html\nContent-length: %d\n", 77+(strlen(url)*2));
-       printf ("Status: 302 Temporal Relocation\n");
-       printf ("Location: %s\n\n", url);
+       printf ("Content-type: text/html\r\nContent-length: %d\r\n", 77+(strlen(url)*2));
+       printf ("Status: 302 Temporal Relocation\r\n");
+       printf ("Location: %s\r\n\r\n", url);
        printf ("<html>\n<body>\nThe page has been moved to <a href=\"%s\">%s</a>\n</body>\n</html>\n", url, url);
     }
 }
 
+void cgiFreeList (char **list)
+{
+    int i;
+
+    for (i=0; list[i] != NULL; i++)
+       free (list[i]);
+    free (list);
+}
+
+void cgiFree (s_cgi *parms)
+{
+    int i;
+
+    if (!parms)
+       return;
+    if (parms->vars) {
+       for (i=0;parms->vars[i]; i++) {
+           if (parms->vars[i]->name)
+               free (parms->vars[i]->name);
+           if (parms->vars[i]->value)
+               free (parms->vars[i]->value);
+           free (parms->vars[i]);
+       }
+       free (parms->vars);
+    }
+    if (parms->cookies) {
+       if (parms->cookies[0]->version)
+           free (parms->cookies[0]->version);
+       for (i=0;parms->cookies[i]; i++) {
+           if (parms->cookies[i]->name)
+               free (parms->cookies[i]->name);
+           if (parms->cookies[i]->value)
+               free (parms->cookies[i]->value);
+           if (parms->cookies[i]->path)
+               free (parms->cookies[i]->path);
+           if (parms->cookies[i]->domain)
+               free (parms->cookies[i]->domain);
+           free (parms->cookies[i]);
+       }
+       free (parms->cookies);
+    }
+    if (parms->files) {
+       for (i=0;parms->files[i]; i++) {
+           if (parms->files[i]->name)
+               free (parms->files[i]->name);
+           if (parms->files[i]->type)
+               free (parms->files[i]->type);
+           if (parms->files[i]->filename)
+               free (parms->files[i]->filename);
+           if (parms->files[i]->tmpfile) {
+               unlink (parms->files[i]->tmpfile);
+               free (parms->files[i]->tmpfile);
+           }
+           free (parms->files[i]);
+       }
+       free (parms->files);
+    }
+    free (parms);
+
+    if (cgiHeaderString) {
+       free (cgiHeaderString);
+       cgiHeaderString = NULL;
+    }
+    if (cgiType) {
+       free (cgiType);
+       cgiType = NULL;
+    }
+}
+
+#ifndef HAVE_STRNDUP
+char *strndup(const char *s, size_t n) {
+    char *fnval;
+
+    if ((fnval = (char *)malloc (n + 1)) == NULL)
+       return NULL;
+    strncpy(fnval, s, n);
+    fnval[n] = '\0';
+    return fnval;
+}
+#endif
+
 /*
  * Local variables:
  *  c-indent-level: 4