aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortenox <as@tenoware.com>2016-01-11 23:57:03 -0800
committertenox <as@tenoware.com>2016-01-11 23:57:03 -0800
commit245eaad379742cc0ba9992c858523664b02102fb (patch)
tree47eb4faed0a498e42aaf927ed24eb66bac601a9e
downloadwfm-1.0.0.tar.gz
initial commit1.0.0
-rw-r--r--Makefile25
-rw-r--r--README57
-rw-r--r--bin2c.c123
-rw-r--r--cgic.c2559
-rw-r--r--cgic.diff102
-rw-r--r--cgic.h232
-rw-r--r--dialogs.c435
-rw-r--r--dir.c607
-rw-r--r--fileio.c518
-rwxr-xr-xicons/adn.gifbin0 -> 49 bytes
-rwxr-xr-xicons/arr.gifbin0 -> 345 bytes
-rwxr-xr-xicons/aup.gifbin0 -> 48 bytes
-rwxr-xr-xicons/clr.gifbin0 -> 55 bytes
-rwxr-xr-xicons/delete.gifbin0 -> 345 bytes
-rwxr-xr-xicons/dir.gifbin0 -> 354 bytes
-rwxr-xr-xicons/dir_up.gifbin0 -> 554 bytes
-rw-r--r--icons/disk.gifbin0 -> 386 bytes
-rwxr-xr-xicons/edit.gifbin0 -> 578 bytes
-rwxr-xr-xicons/exe.gifbin0 -> 354 bytes
-rwxr-xr-xicons/ext.gifbin0 -> 564 bytes
-rwxr-xr-xicons/gen.gifbin0 -> 607 bytes
-rwxr-xr-xicons/home.gifbin0 -> 627 bytes
-rwxr-xr-xicons/img.gifbin0 -> 572 bytes
-rwxr-xr-xicons/iso.gifbin0 -> 617 bytes
-rwxr-xr-xicons/lck.gifbin0 -> 364 bytes
-rwxr-xr-xicons/med.gifbin0 -> 586 bytes
-rwxr-xr-xicons/mkdir.gifbin0 -> 601 bytes
-rwxr-xr-xicons/mkfile.gifbin0 -> 604 bytes
-rwxr-xr-xicons/move.gifbin0 -> 340 bytes
-rwxr-xr-xicons/net.gifbin0 -> 352 bytes
-rwxr-xr-xicons/off.gifbin0 -> 598 bytes
-rwxr-xr-xicons/pdf.gifbin0 -> 605 bytes
-rwxr-xr-xicons/readonly.gifbin0 -> 588 bytes
-rwxr-xr-xicons/readwrite.gifbin0 -> 567 bytes
-rwxr-xr-xicons/reload.gifbin0 -> 604 bytes
-rwxr-xr-xicons/rename.gifbin0 -> 335 bytes
-rwxr-xr-xicons/time.gifbin0 -> 381 bytes
-rwxr-xr-xicons/txt.gifbin0 -> 376 bytes
-rwxr-xr-xicons/user.gifbin0 -> 373 bytes
-rwxr-xr-xicons/ver.gifbin0 -> 591 bytes
-rwxr-xr-xicons/wfmicon.gifbin0 -> 622 bytes
-rwxr-xr-xicons/zip.gifbin0 -> 382 bytes
-rw-r--r--md5.c381
-rw-r--r--md5.h91
-rwxr-xr-xmkicons.sh11
-rw-r--r--wfm.c462
-rw-r--r--wfm.cfg28
-rw-r--r--wfm.h149
48 files changed, 5780 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3753a13
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,25 @@
+CC=gcc -O3 -Wall
+OB=wfm.o dir.o dialogs.o fileio.o cgic.o md5.o
+
+all: wfm
+
+wfm: ${OB}
+ ${CC} ${OB} -o wfm
+ @strip wfm
+ @du -h wfm
+ cp wfm ../web/tenox/app
+
+wfm.o: wfm.c wfmiconres.h wfm.h
+
+wfmiconres.h: bin2c
+ bash ./mkicons.sh
+
+bin2c: bin2c.c
+ ${CC} -o bin2c bin2c.c
+
+.c.o:
+ ${CC} -c $<
+
+clean:
+ rm -f *.o wfm wfmicon*.h bin2c
+
diff --git a/README b/README
new file mode 100644
index 0000000..167cb21
--- /dev/null
+++ b/README
@@ -0,0 +1,57 @@
+WFM - Web File Manager
+======================
+WFM is a web file management application. It allows to upload, download,
+rename, delete files and organize directory / folder structure using a
+standard web browser with CGI.
+
+This program is written using portable C code and compiles natively
+for many flavors of Unix. The output is a pure W3C certified HTML 4.01
+and doesn't require JavaScript support in the browser. WFM will work in
+most HTTP 1.1 web servers with CGI support. Due to compact size and
+lightning fast execution time it's ideal for small and embedded systems.
+
+
+History
+=======
+WFM has been conceived around 1994 as a perl cgi script for CERN httpd
+server to allow uploading data files by field support engineers. Later
+rewritten in C language, when CGIC library and Apache httpd were released
+around 1996. Up to 2015 WFM has been a closed source commercial application,
+supported by a few large enterprise and smaller corporation users. WFM is
+now released as open source.
+
+
+Installation
+============
+WFM binary is self contained including all icons/images. You only need
+to copy the compiled wfm binary (with any name) to your cgi execution
+directory. Include a config file of the same name as the binary file plus
+.cfg extension. Example:
+
+/u/foo/web/cgi-bin
+ wfm
+ wfm.cfg
+
+Edit the cfg file according to your needs.
+
+Point your browser to http://x.x.x.x/cgi-bin/wfm and you are done.
+
+Configuration
+=============
+
+Copyrights
+==========
+Implemented by Antoni Sawicki
+CGIC Library by Thomas Boutell
+Server Side RFC 1321 implementation by L. Peter Deutsch
+Client Side RFC 1321 implementation by Paul Johnston
+Icons by Yusuke Kamiyamane
+Copyright (c) 1994-2015 by Antoni Sawicki
+Copyright (c) 1996-2011 by Thomas Boutell and Boutell.Com, Inc.
+Copyright (c) 2002 by Aladdin Enterprises
+Copyright (c) 1999-2009 by Paul Johnston
+Copyright (c) 2010 by Yusuke Kamiyamane
+
+License
+=======
+BSD
diff --git a/bin2c.c b/bin2c.c
new file mode 100644
index 0000000..bc052ec
--- /dev/null
+++ b/bin2c.c
@@ -0,0 +1,123 @@
+// bin2c.c
+//
+// convert a binary file into a C source vector
+//
+// THE "BEER-WARE LICENSE" (Revision 3.1415):
+// sandro AT sigala DOT it wrote this file. As long as you retain this notice you can do
+// whatever you want with this stuff. If we meet some day, and you think this stuff is
+// worth it, you can buy me a beer in return. Sandro Sigala
+//
+// syntax: bin2c [-c] [-z] <input_file> <output_file>
+//
+// -c add the "const" keyword to definition
+// -z terminate the array with a zero (useful for embedded C strings)
+//
+// examples:
+// bin2c -c myimage.png myimage_png.cpp
+// bin2c -z sometext.txt sometext_txt.cpp
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif
+
+int useconst = 0;
+int zeroterminated = 0;
+
+int myfgetc(FILE *f)
+{
+ int c = fgetc(f);
+ if (c == EOF && zeroterminated)
+ {
+ zeroterminated = 0;
+ return 0;
+ }
+ return c;
+}
+
+void process(const char *ifname, const char *ofname)
+{
+ FILE *ifile, *ofile;
+ ifile = fopen(ifname, "rb");
+ if (ifile == NULL)
+ {
+ fprintf(stderr, "cannot open %s for reading\n", ifname);
+ exit(1);
+ }
+ ofile = fopen(ofname, "ab");
+ if (ofile == NULL)
+ {
+ fprintf(stderr, "cannot open %s for writing\n", ofname);
+ exit(1);
+ }
+ char buf[PATH_MAX], *p;
+ const char *cp;
+ if ((cp = strrchr(ifname, '/')) != NULL)
+ {
+ ++cp;
+ } else {
+ if ((cp = strrchr(ifname, '\\')) != NULL)
+ ++cp;
+ else
+ cp = ifname;
+ }
+ strcpy(buf, cp);
+ for (p = buf; *p != '\0'; ++p)
+ {
+ if (!isalnum((int)*p))
+ *p = '_';
+ }
+ fprintf(ofile, "static %sunsigned char %s[] = {\n", useconst ? "const " : "", buf);
+ int c, col = 1;
+ while ((c = myfgetc(ifile)) != EOF)
+ {
+ if (col >= 78 - 6)
+ {
+ fputc('\n', ofile);
+ col = 1;
+ }
+ fprintf(ofile, "0x%.2x, ", c);
+ col += 6;
+ }
+ fprintf(ofile, "\n};\n");
+
+ fclose(ifile);
+ fclose(ofile);
+}
+
+void usage(void)
+{
+ fprintf(stderr, "usage: bin2c [-cz] <input_file> <output_file>\n");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ while (argc > 3)
+ {
+ if (!strcmp(argv[1], "-c"))
+ {
+ useconst = 1;
+ --argc;
+ ++argv;
+ } else if (!strcmp(argv[1], "-z"))
+ {
+ zeroterminated = 1;
+ --argc;
+ ++argv;
+ } else {
+ usage();
+ }
+ }
+ if (argc != 3)
+ {
+ usage();
+ }
+ process(argv[1], argv[2]);
+ return 0;
+}
+
diff --git a/cgic.c b/cgic.c
new file mode 100644
index 0000000..3991de2
--- /dev/null
+++ b/cgic.c
@@ -0,0 +1,2559 @@
+/* cgicTempDir is the only setting you are likely to need
+ to change in this file. */
+
+/* Used only in Unix environments, in conjunction with mkstemp().
+ Elsewhere (Windows), temporary files go where the tmpnam()
+ function suggests. If this behavior does not work for you,
+ modify the getTempFileName() function to suit your needs. */
+
+#define cgicTempDir "/tmp"
+
+#if CGICDEBUG
+#define CGICDEBUGSTART \
+ { \
+ FILE *dout; \
+ dout = fopen("/home/boutell/public_html/debug", "a"); \
+
+#define CGICDEBUGEND \
+ fclose(dout); \
+ }
+#else /* CGICDEBUG */
+#define CGICDEBUGSTART
+#define CGICDEBUGEND
+#endif /* CGICDEBUG */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <sys/shm.h>
+
+#ifdef WIN32
+#include <io.h>
+
+/* cgic 2.01 */
+#include <fcntl.h>
+
+#else
+#include <unistd.h>
+#endif /* WIN32 */
+#include "cgic.h"
+
+#define cgiStrEq(a, b) (!strcmp((a), (b)))
+
+char *cgiServerSoftware;
+char *cgiServerName;
+char *cgiGatewayInterface;
+char *cgiServerProtocol;
+char *cgiServerPort;
+char *cgiRequestMethod;
+char *cgiPathInfo;
+char *cgiPathTranslated;
+char *cgiScriptName;
+char *cgiQueryString;
+char *cgiRemoteHost;
+char *cgiRemoteAddr;
+char *cgiAuthType;
+char *cgiRemoteUser;
+char *cgiRemoteIdent;
+char cgiContentTypeData[1024];
+char *cgiContentType = cgiContentTypeData;
+char *cgiMultipartBoundary;
+char *cgiCookie;
+int cgiContentLength;
+char *cgiAccept;
+char *cgiUserAgent;
+char *cgiReferrer;
+
+char *shm_addr='\0';
+int shm_key=0;
+int shm_id=0;
+#define SHM_SIZE 16
+
+
+FILE *cgiIn;
+FILE *cgiOut;
+
+/* True if CGI environment was restored from a file. */
+static int cgiRestored = 0;
+
+static void cgiGetenv(char **s, char *var);
+
+typedef enum {
+ cgiParseSuccess,
+ cgiParseMemory,
+ cgiParseIO
+} cgiParseResultType;
+
+/* One form entry, consisting of an attribute-value pair,
+ and an optional filename and content type. All of
+ these are guaranteed to be valid null-terminated strings,
+ which will be of length zero in the event that the
+ field is not present, with the exception of tfileName
+ which will be null when 'in' is null. DO NOT MODIFY THESE
+ VALUES. Make local copies if modifications are desired. */
+
+typedef struct cgiFormEntryStruct {
+ char *attr;
+ /* value is populated for regular form fields only.
+ For file uploads, it points to an empty string, and file
+ upload data should be read from the file tfileName. */
+ char *value;
+ /* When fileName is not an empty string, tfileName is not null,
+ and 'value' points to an empty string. */
+ /* Valid for both files and regular fields; does not include
+ terminating null of regular fields. */
+ int valueLength;
+ char *fileName;
+ char *contentType;
+ /* Temporary file name for working storage of file uploads. */
+ char *tfileName;
+ struct cgiFormEntryStruct *next;
+} cgiFormEntry;
+
+/* The first form entry. */
+static cgiFormEntry *cgiFormEntryFirst;
+
+static cgiParseResultType cgiParseGetFormInput();
+static cgiParseResultType cgiParsePostFormInput();
+static cgiParseResultType cgiParsePostMultipartInput();
+static cgiParseResultType cgiParseFormInput(char *data, int length);
+static void cgiSetupConstants();
+static void cgiFreeResources();
+static int cgiStrEqNc(char *s1, char *s2);
+static int cgiStrBeginsNc(char *s1, char *s2);
+
+/* Dirty little hack to get file upload progress - Part 4: SHM Cleanup */
+void shmcleanup(void) {
+ if(shm_id)
+ shmctl(shm_id, IPC_RMID, NULL);
+}
+
+int main(int argc, char *argv[]) {
+ int result;
+ char *cgiContentLengthString;
+ char *e;
+ cgiSetupConstants();
+ cgiGetenv(&cgiServerSoftware, "SERVER_SOFTWARE");
+ cgiGetenv(&cgiServerName, "SERVER_NAME");
+ cgiGetenv(&cgiGatewayInterface, "GATEWAY_INTERFACE");
+ cgiGetenv(&cgiServerProtocol, "SERVER_PROTOCOL");
+ cgiGetenv(&cgiServerPort, "SERVER_PORT");
+ cgiGetenv(&cgiRequestMethod, "REQUEST_METHOD");
+ cgiGetenv(&cgiPathInfo, "PATH_INFO");
+ cgiGetenv(&cgiPathTranslated, "PATH_TRANSLATED");
+ cgiGetenv(&cgiScriptName, "SCRIPT_NAME");
+ cgiGetenv(&cgiQueryString, "QUERY_STRING");
+ cgiGetenv(&cgiRemoteHost, "REMOTE_HOST");
+ cgiGetenv(&cgiRemoteAddr, "REMOTE_ADDR");
+ cgiGetenv(&cgiAuthType, "AUTH_TYPE");
+ cgiGetenv(&cgiRemoteUser, "REMOTE_USER");
+ cgiGetenv(&cgiRemoteIdent, "REMOTE_IDENT");
+ /* 2.0: the content type string needs to be parsed and modified, so
+ copy it to a buffer. */
+ e = getenv("CONTENT_TYPE");
+ if (e) {
+ if (strlen(e) < sizeof(cgiContentTypeData)) {
+ strcpy(cgiContentType, e);
+ } else {
+ /* Truncate safely in the event of what is almost certainly
+ a hack attempt */
+ strncpy(cgiContentType, e, sizeof(cgiContentTypeData));
+ cgiContentType[sizeof(cgiContentTypeData) - 1] = '\0';
+ }
+ } else {
+ cgiContentType[0] = '\0';
+ }
+ /* Never null */
+ cgiMultipartBoundary = "";
+ /* 2.0: parse semicolon-separated additional parameters of the
+ content type. The one we're interested in is 'boundary'.
+ We discard the rest to make cgiContentType more useful
+ to the typical programmer. */
+ if (strchr(cgiContentType, ';')) {
+ char *sat = strchr(cgiContentType, ';');
+ while (sat) {
+ *sat = '\0';
+ sat++;
+ while (isspace((int)*sat)) {
+ sat++;
+ }
+ if (cgiStrBeginsNc(sat, "boundary=")) {
+ char *s;
+ cgiMultipartBoundary = sat + strlen("boundary=");
+ s = cgiMultipartBoundary;
+ while ((*s) && (!isspace((int)*s))) {
+ s++;
+ }
+ *s = '\0';
+ break;
+ } else {
+ sat = strchr(sat, ';');
+ }
+ }
+ }
+ cgiGetenv(&cgiContentLengthString, "CONTENT_LENGTH");
+ cgiContentLength = atoi(cgiContentLengthString);
+ cgiGetenv(&cgiAccept, "HTTP_ACCEPT");
+ cgiGetenv(&cgiUserAgent, "HTTP_USER_AGENT");
+ cgiGetenv(&cgiReferrer, "HTTP_REFERER");
+ cgiGetenv(&cgiCookie, "HTTP_COOKIE");
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "%d\n", cgiContentLength);
+ fprintf(dout, "%s\n", cgiRequestMethod);
+ fprintf(dout, "%s\n", cgiContentType);
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+#ifdef WIN32
+ /* 1.07: Must set stdin and stdout to binary mode */
+ /* 2.0: this is particularly crucial now and must not be removed */
+ _setmode( _fileno( stdin ), _O_BINARY );
+ _setmode( _fileno( stdout ), _O_BINARY );
+#endif /* WIN32 */
+ cgiFormEntryFirst = 0;
+ cgiIn = stdin;
+ cgiOut = stdout;
+ cgiRestored = 0;
+
+
+ /* These five lines keep compilers from
+ producing warnings that argc and argv
+ are unused. They have no actual function. */
+ if (argc) {
+ if (argv[0]) {
+ cgiRestored = 0;
+ }
+ }
+
+
+ if (cgiStrEqNc(cgiRequestMethod, "post")) {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "POST recognized\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ if (cgiStrEqNc(cgiContentType, "application/x-www-form-urlencoded")) {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "Calling PostFormInput\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ if (cgiParsePostFormInput() != cgiParseSuccess) {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "PostFormInput failed\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ cgiFreeResources();
+ return -1;
+ }
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "PostFormInput succeeded\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ } else if (cgiStrEqNc(cgiContentType, "multipart/form-data")) {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "Calling PostMultipartInput\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ if (cgiParsePostMultipartInput() != cgiParseSuccess) {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "PostMultipartInput failed\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ cgiFreeResources();
+ return -1;
+ }
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "PostMultipartInput succeeded\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ }
+ } else if (cgiStrEqNc(cgiRequestMethod, "get")) {
+ /* The spec says this should be taken care of by
+ the server, but... it isn't */
+ cgiContentLength = strlen(cgiQueryString);
+ if (cgiParseGetFormInput() != cgiParseSuccess) {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "GetFormInput failed\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ cgiFreeResources();
+ return -1;
+ } else {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "GetFormInput succeeded\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ }
+ }
+ result = cgiMain();
+ cgiFreeResources();
+ return result;
+}
+
+static void cgiGetenv(char **s, char *var){
+ *s = getenv(var);
+ if (!(*s)) {
+ *s = "";
+ }
+}
+
+static cgiParseResultType cgiParsePostFormInput() {
+ char *input;
+ cgiParseResultType result;
+ if (!cgiContentLength) {
+ return cgiParseSuccess;
+ }
+ input = (char *) malloc(cgiContentLength);
+ if (!input) {
+ return cgiParseMemory;
+ }
+ if (((int) fread(input, 1, cgiContentLength, cgiIn))
+ != cgiContentLength)
+ {
+ return cgiParseIO;
+ }
+ result = cgiParseFormInput(input, cgiContentLength);
+ free(input);
+ return result;
+}
+
+/* 2.0: A virtual datastream supporting putback of
+ enough characters to handle multipart boundaries easily.
+ A simple memset(&mp, 0, sizeof(mp)) is suitable initialization. */
+
+typedef struct {
+ /* Buffer for putting characters back */
+ char putback[1024];
+ /* Position in putback from which next character will be read.
+ If readPos == writePos, then next character should
+ come from cgiIn. */
+ int readPos;
+ /* Position in putback to which next character will be put back.
+ If writePos catches up to readPos, as opposed to the other
+ way around, the stream no longer functions properly.
+ Calling code must guarantee that no more than
+ sizeof(putback) bytes are put back at any given time. */
+ int writePos;
+ /* Offset in the virtual datastream; can be compared
+ to cgiContentLength */
+ int offset;
+} mpStream, *mpStreamPtr;
+
+int mpRead(mpStreamPtr mpp, char *buffer, int len)
+{
+ int ilen = len;
+ int got = 0;
+ while (len) {
+ if (mpp->readPos != mpp->writePos) {
+ *buffer++ = mpp->putback[mpp->readPos++];
+ mpp->readPos %= sizeof(mpp->putback);
+ got++;
+ len--;
+ } else {
+ break;
+ }
+ }
+ /* Refuse to read past the declared length in order to
+ avoid deadlock */
+ if (len > (cgiContentLength - mpp->offset)) {
+ len = cgiContentLength - mpp->offset;
+ }
+ if (len) {
+ int fgot = fread(buffer, 1, len, cgiIn);
+ if (fgot >= 0) {
+ mpp->offset += (got + fgot);
+ return got + fgot;
+ } else if (got > 0) {
+ mpp->offset += got;
+ return got;
+ } else {
+ /* EOF or error */
+ return fgot;
+ }
+ } else if (got) {
+ return got;
+ } else if (ilen) {
+ return EOF;
+ } else {
+ /* 2.01 */
+ return 0;
+ }
+}
+
+void mpPutBack(mpStreamPtr mpp, char *data, int len)
+{
+ mpp->offset -= len;
+ while (len) {
+ mpp->putback[mpp->writePos++] = *data++;
+ mpp->writePos %= sizeof(mpp->putback);
+ len--;
+ }
+}
+
+/* This function copies the body to outf if it is not null, otherwise to
+ a newly allocated character buffer at *outP, which will be null
+ terminated; if both outf and outP are null the body is not stored.
+ If bodyLengthP is not null, the size of the body in bytes is stored
+ to *bodyLengthP, not including any terminating null added to *outP.
+ If 'first' is nonzero, a preceding newline is not expected before
+ the boundary. If 'first' is zero, a preceding newline is expected.
+ Upon return mpp is positioned after the boundary and its trailing
+ newline, if any; if the boundary is followed by -- the next two
+ characters read after this function returns will be --. Upon error,
+ if outP is not null, *outP is a null pointer; *bodyLengthP
+ is set to zero. Returns cgiParseSuccess, cgiParseMemory
+ or cgiParseIO. */
+
+static cgiParseResultType afterNextBoundary(mpStreamPtr mpp,
+ FILE *outf,
+ char **outP,
+ int *bodyLengthP,
+ int first
+ );
+
+static int readHeaderLine(
+ mpStreamPtr mpp,
+ char *attr,
+ int attrSpace,
+ char *value,
+ int valueSpace);
+
+static void decomposeValue(char *value,
+ char *mvalue, int mvalueSpace,
+ char **argNames,
+ char **argValues,
+ int argValueSpace);
+
+/* tfileName must be 1024 bytes to ensure adequacy on
+ win32 (1024 exceeds the maximum path length and
+ certainly exceeds observed behavior of _tmpnam).
+ May as well also be 1024 bytes on Unix, although actual
+ length is strlen(cgiTempDir) + a short unique pattern. */
+
+static cgiParseResultType getTempFileName(char *tfileName);
+
+static cgiParseResultType cgiParsePostMultipartInput() {
+ cgiParseResultType result;
+ cgiFormEntry *n = 0, *l = 0;
+ int got;
+ int sig;
+ FILE *outf = 0;
+ char *out = 0;
+ char tfileName[1024];
+ mpStream mp;
+ mpStreamPtr mpp = &mp;
+ memset(&mp, 0, sizeof(mp));
+ if (!cgiContentLength) {
+ return cgiParseSuccess;
+ }
+ /* Read first boundary, including trailing newline */
+ result = afterNextBoundary(mpp, 0, 0, 0, 1);
+ if (result == cgiParseIO) {
+ /* An empty submission is not necessarily an error */
+ return cgiParseSuccess;
+ } else if (result != cgiParseSuccess) {
+ return result;
+ }
+ while (1) {
+ char d[1024];
+ char fvalue[1024];
+ char fname[1024];
+ int bodyLength = 0;
+ char ffileName[1024];
+ char fcontentType[1024];
+ char attr[1024];
+ char value[1024];
+ fvalue[0] = 0;
+ fname[0] = 0;
+ ffileName[0] = 0;
+ fcontentType[0] = 0;
+ out = 0;
+ outf = 0;
+ /* Check for EOF */
+ got = mpRead(mpp, d, 2);
+ if (got < 2) {
+ /* Crude EOF */
+ break;
+ }
+ if ((d[0] == '-') && (d[1] == '-')) {
+ /* Graceful EOF */
+ break;
+ }
+ mpPutBack(mpp, d, 2);
+ /* Read header lines until end of header */
+ while (readHeaderLine(
+ mpp, attr, sizeof(attr), value, sizeof(value)))
+ {
+ char *argNames[3];
+ char *argValues[2];
+ /* Content-Disposition: form-data;
+ name="test"; filename="googley.gif" */
+ if (cgiStrEqNc(attr, "Content-Disposition")) {
+ argNames[0] = "name";
+ argNames[1] = "filename";
+ argNames[2] = 0;
+ argValues[0] = fname;
+ argValues[1] = ffileName;
+ decomposeValue(value,
+ fvalue, sizeof(fvalue),
+ argNames,
+ argValues,
+ 1024);
+ } else if (cgiStrEqNc(attr, "Content-Type")) {
+ argNames[0] = 0;
+ decomposeValue(value,
+ fcontentType, sizeof(fcontentType),
+ argNames,
+ 0,
+ 0);
+ }
+ }
+ if (!cgiStrEqNc(fvalue, "form-data")) {
+ /* Not form data */
+ continue;
+ }
+ /* Body is everything from here until the next
+ boundary. So, set it aside and move past boundary.
+ If a filename was submitted as part of the
+ disposition header, store to a temporary file.
+ Otherwise, store to a memory buffer (it is
+ presumably a regular form field). */
+ if (strlen(ffileName)) {
+ if (getTempFileName(tfileName) != cgiParseSuccess) {
+ return cgiParseIO;
+ }
+ outf = fopen(tfileName, "w+b");
+ /* Dirty little hack to get file upload progress - Part 1: Initialize */
+ if (cgiFormInteger("upload_id", &shm_key, 0) == cgiFormSuccess && shm_key) {
+ if ((shm_id = shmget(shm_key, 16, IPC_CREAT | 0666)) > 0) {
+ for(sig=1; sig<=31; sig++)
+ signal(sig, (void*)shmcleanup);
+ if ((shm_addr = shmat(shm_id, NULL, 0)) > 0)
+ *shm_addr='\0';
+ }
+ }
+ } else {
+ outf = 0;
+ tfileName[0] = '\0';
+ }
+ result = afterNextBoundary(mpp, outf, &out, &bodyLength, 0);
+ /* Dirty little hack to get file upload progress - Part 2: Clean up */
+ if(shm_addr)
+ shmdt(shm_addr);
+ if(shm_id)
+ shmctl(shm_id, IPC_RMID, NULL);
+ if (result != cgiParseSuccess) {
+ /* Lack of a boundary here is an error. */
+ if (outf) {
+ fclose(outf);
+ unlink(tfileName);
+ }
+ if (out) {
+ free(out);
+ }
+ return result;
+ }
+ /* OK, we have a new pair, add it to the list. */
+ n = (cgiFormEntry *) malloc(sizeof(cgiFormEntry));
+ if (!n) {
+ goto outOfMemory;
+ }
+ memset(n, 0, sizeof(cgiFormEntry));
+ /* 2.01: one of numerous new casts required
+ to please C++ compilers */
+ n->attr = (char *) malloc(strlen(fname) + 1);
+ if (!n->attr) {
+ goto outOfMemory;
+ }
+ strcpy(n->attr, fname);
+ if (out) {
+ n->value = out;
+ out = 0;
+ } else if (outf) {
+ n->value = (char *) malloc(1);
+ if (!n->value) {
+ goto outOfMemory;
+ }
+ n->value[0] = '\0';
+ fclose(outf);
+ }
+ n->valueLength = bodyLength;
+ n->next = 0;
+ if (!l) {
+ cgiFormEntryFirst = n;
+ } else {
+ l->next = n;
+ }
+ n->fileName = (char *) malloc(strlen(ffileName) + 1);
+ if (!n->fileName) {
+ goto outOfMemory;
+ }
+ strcpy(n->fileName, ffileName);
+ n->contentType = (char *) malloc(strlen(fcontentType) + 1);
+ if (!n->contentType) {
+ goto outOfMemory;
+ }
+ strcpy(n->contentType, fcontentType);
+ n->tfileName = (char *) malloc(strlen(tfileName) + 1);
+ if (!n->tfileName) {
+ goto outOfMemory;
+ }
+ strcpy(n->tfileName, tfileName);
+
+ l = n;
+ }
+ return cgiParseSuccess;
+outOfMemory:
+ if (n) {
+ if (n->attr) {
+ free(n->attr);
+ }
+ if (n->value) {
+ free(n->value);
+ }
+ if (n->fileName) {
+ free(n->fileName);
+ }
+ if (n->tfileName) {
+ free(n->tfileName);
+ }
+ if (n->contentType) {
+ free(n->contentType);
+ }
+ free(n);
+ }
+ if (out) {
+ free(out);
+ }
+ if (outf) {
+ fclose(outf);
+ unlink(tfileName);
+ }
+ return cgiParseMemory;
+}
+
+static cgiParseResultType getTempFileName(char *tfileName)
+{
+#ifndef WIN32
+ /* Unix. Use the robust 'mkstemp' function to create
+ a temporary file that is truly unique, with
+ permissions that are truly safe. The
+ fopen-for-write destroys any bogus information
+ written by potential hackers during the brief
+ window between the file's creation and the
+ chmod call (glibc 2.0.6 and lower might
+ otherwise have allowed this). */
+ int outfd;
+ strcpy(tfileName, cgicTempDir "/cgicXXXXXX");
+ outfd = mkstemp(tfileName);
+ if (outfd == -1) {
+ return cgiParseIO;
+ }
+ close(outfd);
+ /* Fix the permissions */
+ if (chmod(tfileName, 0600) != 0) {
+ unlink(tfileName);
+ return cgiParseIO;
+ }
+#else
+ /* Non-Unix. Do what we can. */
+ if (!tmpnam(tfileName)) {
+ return cgiParseIO;
+ }
+#endif
+ return cgiParseSuccess;
+}
+
+
+#define APPEND(string, char) \
+ { \
+ if ((string##Len + 1) < string##Space) { \
+ string[string##Len++] = (char); \
+ } \
+ }
+
+#define RAPPEND(string, ch) \
+ { \
+ if ((string##Len + 1) == string##Space) { \
+ char *sold = string; \
+ string##Space *= 2; \
+ string = (char *) realloc(string, string##Space); \
+ if (!string) { \
+ string = sold; \
+ goto outOfMemory; \
+ } \
+ } \
+ string[string##Len++] = (ch); \
+ }
+
+#define BAPPEND(ch) \
+ { \
+ if (outf) { \
+ putc(ch, outf); \
+ outLen++; \
+ } else if (out) { \
+ RAPPEND(out, ch); \
+ } \
+ }
+
+cgiParseResultType afterNextBoundary(mpStreamPtr mpp, FILE *outf, char **outP,
+ int *bodyLengthP, int first)
+{
+ int outLen = 0;
+ int outSpace = 256;
+ char *out = 0;
+ cgiParseResultType result;
+ int boffset;
+ int got;
+ off_t tot=0;
+ char d[2];
+ /* This is large enough, because the buffer into which the
+ original boundary string is fetched is shorter by more
+ than four characters due to the space required for
+ the attribute name */
+ char workingBoundaryData[1024];
+ char *workingBoundary = workingBoundaryData;
+ int workingBoundaryLength;
+ if ((!outf) && (outP)) {
+ out = (char *) malloc(outSpace);
+ if (!out) {
+ goto outOfMemory;
+ }
+ }
+ boffset = 0;
+ sprintf(workingBoundaryData, "\r\n--%s", cgiMultipartBoundary);
+ if (first) {
+ workingBoundary = workingBoundaryData + 2;
+ }
+ workingBoundaryLength = strlen(workingBoundary);
+ while (1) {
+ got = mpRead(mpp, d, 1);
+ /* Dirty little hack to get file upload progress - Part 3: Set value */
+ if(outf && shm_addr)
+ snprintf(shm_addr, SHM_SIZE, "%.1f M", (float)(tot+=got)/1024/1024);
+ if (got != 1) {
+ /* 2.01: cgiParseIO, not cgiFormIO */
+ result = cgiParseIO;
+ goto error;
+ }
+ if (d[0] == workingBoundary[boffset]) {
+ /* We matched the next byte of the boundary.
+ Keep track of our progress into the
+ boundary and don't emit anything. */
+ boffset++;
+ if (boffset == workingBoundaryLength) {
+ break;
+ }
+ } else if (boffset > 0) {
+ /* We matched part, but not all, of the
+ boundary. Now we have to be careful:
+ put back all except the first
+ character and try again. The
+ real boundary could begin in the
+ middle of a false match. We can
+ emit the first character only so far. */
+ BAPPEND(workingBoundary[0]);
+ mpPutBack(mpp,
+ workingBoundary + 1, boffset - 1);
+ mpPutBack(mpp, d, 1);
+ boffset = 0;
+ } else {
+ /* Not presently in the middle of a boundary
+ match; just emit the character. */
+ BAPPEND(d[0]);
+ }
+ }
+ /* Read trailing newline or -- EOF marker. A literal EOF here
+ would be an error in the input stream. */
+ got = mpRead(mpp, d, 2);
+ if (got != 2) {
+ result = cgiParseIO;
+ goto error;
+ }
+ if ((d[0] == '\r') && (d[1] == '\n')) {
+ /* OK, EOL */
+ } else if (d[0] == '-') {
+ /* Probably EOF, but we check for
+ that later */
+ mpPutBack(mpp, d, 2);
+ }
+ if (out && outSpace) {
+ char *oout = out;
+ out[outLen] = '\0';
+ out = (char *) realloc(out, outLen + 1);
+ if (!out) {
+ /* Surprising if it happens; and not fatal! We were
+ just trying to give some space back. We can
+ keep it if we have to. */
+ out = oout;
+ }
+ *outP = out;
+ }
+ if (bodyLengthP) {
+ *bodyLengthP = outLen;
+ }
+ return cgiParseSuccess;
+outOfMemory:
+ result = cgiParseMemory;
+ if (outP) {
+ if (out) {
+ free(out);
+ }
+ *outP = '\0';
+ }
+error:
+ if (bodyLengthP) {
+ *bodyLengthP = 0;
+ }
+ if (out) {
+ free(out);
+ }
+ if (outP) {
+ *outP = 0;
+ }
+ return result;
+}
+
+static void decomposeValue(char *value,
+ char *mvalue, int mvalueSpace,
+ char **argNames,
+ char **argValues,
+ int argValueSpace)
+{
+ char argName[1024];
+ int argNameSpace = sizeof(argName);
+ int argNameLen = 0;
+ int mvalueLen = 0;
+ char *argValue;
+ int argNum = 0;
+ while (argNames[argNum]) {
+ if (argValueSpace) {
+ argValues[argNum][0] = '\0';
+ }
+ argNum++;
+ }
+ while (isspace((int)*value)) {
+ value++;
+ }
+ /* Quoted mvalue */
+ if (*value == '\"') {
+ value++;
+ while ((*value) && (*value != '\"')) {
+ APPEND(mvalue, *value);
+ value++;
+ }
+ while ((*value) && (*value != ';')) {
+ value++;
+ }
+ } else {
+ /* Unquoted mvalue */
+ while ((*value) && (*value != ';')) {
+ APPEND(mvalue, *value);
+ value++;
+ }
+ }
+ if (mvalueSpace) {
+ mvalue[mvalueLen] = '\0';
+ }
+ while (*value == ';') {
+ int argNum;
+ int argValueLen = 0;
+ /* Skip the ; between parameters */
+ value++;
+ /* Now skip leading whitespace */
+ while ((*value) && (isspace((int)*value))) {
+ value++;
+ }
+ /* Now read the parameter name */
+ argNameLen = 0;
+ while ((*value) && (isalnum((int)*value))) {
+ APPEND(argName, *value);
+ value++;
+ }
+ if (argNameSpace) {
+ argName[argNameLen] = '\0';
+ }
+ while ((*value) && isspace((int)*value)) {
+ value++;
+ }
+ if (*value != '=') {
+ /* Malformed line */
+ return;
+ }
+ value++;
+ while ((*value) && isspace((int)*value)) {
+ value++;
+ }
+ /* Find the parameter in the argument list, if present */
+ argNum = 0;
+ argValue = 0;
+ while (argNames[argNum]) {
+ if (cgiStrEqNc(argName, argNames[argNum])) {
+ argValue = argValues[argNum];
+ break;
+ }
+ argNum++;
+ }
+ /* Finally, read the parameter value */
+ if (*value == '\"') {
+ value++;
+ while ((*value) && (*value != '\"')) {
+ if (argValue) {
+ APPEND(argValue, *value);
+ }
+ value++;
+ }
+ while ((*value) && (*value != ';')) {
+ value++;
+ }
+ } else {
+ /* Unquoted value */
+ while ((*value) && (*value != ';')) {
+ if (argNames[argNum]) {
+ APPEND(argValue, *value);
+ }
+ value++;
+ }
+ }
+ if (argValueSpace) {
+ argValue[argValueLen] = '\0';
+ }
+ }
+}
+
+static int readHeaderLine(
+ mpStreamPtr mpp,
+ char *attr,
+ int attrSpace,
+ char *value,
+ int valueSpace)
+{
+ int attrLen = 0;
+ int valueLen = 0;
+ int valueFound = 0;
+ while (1) {
+ char d[1];
+ int got = mpRead(mpp, d, 1);
+ if (got != 1) {
+ return 0;
+ }
+ if (d[0] == '\r') {
+ got = mpRead(mpp, d, 1);
+ if (got == 1) {
+ if (d[0] == '\n') {
+ /* OK */
+ } else {
+ mpPutBack(mpp, d, 1);
+ }
+ }
+ break;
+ } else if (d[0] == '\n') {
+ break;
+ } else if ((d[0] == ':') && attrLen) {
+ valueFound = 1;
+ while (mpRead(mpp, d, 1) == 1) {
+ if (!isspace((int)d[0])) {
+ mpPutBack(mpp, d, 1);
+ break;
+ }
+ }
+ } else if (!valueFound) {
+ if (!isspace((int)*d)) {
+ if (attrLen < (attrSpace - 1)) {
+ attr[attrLen++] = *d;
+ }
+ }
+ } else if (valueFound) {
+ if (valueLen < (valueSpace - 1)) {
+ value[valueLen++] = *d;
+ }
+ }
+ }
+ if (attrSpace) {
+ attr[attrLen] = '\0';
+ }
+ if (valueSpace) {
+ value[valueLen] = '\0';
+ }
+ if (attrLen && valueLen) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static cgiParseResultType cgiParseGetFormInput() {
+ return cgiParseFormInput(cgiQueryString, cgiContentLength);
+}
+
+typedef enum {
+ cgiEscapeRest,
+ cgiEscapeFirst,
+ cgiEscapeSecond
+} cgiEscapeState;
+
+typedef enum {
+ cgiUnescapeSuccess,
+ cgiUnescapeMemory
+} cgiUnescapeResultType;
+
+static cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);
+
+static cgiParseResultType cgiParseFormInput(char *data, int length) {
+ /* Scan for pairs, unescaping and storing them as they are found. */
+ int pos = 0;
+ cgiFormEntry *n;
+ cgiFormEntry *l = 0;
+ while (pos != length) {
+ int foundEq = 0;
+ int foundAmp = 0;
+ int start = pos;
+ int len = 0;
+ char *attr;
+ char *value;
+ while (pos != length) {
+ if (data[pos] == '=') {
+ foundEq = 1;
+ pos++;
+ break;
+ }
+ pos++;
+ len++;
+ }
+ if (!foundEq) {
+ break;
+ }
+ if (cgiUnescapeChars(&attr, data+start, len)
+ != cgiUnescapeSuccess) {
+ return cgiParseMemory;
+ }
+ start = pos;
+ len = 0;
+ while (pos != length) {
+ if (data[pos] == '&') {
+ foundAmp = 1;
+ pos++;
+ break;
+ }
+ pos++;
+ len++;
+ }
+ /* The last pair probably won't be followed by a &, but
+ that's fine, so check for that after accepting it */
+ if (cgiUnescapeChars(&value, data+start, len)
+ != cgiUnescapeSuccess) {
+ free(attr);
+ return cgiParseMemory;
+ }
+ /* OK, we have a new pair, add it to the list. */
+ n = (cgiFormEntry *) malloc(sizeof(cgiFormEntry));
+ if (!n) {
+ free(attr);
+ free(value);
+ return cgiParseMemory;
+ }
+ n->attr = attr;
+ n->value = value;
+ n->valueLength = strlen(n->value);
+ n->fileName = (char *) malloc(1);
+ if (!n->fileName) {
+ free(attr);
+ free(value);
+ free(n);
+ return cgiParseMemory;
+ }
+ n->fileName[0] = '\0';
+ n->contentType = (char *) malloc(1);
+ if (!n->contentType) {
+ free(attr);
+ free(value);
+ free(n->fileName);
+ free(n);
+ return cgiParseMemory;
+ }
+ n->contentType[0] = '\0';
+ n->tfileName = (char *) malloc(1);
+ if (!n->tfileName) {
+ free(attr);
+ free(value);
+ free(n->fileName);
+ free(n->contentType);
+ free(n);
+ return cgiParseMemory;
+ }
+ n->tfileName[0] = '\0';
+ n->next = 0;
+ if (!l) {
+ cgiFormEntryFirst = n;
+ } else {
+ l->next = n;
+ }
+ l = n;
+ if (!foundAmp) {
+ break;
+ }
+ }
+ return cgiParseSuccess;
+}
+
+static int cgiHexValue[256];
+
+cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len) {
+ char *s;
+ cgiEscapeState escapeState = cgiEscapeRest;
+ int escapedValue = 0;
+ int srcPos = 0;
+ int dstPos = 0;
+ s = (char *) malloc(len + 1);
+ if (!s) {
+ return cgiUnescapeMemory;
+ }
+ while (srcPos < len) {
+ int ch = cp[srcPos];
+ switch (escapeState) {
+ case cgiEscapeRest:
+ if (ch == '%') {
+ escapeState = cgiEscapeFirst;
+ } else if (ch == '+') {
+ s[dstPos++] = ' ';
+ } else {
+ s[dstPos++] = ch;
+ }
+ break;
+ case cgiEscapeFirst:
+ escapedValue = cgiHexValue[ch] << 4;
+ escapeState = cgiEscapeSecond;
+ break;
+ case cgiEscapeSecond:
+ escapedValue += cgiHexValue[ch];
+ s[dstPos++] = escapedValue;
+ escapeState = cgiEscapeRest;
+ break;
+ }
+ srcPos++;
+ }
+ s[dstPos] = '\0';
+ *sp = s;
+ return cgiUnescapeSuccess;
+}
+
+static void cgiSetupConstants() {
+ int i;
+ for (i=0; (i < 256); i++) {
+ cgiHexValue[i] = 0;
+ }
+ cgiHexValue['0'] = 0;
+ cgiHexValue['1'] = 1;
+ cgiHexValue['2'] = 2;
+ cgiHexValue['3'] = 3;
+ cgiHexValue['4'] = 4;
+ cgiHexValue['5'] = 5;
+ cgiHexValue['6'] = 6;
+ cgiHexValue['7'] = 7;
+ cgiHexValue['8'] = 8;
+ cgiHexValue['9'] = 9;
+ cgiHexValue['A'] = 10;
+ cgiHexValue['B'] = 11;
+ cgiHexValue['C'] = 12;
+ cgiHexValue['D'] = 13;
+ cgiHexValue['E'] = 14;
+ cgiHexValue['F'] = 15;
+ cgiHexValue['a'] = 10;
+ cgiHexValue['b'] = 11;
+ cgiHexValue['c'] = 12;
+ cgiHexValue['d'] = 13;
+ cgiHexValue['e'] = 14;
+ cgiHexValue['f'] = 15;
+}
+
+static void cgiFreeResources() {
+ cgiFormEntry *c = cgiFormEntryFirst;
+ cgiFormEntry *n;
+ while (c) {
+ n = c->next;
+ free(c->attr);
+ free(c->value);
+ free(c->fileName);
+ free(c->contentType);
+ if (strlen(c->tfileName)) {
+ unlink(c->tfileName);
+ }
+ free(c->tfileName);
+ free(c);
+ c = n;
+ }
+ /* If the cgi environment was restored from a saved environment,
+ then these are in allocated space and must also be freed */
+ if (cgiRestored) {
+ free(cgiServerSoftware);
+ free(cgiServerName);
+ free(cgiGatewayInterface);
+ free(cgiServerProtocol);
+ free(cgiServerPort);
+ free(cgiRequestMethod);
+ free(cgiPathInfo);
+ free(cgiPathTranslated);
+ free(cgiScriptName);
+ free(cgiQueryString);
+ free(cgiRemoteHost);
+ free(cgiRemoteAddr);
+ free(cgiAuthType);
+ free(cgiRemoteUser);
+ free(cgiRemoteIdent);
+ free(cgiContentType);
+ free(cgiAccept);
+ free(cgiUserAgent);
+ free(cgiReferrer);
+ }
+ /* 2.0: to clean up the environment for cgiReadEnvironment,
+ we must set these correctly */
+ cgiFormEntryFirst = 0;
+ cgiRestored = 0;
+}
+
+static cgiFormResultType cgiFormEntryString(
+ cgiFormEntry *e, char *result, int max, int newlines);
+
+static cgiFormEntry *cgiFormEntryFindFirst(char *name);
+static cgiFormEntry *cgiFormEntryFindNext();
+
+cgiFormResultType cgiFormString(
+ char *name, char *result, int max) {
+ cgiFormEntry *e;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ strcpy(result, "");
+ return cgiFormNotFound;
+ }
+ return cgiFormEntryString(e, result, max, 1);
+}
+
+cgiFormResultType cgiFormFileName(
+ char *name, char *result, int resultSpace)
+{
+ cgiFormEntry *e;
+ int resultLen = 0;
+ char *s;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ strcpy(result, "");
+ return cgiFormNotFound;
+ }
+ s = e->fileName;
+ while (*s) {
+ APPEND(result, *s);
+ s++;
+ }
+ if (resultSpace) {
+ result[resultLen] = '\0';
+ }
+ if (!strlen(e->fileName)) {
+ return cgiFormNoFileName;
+ } else if (((int) strlen(e->fileName)) > (resultSpace - 1)) {
+ return cgiFormTruncated;
+ } else {
+ return cgiFormSuccess;
+ }
+}
+
+cgiFormResultType cgiFormFileContentType(
+ char *name, char *result, int resultSpace)
+{
+ cgiFormEntry *e;
+ int resultLen = 0;
+ char *s;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ if (resultSpace) {
+ result[0] = '\0';
+ }
+ return cgiFormNotFound;
+ }
+ s = e->contentType;
+ while (*s) {
+ APPEND(result, *s);
+ s++;
+ }
+ if (resultSpace) {
+ result[resultLen] = '\0';
+ }
+ if (!strlen(e->contentType)) {
+ return cgiFormNoContentType;
+ } else if (((int) strlen(e->contentType)) > (resultSpace - 1)) {
+ return cgiFormTruncated;
+ } else {
+ return cgiFormSuccess;
+ }
+}
+
+cgiFormResultType cgiFormFileSize(
+ char *name, int *sizeP)
+{
+ cgiFormEntry *e;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ if (sizeP) {
+ *sizeP = 0;
+ }
+ return cgiFormNotFound;
+ } else if (!strlen(e->tfileName)) {
+ if (sizeP) {
+ *sizeP = 0;
+ }
+ return cgiFormNotAFile;
+ } else {
+ if (sizeP) {
+ *sizeP = e->valueLength;
+ }
+ return cgiFormSuccess;
+ }
+}
+
+typedef struct cgiFileStruct {
+ FILE *in;
+} cgiFile;
+
+cgiFormResultType cgiFormFileOpen(
+ char *name, cgiFilePtr *cfpp)
+{
+ cgiFormEntry *e;
+ cgiFilePtr cfp;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ *cfpp = 0;
+ return cgiFormNotFound;
+ }
+ if (!strlen(e->tfileName)) {
+ *cfpp = 0;
+ return cgiFormNotAFile;
+ }
+ cfp = (cgiFilePtr) malloc(sizeof(cgiFile));
+ if (!cfp) {
+ *cfpp = 0;
+ return cgiFormMemory;
+ }
+ cfp->in = fopen(e->tfileName, "rb");
+ if (!cfp->in) {
+ free(cfp);
+ return cgiFormIO;
+ }
+ *cfpp = cfp;
+ return cgiFormSuccess;
+}
+
+cgiFormResultType cgiFormFileRead(
+ cgiFilePtr cfp, char *buffer,
+ int bufferSize, int *gotP)
+{
+ int got = 0;
+ if (!cfp) {
+ return cgiFormOpenFailed;
+ }
+ got = fread(buffer, 1, bufferSize, cfp->in);
+ if (got <= 0) {
+ return cgiFormEOF;
+ }
+ *gotP = got;
+ return cgiFormSuccess;
+}
+
+cgiFormResultType cgiFormFileClose(cgiFilePtr cfp)
+{
+ if (!cfp) {
+ return cgiFormOpenFailed;
+ }
+ fclose(cfp->in);
+ free(cfp);
+ return cgiFormSuccess;
+}
+
+cgiFormResultType cgiFormStringNoNewlines(
+ char *name, char *result, int max) {
+ cgiFormEntry *e;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ strcpy(result, "");
+ return cgiFormNotFound;
+ }
+ return cgiFormEntryString(e, result, max, 0);
+}
+
+cgiFormResultType cgiFormStringMultiple(
+ char *name, char ***result) {
+ char **stringArray;
+ cgiFormEntry *e;
+ int i;
+ int total = 0;
+ /* Make two passes. One would be more efficient, but this
+ function is not commonly used. The select menu and
+ radio box functions are faster. */
+ e = cgiFormEntryFindFirst(name);
+ if (e != 0) {
+ do {
+ total++;
+ } while ((e = cgiFormEntryFindNext()) != 0);
+ }
+ stringArray = (char **) malloc(sizeof(char *) * (total + 1));
+ if (!stringArray) {
+ *result = 0;
+ return cgiFormMemory;
+ }
+ /* initialize all entries to null; the last will stay that way */
+ for (i=0; (i <= total); i++) {
+ stringArray[i] = 0;
+ }
+ /* Now go get the entries */
+ e = cgiFormEntryFindFirst(name);
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "StringMultiple Beginning\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ if (e) {
+ i = 0;
+ do {
+ int max = (int) (strlen(e->value) + 1);
+ stringArray[i] = (char *) malloc(max);
+ if (stringArray[i] == 0) {
+ /* Memory problems */
+ cgiStringArrayFree(stringArray);
+ *result = 0;
+ return cgiFormMemory;
+ }
+ strcpy(stringArray[i], e->value);
+ cgiFormEntryString(e, stringArray[i], max, 1);
+ i++;
+ } while ((e = cgiFormEntryFindNext()) != 0);
+ *result = stringArray;
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "StringMultiple Succeeding\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ return cgiFormSuccess;
+ } else {
+ *result = stringArray;
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "StringMultiple found nothing\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ return cgiFormNotFound;
+ }
+}
+
+cgiFormResultType cgiFormStringSpaceNeeded(
+ char *name, int *result) {
+ cgiFormEntry *e;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ *result = 1;
+ return cgiFormNotFound;
+ }
+ *result = ((int) strlen(e->value)) + 1;
+ return cgiFormSuccess;
+}
+
+static cgiFormResultType cgiFormEntryString(
+ cgiFormEntry *e, char *result, int max, int newlines) {
+ char *dp, *sp;
+ int truncated = 0;
+ int len = 0;
+ int avail = max-1;
+ int crCount = 0;
+ int lfCount = 0;
+ dp = result;
+ sp = e->value;
+ while (1) {
+ int ch;
+ /* 1.07: don't check for available space now.
+ We check for it immediately before adding
+ an actual character. 1.06 handled the
+ trailing null of the source string improperly,
+ resulting in a cgiFormTruncated error. */
+ ch = *sp;
+ /* Fix the CR/LF, LF, CR nightmare: watch for
+ consecutive bursts of CRs and LFs in whatever
+ pattern, then actually output the larger number
+ of LFs. Consistently sane, yet it still allows
+ consecutive blank lines when the user
+ actually intends them. */
+ if ((ch == 13) || (ch == 10)) {
+ if (ch == 13) {
+ crCount++;
+ } else {
+ lfCount++;
+ }
+ } else {
+ if (crCount || lfCount) {
+ int lfsAdd = crCount;
+ if (lfCount > crCount) {
+ lfsAdd = lfCount;
+ }
+ /* Stomp all newlines if desired */
+ if (!newlines) {
+ lfsAdd = 0;
+ }
+ while (lfsAdd) {
+ if (len >= avail) {
+ truncated = 1;
+ break;
+ }
+ *dp = 10;
+ dp++;
+ lfsAdd--;
+ len++;
+ }
+ crCount = 0;
+ lfCount = 0;
+ }
+ if (ch == '\0') {
+ /* The end of the source string */
+ break;
+ }
+ /* 1.06: check available space before adding
+ the character, because a previously added
+ LF may have brought us to the limit */
+ if (len >= avail) {
+ truncated = 1;
+ break;
+ }
+ *dp = ch;
+ dp++;
+ len++;
+ }
+ sp++;
+ }
+ *dp = '\0';
+ if (truncated) {
+ return cgiFormTruncated;
+ } else if (!len) {
+ return cgiFormEmpty;
+ } else {
+ return cgiFormSuccess;
+ }
+}
+
+static int cgiFirstNonspaceChar(char *s);
+
+cgiFormResultType cgiFormInteger(
+ char *name, int *result, int defaultV) {
+ cgiFormEntry *e;
+ int ch;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ *result = defaultV;
+ return cgiFormNotFound;
+ }
+ if (!strlen(e->value)) {
+ *result = defaultV;
+ return cgiFormEmpty;
+ }
+ ch = cgiFirstNonspaceChar(e->value);
+ if (!(isdigit(ch)) && (ch != '-') && (ch != '+')) {
+ *result = defaultV;
+ return cgiFormBadType;
+ } else {
+ *result = atoi(e->value);
+ return cgiFormSuccess;
+ }
+}
+
+cgiFormResultType cgiFormIntegerBounded(
+ char *name, int *result, int min, int max, int defaultV) {
+ cgiFormResultType error = cgiFormInteger(name, result, defaultV);
+ if (error != cgiFormSuccess) {
+ return error;
+ }
+ if (*result < min) {
+ *result = min;
+ return cgiFormConstrained;
+ }
+ if (*result > max) {
+ *result = max;
+ return cgiFormConstrained;
+ }
+ return cgiFormSuccess;
+}
+
+cgiFormResultType cgiFormDouble(
+ char *name, double *result, double defaultV) {
+ cgiFormEntry *e;
+ int ch;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ *result = defaultV;
+ return cgiFormNotFound;
+ }
+ if (!strlen(e->value)) {
+ *result = defaultV;
+ return cgiFormEmpty;
+ }
+ ch = cgiFirstNonspaceChar(e->value);
+ if (!(isdigit(ch)) && (ch != '.') && (ch != '-') && (ch != '+')) {
+ *result = defaultV;
+ return cgiFormBadType;
+ } else {
+ *result = atof(e->value);
+ return cgiFormSuccess;
+ }
+}
+
+cgiFormResultType cgiFormDoubleBounded(
+ char *name, double *result, double min, double max, double defaultV) {
+ cgiFormResultType error = cgiFormDouble(name, result, defaultV);
+ if (error != cgiFormSuccess) {
+ return error;
+ }
+ if (*result < min) {
+ *result = min;
+ return cgiFormConstrained;
+ }
+ if (*result > max) {
+ *result = max;
+ return cgiFormConstrained;
+ }
+ return cgiFormSuccess;
+}
+
+cgiFormResultType cgiFormSelectSingle(
+ char *name, char **choicesText, int choicesTotal,
+ int *result, int defaultV)
+{
+ cgiFormEntry *e;
+ int i;
+ e = cgiFormEntryFindFirst(name);
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "%d\n", (int) e);
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ if (!e) {
+ *result = defaultV;
+ return cgiFormNotFound;
+ }
+ for (i=0; (i < choicesTotal); i++) {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "%s %s\n", choicesText[i], e->value);
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ if (cgiStrEq(choicesText[i], e->value)) {
+#ifdef CGICDEBUG
+ CGICDEBUGSTART
+ fprintf(dout, "MATCH\n");
+ CGICDEBUGEND
+#endif /* CGICDEBUG */
+ *result = i;
+ return cgiFormSuccess;
+ }
+ }
+ *result = defaultV;
+ return cgiFormNoSuchChoice;
+}
+
+cgiFormResultType cgiFormSelectMultiple(
+ char *name, char **choicesText, int choicesTotal,
+ int *result, int *invalid)
+{
+ cgiFormEntry *e;
+ int i;
+ int hits = 0;
+ int invalidE = 0;
+ for (i=0; (i < choicesTotal); i++) {
+ result[i] = 0;
+ }
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ *invalid = invalidE;
+ return cgiFormNotFound;
+ }
+ do {
+ int hit = 0;
+ for (i=0; (i < choicesTotal); i++) {
+ if (cgiStrEq(choicesText[i], e->value)) {
+ result[i] = 1;
+ hits++;
+ hit = 1;
+ break;
+ }
+ }
+ if (!(hit)) {
+ invalidE++;
+ }
+ } while ((e = cgiFormEntryFindNext()) != 0);
+
+ *invalid = invalidE;
+
+ if (hits) {
+ return cgiFormSuccess;
+ } else {
+ return cgiFormNotFound;
+ }
+}
+
+cgiFormResultType cgiFormCheckboxSingle(
+ char *name)
+{
+ cgiFormEntry *e;
+ e = cgiFormEntryFindFirst(name);
+ if (!e) {
+ return cgiFormNotFound;
+ }
+ return cgiFormSuccess;
+}
+
+extern cgiFormResultType cgiFormCheckboxMultiple(
+ char *name, char **valuesText, int valuesTotal,
+ int *result, int *invalid)
+{
+ /* Implementation is identical to cgiFormSelectMultiple. */
+ return cgiFormSelectMultiple(name, valuesText,
+ valuesTotal, result, invalid);
+}
+
+cgiFormResultType cgiFormRadio(
+ char *name,
+ char **valuesText, int valuesTotal, int *result, int defaultV)
+{
+ /* Implementation is identical to cgiFormSelectSingle. */
+ return cgiFormSelectSingle(name, valuesText, valuesTotal,
+ result, defaultV);
+}
+
+cgiFormResultType cgiCookieString(
+ char *name,
+ char *value,
+ int space)
+{
+ char *p = cgiCookie;
+ while (*p) {
+ char *n = name;
+ /* 2.02: if cgiCookie is exactly equal to name, this
+ can cause an overrun. The server probably wouldn't
+ allow it, since a name without values makes no sense
+ -- but then again it might not check, so this is a
+ genuine security concern. Thanks to Nicolas
+ Tomadakis. */
+ while (*p == *n) {
+ if ((p == '\0') && (n == '\0')) {
+ /* Malformed cookie header from client */
+ return cgiFormNotFound;
+ }
+ p++;
+ n++;
+ }
+ if ((!*n) && (*p == '=')) {
+ p++;
+ while ((*p != ';') && (*p != '\0') &&
+ (space > 1))
+ {
+ *value = *p;
+ value++;
+ p++;
+ space--;
+ }
+ if (space > 0) {
+ *value = '\0';
+ }
+ /* Correct parens: 2.02. Thanks to
+ Mathieu Villeneuve-Belair. */
+ if (!(((*p) == ';') || ((*p) == '\0')))
+ {
+ return cgiFormTruncated;
+ } else {
+ return cgiFormSuccess;
+ }
+ } else {
+ /* Skip to next cookie */
+ while (*p) {
+ if (*p == ';') {
+ break;
+ }
+ p++;
+ }
+ if (!*p) {
+ /* 2.01: default to empty */
+ if (space) {
+ *value = '\0';
+ }
+ return cgiFormNotFound;
+ }
+ p++;
+ /* Allow whitespace after semicolon */
+ while ((*p) && isspace((int)*p)) {
+ p++;
+ }
+ }
+ }
+ /* 2.01: actually the above loop never terminates except
+ with a return, but do this to placate gcc */
+ if (space) {
+ *value = '\0';
+ }
+ return cgiFormNotFound;
+}
+
+cgiFormResultType cgiCookieInteger(
+ char *name,
+ int *result,
+ int defaultV)
+{
+ char buffer[256];
+ cgiFormResultType r =
+ cgiCookieString(name, buffer, sizeof(buffer));
+ if (r != cgiFormSuccess) {
+ *result = defaultV;
+ } else {
+ *result = atoi(buffer);
+ }
+ return r;
+}
+
+void cgiHeaderCookieSetInteger(char *name, int value, int secondsToLive,
+ char *path, char *domain)
+{
+ char svalue[256];
+ sprintf(svalue, "%d", value);
+ cgiHeaderCookieSetString(name, svalue, secondsToLive, path, domain);
+}
+
+char *days[] = {
+ "Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat"
+};
+
+char *months[] = {
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec"
+};
+
+void cgiHeaderCookieSetString(char *name, char *value, int secondsToLive,
+ char *path, char *domain)
+{
+ /* cgic 2.02: simpler and more widely compatible implementation.
+ Thanks to Chunfu Lai.
+ cgic 2.03: yes, but it didn't work. Reimplemented by
+ Thomas Boutell. ; after last element was a bug.
+ Examples of real world cookies that really work:
+ Set-Cookie: MSNADS=UM=; domain=.slate.com;
+ expires=Tue, 26-Apr-2022 19:00:00 GMT; path=/
+ Set-Cookie: MC1=V=3&ID=b5bc08af2b8a43ff85fcb5efd8b238f0;
+ domain=.slate.com; expires=Mon, 04-Oct-2021 19:00:00 GMT; path=/
+ */
+ time_t now;
+ time_t then;
+ struct tm *gt;
+ time(&now);
+ then = now + secondsToLive;
+ gt = gmtime(&then);
+ fprintf(cgiOut,
+ "Set-Cookie: %s=%s; domain=%s; expires=%s, %02d-%s-%04d %02d:%02d:%02d GMT; path=%s\r\n",
+ name, value, domain,
+ days[gt->tm_wday],
+ gt->tm_mday,
+ months[gt->tm_mon],
+ gt->tm_year + 1900,
+ gt->tm_hour,
+ gt->tm_min,
+ gt->tm_sec,
+ path);
+}
+
+void cgiHeaderLocation(char *redirectUrl) {
+ fprintf(cgiOut, "Location: %s\r\n\r\n", redirectUrl);
+}
+
+void cgiHeaderStatus(int status, char *statusMessage) {
+ fprintf(cgiOut, "Status: %d %s\r\n\r\n", status, statusMessage);
+}
+
+void cgiHeaderContentType(char *mimeType) {
+ fprintf(cgiOut, "Content-type: %s\r\n\r\n", mimeType);
+}
+
+static int cgiWriteString(FILE *out, char *s);
+
+static int cgiWriteInt(FILE *out, int i);
+
+#define CGIC_VERSION "2.0"
+
+cgiEnvironmentResultType cgiWriteEnvironment(char *filename) {
+ FILE *out;
+ cgiFormEntry *e;
+ /* Be sure to open in binary mode */
+ out = fopen(filename, "wb");
+ if (!out) {
+ /* Can't create file */
+ return cgiEnvironmentIO;
+ }
+ if (!cgiWriteString(out, "CGIC2.0")) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiServerSoftware)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiServerName)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiGatewayInterface)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiServerProtocol)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiServerPort)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiRequestMethod)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiPathInfo)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiPathTranslated)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiScriptName)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiQueryString)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiRemoteHost)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiRemoteAddr)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiAuthType)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiRemoteUser)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiRemoteIdent)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiContentType)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiAccept)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiUserAgent)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiReferrer)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, cgiCookie)) {
+ goto error;
+ }
+ if (!cgiWriteInt(out, cgiContentLength)) {
+ goto error;
+ }
+ e = cgiFormEntryFirst;
+ while (e) {
+ cgiFilePtr fp;
+ if (!cgiWriteString(out, e->attr)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, e->value)) {
+ goto error;
+ }
+ /* New 2.0 fields and file uploads */
+ if (!cgiWriteString(out, e->fileName)) {
+ goto error;
+ }
+ if (!cgiWriteString(out, e->contentType)) {
+ goto error;
+ }
+ if (!cgiWriteInt(out, e->valueLength)) {
+ goto error;
+ }
+ if (cgiFormFileOpen(e->attr, &fp) == cgiFormSuccess) {
+ char buffer[1024];
+ int got;
+ if (!cgiWriteInt(out, 1)) {
+ cgiFormFileClose(fp);
+ goto error;
+ }
+ while (cgiFormFileRead(fp, buffer,
+ sizeof(buffer), &got) == cgiFormSuccess)
+ {
+ if (((int) fwrite(buffer, 1, got, out)) != got) {
+ cgiFormFileClose(fp);
+ goto error;
+ }
+ }
+ if (cgiFormFileClose(fp) != cgiFormSuccess) {
+ goto error;
+ }
+ } else {
+ if (!cgiWriteInt(out, 0)) {
+ goto error;
+ }
+ }
+ e = e->next;
+ }
+ fclose(out);
+ return cgiEnvironmentSuccess;
+error:
+ fclose(out);
+ /* If this function is not defined in your system,
+ you must substitute the appropriate
+ file-deletion function. */
+ unlink(filename);
+ return cgiEnvironmentIO;
+}
+
+static int cgiWriteString(FILE *out, char *s) {
+ int len = (int) strlen(s);
+ cgiWriteInt(out, len);
+ if (((int) fwrite(s, 1, len, out)) != len) {
+ return 0;
+ }
+ return 1;
+}
+
+static int cgiWriteInt(FILE *out, int i) {
+ if (!fwrite(&i, sizeof(int), 1, out)) {
+ return 0;
+ }
+ return 1;
+}
+
+static int cgiReadString(FILE *out, char **s);
+
+static int cgiReadInt(FILE *out, int *i);
+
+cgiEnvironmentResultType cgiReadEnvironment(char *filename) {
+ FILE *in;
+ cgiFormEntry *e = 0, *p;
+ char *version;
+ /* Prevent compiler warnings */
+ cgiEnvironmentResultType result = cgiEnvironmentIO;
+ /* Free any existing data first */
+ cgiFreeResources();
+ /* Be sure to open in binary mode */
+ in = fopen(filename, "rb");
+ if (!in) {
+ /* Can't access file */
+ return cgiEnvironmentIO;
+ }
+ if (!cgiReadString(in, &version)) {
+ goto error;
+ }
+ if (strcmp(version, "CGIC" CGIC_VERSION)) {
+ /* 2.02: Merezko Oleg */
+ free(version);
+ return cgiEnvironmentWrongVersion;
+ }
+ /* 2.02: Merezko Oleg */
+ free(version);
+ if (!cgiReadString(in, &cgiServerSoftware)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiServerName)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiGatewayInterface)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiServerProtocol)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiServerPort)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiRequestMethod)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiPathInfo)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiPathTranslated)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiScriptName)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiQueryString)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiRemoteHost)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiRemoteAddr)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiAuthType)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiRemoteUser)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiRemoteIdent)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiContentType)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiAccept)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiUserAgent)) {
+ goto error;
+ }
+ if (!cgiReadString(in, &cgiReferrer)) {
+ goto error;
+ }
+ /* 2.0 */
+ if (!cgiReadString(in, &cgiCookie)) {
+ goto error;
+ }
+ if (!cgiReadInt(in, &cgiContentLength)) {
+ goto error;
+ }
+ p = 0;
+ while (1) {
+ int fileFlag;
+ e = (cgiFormEntry *) calloc(1, sizeof(cgiFormEntry));
+ if (!e) {
+ cgiFreeResources();
+ fclose(in);
+ return cgiEnvironmentMemory;
+ }
+ memset(e, 0, sizeof(cgiFormEntry));
+ if (!cgiReadString(in, &e->attr)) {
+ /* This means we've reached the end of the list. */
+ /* 2.02: thanks to Merezko Oleg */
+ free(e);
+ break;
+ }
+ if (!cgiReadString(in, &e->value)) {
+ goto outOfMemory;
+ }
+ if (!cgiReadString(in, &e->fileName)) {
+ goto outOfMemory;
+ }
+ if (!cgiReadString(in, &e->contentType)) {
+ goto outOfMemory;
+ }
+ if (!cgiReadInt(in, &e->valueLength)) {
+ goto outOfMemory;
+ }
+ if (!cgiReadInt(in, &fileFlag)) {
+ goto outOfMemory;
+ }
+ if (fileFlag) {
+ char buffer[1024];
+ FILE *out;
+ char tfileName[1024];
+ int got;
+ int len = e->valueLength;
+ if (getTempFileName(tfileName)
+ != cgiParseSuccess)
+ {
+ result = cgiEnvironmentIO;
+ goto error;
+ }
+ out = fopen(tfileName, "w+b");
+ if (!out) {
+ result = cgiEnvironmentIO;
+ goto error;
+ }
+ while (len > 0) {
+ /* 2.01: try is a bad variable name in
+ C++, and it wasn't being used
+ properly either */
+ int tryr = len;
+ if (tryr > ((int) sizeof(buffer))) {
+ tryr = sizeof(buffer);
+ }
+ got = fread(buffer, 1, tryr, in);
+ if (got <= 0) {
+ result = cgiEnvironmentIO;
+ fclose(out);
+ unlink(tfileName);
+ goto error;
+ }
+ if (((int) fwrite(buffer, 1, got, out)) != got) {
+ result = cgiEnvironmentIO;
+ fclose(out);
+ unlink(tfileName);
+ goto error;
+ }
+ len -= got;
+ }
+ /* cgic 2.05: should be fclose not rewind */
+ fclose(out);
+ e->tfileName = (char *) malloc((int) strlen(tfileName) + 1);
+ if (!e->tfileName) {
+ result = cgiEnvironmentMemory;
+ unlink(tfileName);
+ goto error;
+ }
+ strcpy(e->tfileName, tfileName);
+ } else {
+ e->tfileName = (char *) malloc(1);
+ if (!e->tfileName) {
+ result = cgiEnvironmentMemory;
+ goto error;
+ }
+ }
+ e->next = 0;
+ if (p) {
+ p->next = e;
+ } else {
+ cgiFormEntryFirst = e;
+ }
+ p = e;
+ }
+ fclose(in);
+ cgiRestored = 1;
+ return cgiEnvironmentSuccess;
+outOfMemory:
+ result = cgiEnvironmentMemory;
+error:
+ cgiFreeResources();
+ fclose(in);
+ if (e) {
+ if (e->attr) {
+ free(e->attr);
+ }
+ if (e->value) {
+ free(e->value);
+ }
+ if (e->fileName) {
+ free(e->fileName);
+ }
+ if (e->contentType) {
+ free(e->contentType);
+ }
+ if (e->tfileName) {
+ free(e->tfileName);
+ }
+ free(e);
+ }
+ return result;
+}
+
+static int cgiReadString(FILE *in, char **s) {
+ int len;
+ /* 2.0 fix: test cgiReadInt for failure! */
+ if (!cgiReadInt(in, &len)) {
+ return 0;
+ }
+ *s = (char *) malloc(len + 1);
+ if (!(*s)) {
+ return 0;
+ }
+ if (((int) fread(*s, 1, len, in)) != len) {
+ return 0;
+ }
+ (*s)[len] = '\0';
+ return 1;
+}
+
+static int cgiReadInt(FILE *out, int *i) {
+ if (!fread(i, sizeof(int), 1, out)) {
+ return 0;
+ }
+ return 1;
+}
+
+static int cgiStrEqNc(char *s1, char *s2) {
+ while(1) {
+ if (!(*s1)) {
+ if (!(*s2)) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (!(*s2)) {
+ return 0;
+ }
+ if (isalpha((int)*s1)) {
+ if (tolower(*s1) != tolower(*s2)) {
+ return 0;
+ }
+ } else if ((*s1) != (*s2)) {
+ return 0;
+ }
+ s1++;
+ s2++;
+ }
+}
+
+static int cgiStrBeginsNc(char *s1, char *s2) {
+ while(1) {
+ if (!(*s2)) {
+ return 1;
+ } else if (!(*s1)) {
+ return 0;
+ }
+ if (isalpha((int)*s1)) {
+ if (tolower(*s1) != tolower(*s2)) {
+ return 0;
+ }
+ } else if ((*s1) != (*s2)) {
+ return 0;
+ }
+ s1++;
+ s2++;
+ }
+}
+
+static char *cgiFindTarget = 0;
+static cgiFormEntry *cgiFindPos = 0;
+
+static cgiFormEntry *cgiFormEntryFindFirst(char *name) {
+ cgiFindTarget = name;
+ cgiFindPos = cgiFormEntryFirst;
+ return cgiFormEntryFindNext();
+}
+
+static cgiFormEntry *cgiFormEntryFindNext() {
+ while (cgiFindPos) {
+ cgiFormEntry *c = cgiFindPos;
+ cgiFindPos = c->next;
+ if (!strcmp(c -> attr, cgiFindTarget)) {
+ return c;
+ }
+ }
+ return 0;
+}
+
+static int cgiFirstNonspaceChar(char *s) {
+ int len = strspn(s, " \n\r\t");
+ return s[len];
+}
+
+void cgiStringArrayFree(char **stringArray) {
+ char *p;
+ char **arrayItself = stringArray;
+ p = *stringArray;
+ while (p) {
+ free(p);
+ stringArray++;
+ p = *stringArray;
+ }
+ /* 2.0: free the array itself! */
+ free(arrayItself);
+}
+
+cgiFormResultType cgiCookies(char ***result) {
+ char **stringArray;
+ int i;
+ int total = 0;
+ char *p;
+ char *n;
+ p = cgiCookie;
+ while (*p) {
+ if (*p == '=') {
+ total++;
+ }
+ p++;
+ }
+ stringArray = (char **) malloc(sizeof(char *) * (total + 1));
+ if (!stringArray) {
+ *result = 0;
+ return cgiFormMemory;
+ }
+ /* initialize all entries to null; the last will stay that way */
+ for (i=0; (i <= total); i++) {
+ stringArray[i] = 0;
+ }
+ i = 0;
+ p = cgiCookie;
+ while (*p) {
+ while (*p && isspace((int)*p)) {
+ p++;
+ }
+ n = p;
+ while (*p && (*p != '=')) {
+ p++;
+ }
+ if (p != n) {
+ stringArray[i] = (char *) malloc((p - n) + 1);
+ if (!stringArray[i]) {
+ cgiStringArrayFree(stringArray);
+ *result = 0;
+ return cgiFormMemory;
+ }
+ memcpy(stringArray[i], n, p - n);
+ stringArray[i][p - n] = '\0';
+ i++;
+ }
+ while (*p && (*p != ';')) {
+ p++;
+ }
+ if (!*p) {
+ break;
+ }
+ if (*p == ';') {
+ p++;
+ }
+ }
+ *result = stringArray;
+ return cgiFormSuccess;
+}
+
+cgiFormResultType cgiFormEntries(char ***result) {
+ char **stringArray;
+ cgiFormEntry *e, *pe;
+ int i;
+ int total = 0;
+ e = cgiFormEntryFirst;
+ while (e) {
+ /* Don't count a field name more than once if
+ multiple values happen to be present for it */
+ pe = cgiFormEntryFirst;
+ while (pe != e) {
+ if (!strcmp(e->attr, pe->attr)) {
+ goto skipSecondValue;
+ }
+ pe = pe->next;
+ }
+ total++;
+skipSecondValue:
+ e = e->next;
+ }
+ stringArray = (char **) malloc(sizeof(char *) * (total + 1));
+ if (!stringArray) {
+ *result = 0;
+ return cgiFormMemory;
+ }
+ /* initialize all entries to null; the last will stay that way */
+ for (i=0; (i <= total); i++) {
+ stringArray[i] = 0;
+ }
+ /* Now go get the entries */
+ e = cgiFormEntryFirst;
+ i = 0;
+ while (e) {
+ int space;
+ /* Don't return a field name more than once if
+ multiple values happen to be present for it */
+ pe = cgiFormEntryFirst;
+ while (pe != e) {
+ if (!strcmp(e->attr, pe->attr)) {
+ goto skipSecondValue2;
+ }
+ pe = pe->next;
+ }
+ space = (int) strlen(e->attr) + 1;
+ stringArray[i] = (char *) malloc(space);
+ if (stringArray[i] == 0) {
+ /* Memory problems */
+ cgiStringArrayFree(stringArray);
+ *result = 0;
+ return cgiFormMemory;
+ }
+ strcpy(stringArray[i], e->attr);
+ i++;
+skipSecondValue2:
+ e = e->next;
+ }
+ *result = stringArray;
+ return cgiFormSuccess;
+}
+
+#define TRYPUTC(ch) \
+ { \
+ if (putc((ch), cgiOut) == EOF) { \
+ return cgiFormIO; \
+ } \
+ }
+
+cgiFormResultType cgiHtmlEscapeData(char *data, int len)
+{
+ while (len--) {
+ if (*data == '<') {
+ TRYPUTC('&');
+ TRYPUTC('l');
+ TRYPUTC('t');
+ TRYPUTC(';');
+ } else if (*data == '&') {
+ TRYPUTC('&');
+ TRYPUTC('a');
+ TRYPUTC('m');
+ TRYPUTC('p');
+ TRYPUTC(';');
+ } else if (*data == '>') {
+ TRYPUTC('&');
+ TRYPUTC('g');
+ TRYPUTC('t');
+ TRYPUTC(';');
+ } else {
+ TRYPUTC(*data);
+ }
+ data++;
+ }
+ return cgiFormSuccess;
+}
+
+cgiFormResultType cgiHtmlEscape(char *s)
+{
+ return cgiHtmlEscapeData(s, (int) strlen(s));
+}
+
+/* Output data with the " character HTML-escaped, and no
+ other characters escaped. This is useful when outputting
+ the contents of a tag attribute such as 'href' or 'src'.
+ 'data' is not null-terminated; 'len' is the number of
+ bytes in 'data'. Returns cgiFormIO in the event
+ of error, cgiFormSuccess otherwise. */
+cgiFormResultType cgiValueEscapeData(char *data, int len)
+{
+ while (len--) {
+ if (*data == '\"') {
+ TRYPUTC('&');
+ TRYPUTC('#');
+ TRYPUTC('3');
+ TRYPUTC('4');
+ TRYPUTC(';');
+ } else {
+ TRYPUTC(*data);
+ }
+ data++;
+ }
+ return cgiFormSuccess;
+}
+
+cgiFormResultType cgiValueEscape(char *s)
+{
+ return cgiValueEscapeData(s, (int) strlen(s));
+}
+
+
diff --git a/cgic.diff b/cgic.diff
new file mode 100644
index 0000000..08ec75d
--- /dev/null
+++ b/cgic.diff
@@ -0,0 +1,102 @@
+*** cgic.c 2004-11-15 08:57:59.000000000 -0800
+--- ../wfr33/cgic.c 2011-11-24 14:14:52.000000000 -0800
+***************
+*** 29,34 ****
+--- 29,36 ----
+ #include <time.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
++ #include <signal.h>
++ #include <sys/shm.h>
+
+ #ifdef WIN32
+ #include <io.h>
+*************** char *cgiAccept;
+*** 67,72 ****
+--- 69,79 ----
+ char *cgiUserAgent;
+ char *cgiReferrer;
+
++ off_t *shm;
++ int shm_key;
++ int shmid;
++
++
+ FILE *cgiIn;
+ FILE *cgiOut;
+
+*************** static void cgiFreeResources();
+*** 119,124 ****
+--- 126,137 ----
+ static int cgiStrEqNc(char *s1, char *s2);
+ static int cgiStrBeginsNc(char *s1, char *s2);
+
++ /* Dirty little hack to get file upload progress - Part 4: SHM Cleanup */
++ void shmcleanup(void) {
++ if(shmid>0)
++ shmctl(shmid, IPC_RMID, NULL);
++ }
++
+ int main(int argc, char *argv[]) {
+ int result;
+ char *cgiContentLengthString;
+*************** static cgiParseResultType cgiParsePostMu
+*** 435,440 ****
+--- 448,454 ----
+ cgiParseResultType result;
+ cgiFormEntry *n = 0, *l = 0;
+ int got;
++ int sig;
+ FILE *outf = 0;
+ char *out = 0;
+ char tfileName[1024];
+*************** static cgiParseResultType cgiParsePostMu
+*** 521,531 ****
+--- 535,559 ----
+ return cgiParseIO;
+ }
+ outf = fopen(tfileName, "w+b");
++ /* Dirty little hack to get file upload progress - Part 1: Initialize */
++ if (cgiFormInteger("upload_id", &shm_key, 0) == cgiFormSuccess && shm_key) {
++ if ((shmid = shmget(shm_key, sizeof(int), IPC_CREAT | 0666)) > 0) {
++ for(sig=1; sig<=31; sig++)
++ signal(sig, (void*)shmcleanup);
++ if ((shm = shmat(shmid, NULL, 0)) > 0)
++ *shm=0;
++ }
++ }
+ } else {
+ outf = 0;
+ tfileName[0] = '\0';
+ }
+ result = afterNextBoundary(mpp, outf, &out, &bodyLength, 0);
++ /* Dirty little hack to get file upload progress - Part 2: Clean up */
++ if(shm>0)
++ shmdt(shm);
++ if(shmid>0)
++ shmctl(shmid, IPC_RMID, NULL);
+ if (result != cgiParseSuccess) {
+ /* Lack of a boundary here is an error. */
+ if (outf) {
+*************** cgiParseResultType afterNextBoundary(mpS
+*** 689,694 ****
+--- 717,723 ----
+ cgiParseResultType result;
+ int boffset;
+ int got;
++ off_t tot=0;
+ char d[2];
+ /* This is large enough, because the buffer into which the
+ original boundary string is fetched is shorter by more
+*************** cgiParseResultType afterNextBoundary(mpS
+*** 711,716 ****
+--- 740,748 ----
+ workingBoundaryLength = strlen(workingBoundary);
+ while (1) {
+ got = mpRead(mpp, d, 1);
++ /* Dirty little hack to get file upload progress - Part 3: Set value */
++ if(outf && shm>0)
++ *shm=tot+=got;
+ if (got != 1) {
+ /* 2.01: cgiParseIO, not cgiFormIO */
+ result = cgiParseIO;
diff --git a/cgic.h b/cgic.h
new file mode 100644
index 0000000..54270db
--- /dev/null
+++ b/cgic.h
@@ -0,0 +1,232 @@
+/* The CGI_C library, by Thomas Boutell, version 2.01. CGI_C is intended
+ to be a high-quality API to simplify CGI programming tasks. */
+
+/* Make sure this is only included once. */
+
+#ifndef CGI_C
+#define CGI_C 1
+
+/* Bring in standard I/O since some of the functions refer to
+ types defined by it, such as FILE *. */
+
+#include <stdio.h>
+
+/* The various CGI environment variables. Instead of using getenv(),
+ the programmer should refer to these, which are always
+ valid null-terminated strings (they may be empty, but they
+ will never be null). If these variables are used instead
+ of calling getenv(), then it will be possible to save
+ and restore CGI environments, which is highly convenient
+ for debugging. */
+
+extern char *cgiServerSoftware;
+extern char *cgiServerName;
+extern char *cgiGatewayInterface;
+extern char *cgiServerProtocol;
+extern char *cgiServerPort;
+extern char *cgiRequestMethod;
+extern char *cgiPathInfo;
+extern char *cgiPathTranslated;
+extern char *cgiScriptName;
+extern char *cgiQueryString;
+extern char *cgiRemoteHost;
+extern char *cgiRemoteAddr;
+extern char *cgiAuthType;
+extern char *cgiRemoteUser;
+extern char *cgiRemoteIdent;
+extern char *cgiContentType;
+extern char *cgiAccept;
+extern char *cgiUserAgent;
+extern char *cgiReferrer;
+
+/* Cookies as sent to the server. You can also get them
+ individually, or as a string array; see the documentation. */
+extern char *cgiCookie;
+
+/* A macro providing the same incorrect spelling that is
+ found in the HTTP/CGI specifications */
+#define cgiReferer cgiReferrer
+
+/* The number of bytes of data received.
+ Note that if the submission is a form submission
+ the library will read and parse all the information
+ directly from cgiIn; the programmer need not do so. */
+
+extern int cgiContentLength;
+
+/* Pointer to CGI output. The cgiHeader functions should be used
+ first to output the mime headers; the output HTML
+ page, GIF image or other web document should then be written
+ to cgiOut by the programmer. In the standard CGIC library,
+ cgiOut is always equivalent to stdout. */
+
+extern FILE *cgiOut;
+
+/* Pointer to CGI input. The programmer does not read from this.
+ We have continued to export it for backwards compatibility
+ so that cgic 1.x applications link properly. */
+
+extern FILE *cgiIn;
+
+/* Possible return codes from the cgiForm family of functions (see below). */
+
+typedef enum {
+ cgiFormSuccess,
+ cgiFormTruncated,
+ cgiFormBadType,
+ cgiFormEmpty,
+ cgiFormNotFound,
+ cgiFormConstrained,
+ cgiFormNoSuchChoice,
+ cgiFormMemory,
+ cgiFormNoFileName,
+ cgiFormNoContentType,
+ cgiFormNotAFile,
+ cgiFormOpenFailed,
+ cgiFormIO,
+ cgiFormEOF
+} cgiFormResultType;
+
+/* These functions are used to retrieve form data. See
+ cgic.html for documentation. */
+
+extern cgiFormResultType cgiFormString(
+ char *name, char *result, int max);
+
+extern cgiFormResultType cgiFormStringNoNewlines(
+ char *name, char *result, int max);
+
+
+extern cgiFormResultType cgiFormStringSpaceNeeded(
+ char *name, int *length);
+
+
+extern cgiFormResultType cgiFormStringMultiple(
+ char *name, char ***ptrToStringArray);
+
+extern void cgiStringArrayFree(char **stringArray);
+
+extern cgiFormResultType cgiFormInteger(
+ char *name, int *result, int defaultV);
+
+extern cgiFormResultType cgiFormIntegerBounded(
+ char *name, int *result, int min, int max, int defaultV);
+
+extern cgiFormResultType cgiFormDouble(
+ char *name, double *result, double defaultV);
+
+extern cgiFormResultType cgiFormDoubleBounded(
+ char *name, double *result, double min, double max, double defaultV);
+
+extern cgiFormResultType cgiFormSelectSingle(
+ char *name, char **choicesText, int choicesTotal,
+ int *result, int defaultV);
+
+
+extern cgiFormResultType cgiFormSelectMultiple(
+ char *name, char **choicesText, int choicesTotal,
+ int *result, int *invalid);
+
+/* Just an alias; users have asked for this */
+#define cgiFormSubmitClicked cgiFormCheckboxSingle
+
+extern cgiFormResultType cgiFormCheckboxSingle(
+ char *name);
+
+extern cgiFormResultType cgiFormCheckboxMultiple(
+ char *name, char **valuesText, int valuesTotal,
+ int *result, int *invalid);
+
+extern cgiFormResultType cgiFormRadio(
+ char *name, char **valuesText, int valuesTotal,
+ int *result, int defaultV);
+
+/* The paths returned by this function are the original names of files
+ as reported by the uploading web browser and shoult NOT be
+ blindly assumed to be "safe" names for server-side use! */
+extern cgiFormResultType cgiFormFileName(
+ char *name, char *result, int max);
+
+/* The content type of the uploaded file, as reported by the browser.
+ It should NOT be assumed that browsers will never falsify
+ such information. */
+extern cgiFormResultType cgiFormFileContentType(
+ char *name, char *result, int max);
+
+extern cgiFormResultType cgiFormFileSize(
+ char *name, int *sizeP);
+
+typedef struct cgiFileStruct *cgiFilePtr;
+
+extern cgiFormResultType cgiFormFileOpen(
+ char *name, cgiFilePtr *cfpp);
+
+extern cgiFormResultType cgiFormFileRead(
+ cgiFilePtr cfp, char *buffer, int bufferSize, int *gotP);
+
+extern cgiFormResultType cgiFormFileClose(
+ cgiFilePtr cfp);
+
+extern cgiFormResultType cgiCookieString(
+ char *name, char *result, int max);
+
+extern cgiFormResultType cgiCookieInteger(
+ char *name, int *result, int defaultV);
+
+cgiFormResultType cgiCookies(
+ char ***ptrToStringArray);
+
+/* path can be null or empty in which case a path of / (entire site) is set.
+ domain can be a single web site; if it is an entire domain, such as
+ 'boutell.com', it should begin with a dot: '.boutell.com' */
+extern void cgiHeaderCookieSetString(char *name, char *value,
+ int secondsToLive, char *path, char *domain);
+extern void cgiHeaderCookieSetInteger(char *name, int value,
+ int secondsToLive, char *path, char *domain);
+extern void cgiHeaderLocation(char *redirectUrl);
+extern void cgiHeaderStatus(int status, char *statusMessage);
+extern void cgiHeaderContentType(char *mimeType);
+
+typedef enum {
+ cgiEnvironmentIO,
+ cgiEnvironmentMemory,
+ cgiEnvironmentSuccess,
+ cgiEnvironmentWrongVersion
+} cgiEnvironmentResultType;
+
+extern cgiEnvironmentResultType cgiWriteEnvironment(char *filename);
+extern cgiEnvironmentResultType cgiReadEnvironment(char *filename);
+
+extern int cgiMain();
+
+extern cgiFormResultType cgiFormEntries(
+ char ***ptrToStringArray);
+
+/* Output string with the <, &, and > characters HTML-escaped.
+ 's' is null-terminated. Returns cgiFormIO in the event
+ of error, cgiFormSuccess otherwise. */
+cgiFormResultType cgiHtmlEscape(char *s);
+
+/* Output data with the <, &, and > characters HTML-escaped.
+ 'data' is not null-terminated; 'len' is the number of
+ bytes in 'data'. Returns cgiFormIO in the event
+ of error, cgiFormSuccess otherwise. */
+cgiFormResultType cgiHtmlEscapeData(char *data, int len);
+
+/* Output string with the " character HTML-escaped, and no
+ other characters escaped. This is useful when outputting
+ the contents of a tag attribute such as 'href' or 'src'.
+ 's' is null-terminated. Returns cgiFormIO in the event
+ of error, cgiFormSuccess otherwise. */
+cgiFormResultType cgiValueEscape(char *s);
+
+/* Output data with the " character HTML-escaped, and no
+ other characters escaped. This is useful when outputting
+ the contents of a tag attribute such as 'href' or 'src'.
+ 'data' is not null-terminated; 'len' is the number of
+ bytes in 'data'. Returns cgiFormIO in the event
+ of error, cgiFormSuccess otherwise. */
+cgiFormResultType cgiValueEscapeData(char *data, int len);
+
+#endif /* CGI_C */
+
diff --git a/dialogs.c b/dialogs.c
new file mode 100644
index 0000000..abe61cb
--- /dev/null
+++ b/dialogs.c
@@ -0,0 +1,435 @@
+// WFM HTML Dialog Routines
+
+#include "wfm.h"
+
+//
+// Prompt for delete / move operation
+//
+void multiprompt_ui(char *m_action) {
+ int i;
+ int res;
+ char **responses;
+ struct stat fileinfo;
+ char M_action[64];
+
+ res=cgiFormStringMultiple("multiselect_filename", &responses);
+
+ // pre-check for filenames so, that if there is an error, HTML is not yet out, allowing error dialog to be rendered
+ if(res == cgiFormNotFound) {
+ checkfilename(NULL);
+ } else {
+ for(i=0; responses[i]; i++)
+ checkfilename(responses[i]);
+ }
+
+ cgiHeaderContentType("text/html");
+ snprintf(M_action, sizeof(M_action), "%c%s Confirmation", toupper(m_action[0]), m_action+1);
+ html_title(M_action);
+
+ fprintf(cgiOut,
+ "</HEAD>\n"
+ "<!-- Multi Prompt -->\n"
+ "<BODY BGCOLOR=\"#FFFFFF\">\n"
+ "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 CLASS=\"twh\"><TR><TD VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"
+ "<FORM NAME=\"wfm\" ACTION=\"%s\" METHOD=\"POST\" ENCTYPE=\"multipart/form-data\">\n\n", cgiScriptName);
+
+ fprintf(cgiOut,
+ "<TABLE WIDTH=\"500\" BGCOLOR=\"#F0F0F0\" BORDER=0 CELLSPACING=0 CELLPADDING=1 CLASS=\"tbr\">\n"
+ " <TR><TD COLSPAN=2 BGCOLOR=\"#004080\"><FONT COLOR=\"#FFFFFF\">&nbsp; %s</FONT></TD></TR>\n"
+ " <TR><TD WIDTH=30>&nbsp;</TD><TD>\n"
+ " &nbsp;<BR>\n"
+ " About to %s following items:<P><UL>\n",
+ M_action, m_action);
+
+
+ if(res == cgiFormNotFound) {
+ checkfilename(NULL);
+ if(stat(phys_filename, &fileinfo)==0) {
+ fprintf(cgiOut, "<INPUT TYPE=\"HIDDEN\" NAME=\"filename\" VALUE=\"%s\">\n", virt_filename);
+ fprintf(cgiOut, "<LI TYPE=\"square\"><B>%s/</B>", virt_filename);
+ if(S_ISDIR(fileinfo.st_mode))
+ fprintf(cgiOut, " [directory %s]\n", buprintf(du(phys_filename), FALSE));
+ else
+ fprintf(cgiOut, " [file %s]\n", buprintf(fileinfo.st_size, FALSE));
+ }
+ } else {
+ for(i=0; responses[i]; i++) {
+ checkfilename(responses[i]);
+ if(stat(phys_filename, &fileinfo)==0) {
+ fprintf(cgiOut, "<INPUT TYPE=\"HIDDEN\" NAME=\"multiselect_filename\" VALUE=\"%s\">\n", virt_filename);
+ fprintf(cgiOut, "<LI TYPE=\"square\"><B>%s</B>", virt_filename);
+ if(S_ISDIR(fileinfo.st_mode))
+ fprintf(cgiOut, "/ [directory %s]\n", buprintf(du(phys_filename), FALSE));
+ else
+ fprintf(cgiOut, " [file %s]\n", buprintf(fileinfo.st_size, FALSE));
+ }
+ }
+ }
+
+ fprintf(cgiOut, "</UL>");
+
+ // move needs a destination...
+ if(strcmp(m_action, "move")==0) {
+ fprintf(cgiOut, "<P>Destination: <SELECT NAME=\"destination\"><OPTION VALUE=\"/\">/ - Root Directory</OPTION>\n");
+ re_dir_ui("/", 1);
+ fprintf(cgiOut, "</SELECT>\n<INPUT TYPE=\"HIDDEN\" NAME=\"absdst\" VALUE=\"1\">\n<P>\n");
+ }
+
+ fprintf(cgiOut,
+ " </TD></TR>\n"
+ " <TR><TD COLSPAN=2>\n"
+ " <P><CENTER>\n"
+ " <INPUT TYPE=\"HIDDEN\" NAME=\"action\" VALUE=\"%s\">\n"
+ " <INPUT TYPE=\"HIDDEN\" NAME=\"directory\" VALUE=\"%s\">\n"
+ " <INPUT TYPE=\"HIDDEN\" NAME=\"token\" VALUE=\"%s\">\n"
+ " <INPUT TYPE=\"SUBMIT\" VALUE=\" OK \" NAME=\"OK\">\n"
+ " <INPUT TYPE=\"BUTTON\" VALUE=\" Cancel \" onClick=\"self.location='?directory=%s&amp;token=%s'; return true;\">\n"
+ " </CENTER><BR>\n"
+ " </TD></TR>\n"
+ "</TABLE></FORM>\n\n"
+ "</TD></TR></TABLE>\n"
+ "</BODY>\n</HTML>\n", m_action, virt_dirname, token, virt_dirname, token);
+
+ cgiStringArrayFree(responses);
+
+}
+
+//
+// Single Prompt
+// Used for rename, mkfile, mkdir
+//
+void singleprompt_ui(char *m_action) {
+ char M_action[64];
+
+ snprintf(M_action, sizeof(M_action), "%c%s", toupper(m_action[0]), m_action+1);
+
+ if(strcmp(m_action, "move")==0) {
+ checkfilename(NULL);
+ snprintf(M_action, sizeof(M_action), "Rename");
+ }
+
+ cgiHeaderContentType("text/html");
+ html_title(M_action);
+
+ fprintf(cgiOut,
+ "</HEAD>\n"
+ "<!-- Single Prompt -->\n"
+ "<BODY ONLOAD=\"document.wfm.inp1.focus();\" BGCOLOR=\"#FFFFFF\">\n"
+ "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 CLASS=\"twh\"><TR><TD VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"
+ "<FORM NAME=\"wfm\" ACTION=\"%s\" METHOD=\"POST\" ENCTYPE=\"multipart/form-data\">\n\n"
+ "<TABLE WIDTH=\"400\" BGCOLOR=\"#F0F0F0\" BORDER=0 CELLSPACING=0 CELLPADDING=1 CLASS=\"tbr\">\n"
+ " <TR><TD COLSPAN=2 BGCOLOR=\"#004080\"><FONT COLOR=\"#FFFFFF\">&nbsp; %s</FONT></TD></TR>\n"
+ " <TR><TD WIDTH=30>&nbsp;</TD><TD>\n"
+ " &nbsp;<BR>\n", cgiScriptName, M_action);
+
+
+ if(strcmp(m_action, "move")==0)
+ fprintf(cgiOut,
+ " Current Name: <B>%s</B><P>\n"
+ " Enter new name:<P>\n"
+ " <INPUT TYPE=\"TEXT\" ID=\"inp1\" NAME=\"destination\" SIZE=\"40\" VALUE=\"%s\">\n"
+ " <INPUT TYPE=\"HIDDEN\" NAME=\"filename\" VALUE=\"%s\">\n",
+ virt_filename, virt_filename, virt_filename);
+
+ else if(strcmp(m_action, "mkfile")==0)
+ fprintf(cgiOut,
+ " Enter name of the new text file:<P>\n"
+ " <INPUT TYPE=\"TEXT\" ID=\"inp1\" NAME=\"filename\" SIZE=\"40\" VALUE=\"\">\n");
+
+ else if(strcmp(m_action, "mkdir")==0)
+ fprintf(cgiOut,
+ " &nbsp;<BR>Enter name of the new directory:<P>\n"
+ " <INPUT TYPE=\"TEXT\" ID=\"inp1\" NAME=\"filename\" SIZE=\"40\" VALUE=\"\">\n");
+
+ fprintf(cgiOut,
+ " </TD></TR>\n"
+ " <TR><TD COLSPAN=2>\n"
+ " <P><CENTER>\n"
+ " <INPUT TYPE=\"HIDDEN\" NAME=\"action\" VALUE=\"%s\">\n"
+ " <INPUT TYPE=\"HIDDEN\" NAME=\"directory\" VALUE=\"%s\">\n"
+ " <INPUT TYPE=\"HIDDEN\" NAME=\"token\" VALUE=\"%s\">\n"
+ " <INPUT TYPE=\"SUBMIT\" VALUE=\" OK \" NAME=\"OK\">\n"
+ " <INPUT TYPE=\"BUTTON\" VALUE=\" Cancel \" onClick=\"self.location='?directory=%s&amp;token=%s'; return true;\">\n"
+ " </CENTER><BR>\n"
+ " </TD></TR>\n"
+ "</TABLE></FORM>\n\n"
+ "</TD></TR></TABLE>\n"
+ "</BODY>\n</HTML>\n", m_action, virt_dirname, token, virt_dirname, token);
+
+}
+
+
+//
+// Error message - note that strerror() is already passed by the caller
+//
+void error(char *msg, ...) {
+ va_list ap;
+ char buff[1024]={0};
+
+ if(msg) {
+ va_start(ap, msg);
+ vsnprintf(buff, sizeof(buff), msg, ap);
+ va_end(ap);
+
+ cgiHeaderContentType("text/html");
+ html_title("ERROR");
+ fprintf(cgiOut,
+ "</HEAD>\n"\
+ "<BODY BGCOLOR=\"#FFFFFF\">\n"\
+ "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 CLASS=\"twh\"><TR><TD VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"
+ "<TABLE WIDTH=\"400\" BGCOLOR=\"#D4D0C8\" BORDER=0 CELLSPACING=0 CELLPADDING=1 CLASS=\"tbr\">\n"
+ "<TR BGCOLOR=\"#FF0000\">\n"
+ "<TD COLSPAN=3 ALIGN=\"LEFT\">\n"
+ "<SPAN STYLE=\"color:#FFFFFF; font-weight:bold;\">&nbsp;ERROR:</SPAN>\n"
+ "</TD>\n"
+ "</TR>\n"
+ "<TR BGCOLOR=\"#EEEEEE\">\n"
+ "<TD WIDTH=\"50\" VALIGN=\"top\">\n"
+ "&nbsp;<BR>\n"
+ "</TD>\n"
+ "<TD ALIGN=\"LEFT\">\n"
+ "&nbsp;<BR>\n"
+ "%s<BR>\n"
+ "&nbsp;<P>\n"
+ "&nbsp;<P>\n"
+ "</TD>\n"
+ "<TD WIDTH=\"20\">\n"
+ "&nbsp;\n"
+ "</TD>\n"
+ "</TR>\n"
+ "<TR><TD COLSPAN=3 ALIGN=\"CENTER\" BGCOLOR=\"#EEEEEE\">\n"
+ "<INPUT TYPE=\"BUTTON\" VALUE=\" OK \" onClick=\"self.location='?directory=%s&amp;token=%s'; return true;\">\n"
+ "</TD></TR>\n"
+ "<TR><TD COLSPAN=3 BGCOLOR=\"#EEEEEE\">&nbsp;</TD></TR>\n"
+ "</TABLE>\n"
+ "</TD></TR></TABLE>\n</BODY></HTML>\n",
+ buff, virt_dirname, token);
+ }
+ else {
+ cgiHeaderContentType("text/plain");
+ fprintf(cgiOut, "FATAL ERROR\n");
+ }
+
+ exit(0);
+}
+
+
+//
+// About message
+//
+void about(void) {
+ cgiHeaderContentType("text/html");
+ html_title("About");
+ fprintf(cgiOut,
+ "</HEAD>\n"
+ "<BODY BGCOLOR=\"#FFFFFF\">\n"
+ "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 CLASS=\"twh\"><TR><TD VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"
+ "<TABLE WIDTH=\"500\" BGCOLOR=\"#D4D0C8\" BORDER=0 CELLSPACING=0 CELLPADDING=1 CLASS=\"tbr\">\n"
+ "<TR BGCOLOR=\"#0040A0\">\n"
+ "<TD COLSPAN=3 ALIGN=\"LEFT\">\n"
+ "<IMG SRC=\"%swfmicon.gif\" ALT=\"wfm Icon\"><SPAN STYLE=\"color:#FFFFFF\"> About:</SPAN>\n"
+ "</TD>\n"
+ "</TR>\n"
+ "<TR BGCOLOR=\"#EEEEEE\">\n"
+ "<TD WIDTH=\"50\" VALIGN=\"top\">\n"
+ "&nbsp;<BR>\n"
+ "</TD>\n"
+ "<TD ALIGN=\"LEFT\">\n"
+ "&nbsp;<BR>\n"
+ "<U>%s</U><BR>\n"
+ "WFM Implemented by Antoni Sawicki<BR>\n"
+ "CGIC Library by Thomas Boutell<BR>\n"
+ "Server Side RFC 1321 implementation by L. Peter Deutsch<BR>\n"
+ "Client Side RFC 1321 implementation by Paul Johnston<BR>\n"
+ "Icons by Yusuke Kamiyamane<BR>\n"
+ "Copyright &copy; 1994-2016 by Antoni Sawicki<BR>\n"
+ "Copyright &copy; 1996-2011 by Thomas Boutell and Boutell.Com, Inc.<BR>\n"
+ "Copyright &copy; 2002 by Aladdin Enterprises<BR>\n"
+ "Copyright &copy; 1999-2009 by Paul Johnston<BR>\n"
+ "Copyright &copy; 2010 by Yusuke Kamiyamane<BR>\n"
+ "<HR>\n"
+ "WFM: %s (build %s / %s)<BR>\n"
+ "GCC: %s<BR>\n"
+ "Server: %s<BR>\n"
+ "<img src=\"http://www.w3.org/Icons/valid-html401\" alt=\"Valid HTML 4.01 Transitional\">\n"
+ "&nbsp;<P>\n"
+ "&nbsp;<P>\n"
+ "</TD>\n"
+ "<TD WIDTH=\"20\">\n"
+ "&nbsp;\n"
+ "</TD>\n"
+ "</TR>\n"
+ "<TR><TD COLSPAN=3 ALIGN=\"CENTER\" BGCOLOR=\"#EEEEEE\">\n"
+ "<INPUT TYPE=\"BUTTON\" VALUE=\" OK \" onClick=\"self.location='?directory=%s&amp;token=%s'; return true;\">\n"
+ "</TD></TR>\n"
+ "<TR><TD COLSPAN=3 BGCOLOR=\"#EEEEEE\">&nbsp;</TD></TR>\n"
+ "</TABLE>\n"
+ "</TD></TR></TABLE>\n</BODY></HTML>\n",
+ ICONSURL, TAGLINE, VERSION, __DATE__, __TIME__, __VERSION__, cgiServerSoftware, virt_dirname, token);
+
+}
+
+
+//
+// Prompt for username and password
+//
+void login_ui(void) {
+ cgiHeaderContentType("text/html");
+ html_title("Login");
+ fprintf(cgiOut,
+ "<SCRIPT LANGUAGE=\"JavaScript\" TYPE=\"text/javascript\">\n<!--\n"
+ "var hexcase=0;function hex_md5(a){return rstr2hex(rstr_md5(str2rstr_utf8(a)))}function hex_hmac_md5(a,b){return rstr2hex(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(b)))}function md5_vm_test(){return hex_md5(\"abc\").toLowerCase()==\"900150983cd24fb0d6963f7d28e17f72\"}function rstr_md5(a){return binl2rstr(binl_md5(rstr2binl(a),a.length*8))}function rstr_hmac_md5(c,f){var e=rstr2binl(c);if(e.length>16){e=binl_md5(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binl_md5(a.concat(rstr2binl(f)),512+f.length*8);return binl2rstr(binl_md5(d.concat(g),512+128))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?\"0123456789ABCDEF\":\"0123456789abcdef\";var b=\"\";var a;for(var d=0;d<c.length;d++){a=c.charCodeAt(d);b+=f.charAt((a>>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b=\"\";var d=-1;var a,e;while(++d<c.length){a=c.charCodeAt(d);e=d+1<c.length?c.charCodeAt(d+1):0;if(55296<=a&&a<=56319&&56320<=e&&e<=57343){a=65536+((a&1023)<<10)+(e&1023);d++}if(a<=127){b+=String.fromCharCode(a)}else{if(a<=2047){b+=String.fromCharCode(192|((a>>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binl(b){var a=Array(b.length>>2);for(var c=0;c<a.length;c++){a[c]=0}for(var c=0;c<b.length*8;c+=8){a[c>>5]|=(b.charCodeAt(c/8)&255)<<(c%%32)}return a}function binl2rstr(b){var a=\"\";for(var c=0;c<b.length*32;c+=8){a+=String.fromCharCode((b[c>>5]>>>(c%%32))&255)}return a}function binl_md5(p,k){p[k>>5]|=128<<((k)%%32);p[(((k+64)>>>9)<<4)+14]=k;var o=1732584193;var n=-271733879;var m=-1732584194;var l=271733878;for(var g=0;g<p.length;g+=16){var j=o;var h=n;var f=m;var e=l;o=md5_ff(o,n,m,l,p[g+0],7,-680876936);l=md5_ff(l,o,n,m,p[g+1],12,-389564586);m=md5_ff(m,l,o,n,p[g+2],17,606105819);n=md5_ff(n,m,l,o,p[g+3],22,-1044525330);o=md5_ff(o,n,m,l,p[g+4],7,-176418897);l=md5_ff(l,o,n,m,p[g+5],12,1200080426);m=md5_ff(m,l,o,n,p[g+6],17,-1473231341);n=md5_ff(n,m,l,o,p[g+7],22,-45705983);o=md5_ff(o,n,m,l,p[g+8],7,1770035416);l=md5_ff(l,o,n,m,p[g+9],12,-1958414417);m=md5_ff(m,l,o,n,p[g+10],17,-42063);n=md5_ff(n,m,l,o,p[g+11],22,-1990404162);o=md5_ff(o,n,m,l,p[g+12],7,1804603682);l=md5_ff(l,o,n,m,p[g+13],12,-40341101);m=md5_ff(m,l,o,n,p[g+14],17,-1502002290);n=md5_ff(n,m,l,o,p[g+15],22,1236535329);o=md5_gg(o,n,m,l,p[g+1],5,-165796510);l=md5_gg(l,o,n,m,p[g+6],9,-1069501632);m=md5_gg(m,l,o,n,p[g+11],14,643717713);n=md5_gg(n,m,l,o,p[g+0],20,-373897302);o=md5_gg(o,n,m,l,p[g+5],5,-701558691);l=md5_gg(l,o,n,m,p[g+10],9,38016083);m=md5_gg(m,l,o,n,p[g+15],14,-660478335);n=md5_gg(n,m,l,o,p[g+4],20,-405537848);o=md5_gg(o,n,m,l,p[g+9],5,568446438);l=md5_gg(l,o,n,m,p[g+14],9,-1019803690);m=md5_gg(m,l,o,n,p[g+3],14,-187363961);n=md5_gg(n,m,l,o,p[g+8],20,1163531501);o=md5_gg(o,n,m,l,p[g+13],5,-1444681467);l=md5_gg(l,o,n,m,p[g+2],9,-51403784);m=md5_gg(m,l,o,n,p[g+7],14,1735328473);n=md5_gg(n,m,l,o,p[g+12],20,-1926607734);o=md5_hh(o,n,m,l,p[g+5],4,-378558);l=md5_hh(l,o,n,m,p[g+8],11,-2022574463);m=md5_hh(m,l,o,n,p[g+11],16,1839030562);n=md5_hh(n,m,l,o,p[g+14],23,-35309556);o=md5_hh(o,n,m,l,p[g+1],4,-1530992060);l=md5_hh(l,o,n,m,p[g+4],11,1272893353);m=md5_hh(m,l,o,n,p[g+7],16,-155497632);n=md5_hh(n,m,l,o,p[g+10],23,-1094730640);o=md5_hh(o,n,m,l,p[g+13],4,681279174);l=md5_hh(l,o,n,m,p[g+0],11,-358537222);m=md5_hh(m,l,o,n,p[g+3],16,-722521979);n=md5_hh(n,m,l,o,p[g+6],23,76029189);o=md5_hh(o,n,m,l,p[g+9],4,-640364487);l=md5_hh(l,o,n,m,p[g+12],11,-421815835);m=md5_hh(m,l,o,n,p[g+15],16,530742520);n=md5_hh(n,m,l,o,p[g+2],23,-995338651);o=md5_ii(o,n,m,l,p[g+0],6,-198630844);l=md5_ii(l,o,n,m,p[g+7],10,1126891415);m=md5_ii(m,l,o,n,p[g+14],15,-1416354905);n=md5_ii(n,m,l,o,p[g+5],21,-57434055);o=md5_ii(o,n,m,l,p[g+12],6,1700485571);l=md5_ii(l,o,n,m,p[g+3],10,-1894986606);m=md5_ii(m,l,o,n,p[g+10],15,-1051523);n=md5_ii(n,m,l,o,p[g+1],21,-2054922799);o=md5_ii(o,n,m,l,p[g+8],6,1873313359);l=md5_ii(l,o,n,m,p[g+15],10,-30611744);m=md5_ii(m,l,o,n,p[g+6],15,-1560198380);n=md5_ii(n,m,l,o,p[g+13],21,1309151649);o=md5_ii(o,n,m,l,p[g+4],6,-145523070);l=md5_ii(l,o,n,m,p[g+11],10,-1120210379);m=md5_ii(m,l,o,n,p[g+2],15,718787259);n=md5_ii(n,m,l,o,p[g+9],21,-343485551);o=safe_add(o,j);n=safe_add(n,h);m=safe_add(m,f);l=safe_add(l,e)}return Array(o,n,m,l)}function md5_cmn(h,e,d,c,g,f){return safe_add(bit_rol(safe_add(safe_add(e,h),safe_add(c,f)),g),d)}function md5_ff(g,f,k,j,e,i,h){return md5_cmn((f&k)|((~f)&j),g,f,e,i,h)}function md5_gg(g,f,k,j,e,i,h){return md5_cmn((f&j)|(k&(~j)),g,f,e,i,h)}function md5_hh(g,f,k,j,e,i,h){return md5_cmn(f^k^j,g,f,e,i,h)}function md5_ii(g,f,k,j,e,i,h){return md5_cmn(k^(f|(~j)),g,f,e,i,h)}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function bit_rol(a,b){return(a<<b)|(a>>>(32-b))};"
+ "\n//-->\n</SCRIPT>\n");
+
+ fprintf(cgiOut,
+ "</HEAD>\n"
+ "<BODY ONLOAD=\"document.wfm.username.focus(); document.wfm.Login.value='MD5 Login';\" BGCOLOR=\"#FFFFFF\">\n"
+ "<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 CLASS=\"twh\"><TR><TD VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"
+ "<FORM NAME=\"wfm\" ACTION=\"%s\" METHOD=\"POST\" ENCTYPE=\"multipart/form-data\">\n\n", cgiScriptName);
+
+ fprintf(cgiOut,
+ "<TABLE WIDTH=\"400\" BGCOLOR=\"#F0F0F0\" BORDER=0 CELLSPACING=0 CELLPADDING=1 CLASS=\"tbr\">\n"
+ " <TR><TD COLSPAN=2 BGCOLOR=\"#004080\"><FONT COLOR=\"#FFFFFF\">&nbsp; Authentication Required</FONT></TD></TR>\n"
+ " <TR><TD WIDTH=30>&nbsp;</TD><TD>\n"
+ " &nbsp;<BR>Enter your login credentials:<P>\n"
+ " <TABLE>\n"
+ " <TR><TD>Username:</TD><TD><INPUT TYPE=\"TEXT\" NAME=\"username\" SIZE=\"32\" VALUE=\"\"></TD></TR>\n"
+ " <TR><TD>Password:</TD><TD><INPUT TYPE=\"PASSWORD\" NAME=\"password\" SIZE=\"32\" VALUE=\"\"></TD></TR>\n"
+ " </TABLE><P>\n"
+ " </TD></TR>\n"
+ " <TR><TD COLSPAN=2>\n"
+ " <P><CENTER>\n"
+ " <INPUT TYPE=\"HIDDEN\" VALUE=\"login\" NAME=\"action\">\n"
+ " <INPUT TYPE=\"HIDDEN\" VALUE=\"%s\" NAME=\"directory\">\n"
+ " <INPUT TYPE=\"SUBMIT\" VALUE=\" %s Login \" NAME=\"Login\" "
+ "onClick=\"self.location='?directory=%s&amp;login=client&amp;token=' + hex_md5('%s:' + document.wfm.username.value + ':' + document.wfm.password.value); return false;\" >\n"
+ " </CENTER><BR>\n"
+ " </TD></TR>\n"
+ "</TABLE></FORM>\n\n"
+ "</TD></TR></TABLE>\n"
+ "</BODY>\n</HTML>\n", virt_dirname, (getenv("HTTPS")) ? "SSL" : "UNSECURE", virt_dirname, cgiRemoteAddr);
+
+}
+
+
+//
+// Text Area File Editor
+//
+// size info: the file is read as raw text with size in bytes
+// the output is escaped in textarea resulting in more length
+// however the output from textarea is saved unescaped so the
+// resulting file size is same as before escaping
+//
+// size variable is used by edit_save() to read the specified
+// lenght from cgiFormString() however fwrite() uses strlen()
+//
+void edit_ui(void) {
+ FILE *input;
+ char *buff;
+ char backup[4];
+ char *bkcolor;
+ int size;
+
+ checkfilename(NULL);
+
+ cgiFormString("backup", backup, sizeof(backup));
+
+ if(strcmp("yes", backup)==0)
+ bkcolor="background-color:#404040; color:#FFFFFF;";
+ else
+ bkcolor="background-color:#EEEEEE; color:#000000;";
+
+ input=fopen(phys_filename, "r");
+ if(input==NULL)
+ error("Unable to open file.<BR>%s", strerror(errno));
+
+ fseek(input, 0, SEEK_END);
+ size=ftell(input);
+ fseek(input, 0, SEEK_SET);
+
+ buff=(char *) malloc(size+1);
+ if(buff==NULL)
+ error("Unable to allocate memory.");
+
+ memset(buff, 0, size+1);
+
+ fread(buff, size, 1, input);
+ fclose(input);
+
+ cgiHeaderContentType("text/html");
+ html_title("Editor");
+
+ fprintf(cgiOut,
+ "<SCRIPT LANGUAGE=\"JavaScript\" TYPE=\"text/javascript\">\n"
+ "<!--\n"
+ "function chwrap() { \n"
+ " if(document.EDITOR.content.wrap=='off') { \n"
+ " document.EDITOR.content.wrap='soft';\n"
+ " document.EDITOR.wrapbtn.style.backgroundColor='#404040';\n"
+ " document.EDITOR.wrapbtn.style.color='#FFFFFF';\n"
+ " } else { \n"
+ " document.EDITOR.content.wrap='off';\n"
+ " document.EDITOR.wrapbtn.style.backgroundColor='#EEEEEE';\n"
+ " document.EDITOR.wrapbtn.style.color='#000000';\n"
+ " } \n"
+ "} \n"
+ "function chbak() { \n"
+ " if(document.EDITOR.backup.value=='yes') { \n"
+ " document.EDITOR.backup.value='no'; \n"
+ " document.EDITOR.bakbtn.style.backgroundColor='#EEEEEE';\n"
+ " document.EDITOR.bakbtn.style.color='#000000';\n"
+ " } else { \n"
+ " document.EDITOR.backup.value='yes'; \n"
+ " document.EDITOR.bakbtn.style.backgroundColor='#404040';\n"
+ " document.EDITOR.bakbtn.style.color='#FFFFFF';\n"
+ " } \n"
+ "} \n"
+ "//-->\n"
+ "</SCRIPT>\n"
+ "<STYLE TYPE=\"text/css\"><!-- \n"
+ "html, body, table, textarea, form { box-sizing: border-box; width:100%%; height:100%%; margin:0px; padding:0px; } \n"
+ "--></STYLE>\n"
+ "</HEAD>\n"
+ "<BODY>\n"
+ "<FORM NAME=\"EDITOR\" ACTION=\"?action=edit_save\" METHOD=\"POST\" ENCTYPE=\"multipart/form-data\" STYLE=\"height:100%%;\">\n"
+ "<TABLE BGCOLOR=\"#EEEEEE\" BORDER=0 CELLSPACING=0 CELLPADDING=5 STYLE=\"height:100%%;\">\n"
+ "<TR STYLE=\"height:1%%;\">\n"
+ "<TD ALIGN=\"LEFT\" VALIGN=\"MIDDLE\" BGCOLOR=\"#CCCCCC\">\n"
+ "<IMG SRC=\"%sedit.gif\" BORDER=0 ALIGN=\"MIDDLE\" ALT=\"EDIT\">\n"
+ "File Editor: %s\n"
+ "</TD>\n"
+ "<TD BGCOLOR=\"#CCCCCC\" ALIGN=\"RIGHT\">"
+ "<INPUT TYPE=\"button\" ID=\"bakbtn\" onClick=\"chbak()\" VALUE=\"Backup\" STYLE=\"border:none; %s \"> \n"
+ "<INPUT TYPE=\"button\" ID=\"wrapbtn\" onClick=\"chwrap()\" VALUE=\"Wrap\" STYLE=\"border:none; background-color:#404040; color:#FFFFFF;\">\n"
+ "</TD>\n"
+ "</TR>\n"
+ "<TR STYLE=\"height:99%%;\">\n"
+ "<TD COLSPAN=2 ALIGN=\"CENTER\" VALIGN=\"MIDDLE\" STYLE=\"height:100%%;\">\n"
+ "<TEXTAREA COLS=\"80\" ROWS=\"24\" NAME=\"content\" STYLE=\"resize:none;\">",
+ ICONSURL, virt_filename, bkcolor);
+
+
+ cgiHtmlEscapeData(buff, size);
+
+ fprintf(cgiOut, "</TEXTAREA>\n"
+ "</TD>\n"
+ "</TR>\n"
+ "<TR>\n"
+ "<TD COLSPAN=2 ALIGN=\"RIGHT\" VALIGN=\"MIDDLE\">\n"
+ "<INPUT TYPE=\"SUBMIT\" VALUE=\"Save\" onClick=\"document.EDITOR.size.value=document.EDITOR.content.value.length+1; return true;\">&nbsp;\n"
+ "<INPUT TYPE=\"BUTTON\" VALUE=\"Cancel\" onClick=\"self.location='?directory=%s&amp;token=%s'; return true;\">\n"
+ "</TD>\n"
+ "</TR>\n"
+ "</TABLE>\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"action\" VALUE=\"edit_save\">\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"filename\" VALUE=\"%s\">\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"size\" VALUE=\"%d\">\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"directory\" VALUE=\"%s\">\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"token\" VALUE=\"%s\">\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"backup\" VALUE=\"%s\">\n"
+ "</FORM></BODY></HTML>\n",
+ virt_dirname, token, virt_filename, size, virt_dirname, token, backup);
+
+ free(buff);
+
+} \ No newline at end of file
diff --git a/dir.c b/dir.c
new file mode 100644
index 0000000..ed38d33
--- /dev/null
+++ b/dir.c
@@ -0,0 +1,607 @@
+#include "wfm.h"
+
+char DIRIMG[256], AUPIMG[256], ADNIMG[256], GENIMG[256], NEWIMG[256], ZIPIMG[256];
+char IMGIMG[256], OFFIMG[256], PDFIMG[256];
+char TXTIMG[256], EXEIMG[256], MEDIMG[257], ISOIMG[256], LNKIMG[256];
+regex_t reg_zip, reg_img, reg_pdf, reg_exe, reg_txt, reg_off, reg_med, reg_iso;
+
+char M_HR[]="<FONT COLOR=\"#000000\" STYLE=\"font-weight:bold;\">(Last Hour)";
+char M_DAY[]="<FONT COLOR=\"#505050\" STYLE=\"font-weight:bold;\">(Last Day)";
+char M_WK[]="<FONT COLOR=\"#000000\">(Last Week)";
+//char M_2WK[]="<FONT COLOR=\"#000000\">(Last 2 Weeks)";
+char M_MO[]="<FONT COLOR=\"#505050\">(Last Month)";
+//char M_2MO[]="<FONT COLOR=\"#505050\">(Last 2 Months)";
+//char M_6MO[]="<FONT COLOR=\"#707070\">(Last 6 Months)";
+char M_YR[]="<FONT COLOR=\"#909090\">(Last Year)";
+char M_OLD[]="<FONT COLOR=\"#C0C0C0\">(Old)";
+
+char tNORMAL_COLOR[]="FFFFFF";
+char tHIGH_COLOR[]="33CC33";
+char tHL_COLOR[]="FFD700";
+
+static const char *access_string[]={ "none", "readonly", "readwrite" };
+
+void dir_icoinita(void) {
+ snprintf(DIRIMG, sizeof(DIRIMG), "<IMG SRC=\"%sdir.gif\" ALT=\"Dir\" ALIGN=\"MIDDLE\" BORDER=\"0\">", ICONSURL);
+ snprintf(LNKIMG, sizeof(LNKIMG), "<IMG SRC=\"%slnk.gif\" ALT=\"Symlink\" ALIGN=\"MIDDLE\" BORDER=\"0\">", ICONSURL);
+ snprintf(AUPIMG, sizeof(AUPIMG), "<IMG SRC=\"%saup.gif\" ALT=\"Up\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"7\" HEIGHT=\"4\">", ICONSURL);
+ snprintf(ADNIMG, sizeof(ADNIMG), "<IMG SRC=\"%sadn.gif\" ALT=\"Down\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"7\" HEIGHT=\"4\">", ICONSURL);
+ snprintf(GENIMG, sizeof(GENIMG), "<IMG SRC=\"%sgen.gif\" ALT=\"Unknown\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(NEWIMG, sizeof(NEWIMG), "<IMG SRC=\"%sarr.gif\" ALT=\"New\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(ZIPIMG, sizeof(ZIPIMG), "<IMG SRC=\"%szip.gif\" ALT=\"Archive\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(IMGIMG, sizeof(IMGIMG), "<IMG SRC=\"%simg.gif\" ALT=\"Image\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(OFFIMG, sizeof(OFFIMG), "<IMG SRC=\"%soff.gif\" ALT=\"Office File\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(PDFIMG, sizeof(PDFIMG), "<IMG SRC=\"%spdf.gif\" ALT=\"PDF\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(TXTIMG, sizeof(TXTIMG), "<IMG SRC=\"%stxt.gif\" ALT=\"Text\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(EXEIMG, sizeof(EXEIMG), "<IMG SRC=\"%sexe.gif\" ALT=\"Exec\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(MEDIMG, sizeof(MEDIMG), "<IMG SRC=\"%smed.gif\" ALT=\"Multimedia\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+ snprintf(ISOIMG, sizeof(ISOIMG), "<IMG SRC=\"%siso.gif\" ALT=\"Disk Image\" ALIGN=\"MIDDLE\" BORDER=\"0\" WIDTH=\"16\" HEIGHT=\"16\">", ICONSURL);
+
+ if(
+ regcomp(&reg_zip, "\\.(zip|rar|tar|gz|tgz|z|arj|bz|tbz|7z|xz)$", REG_EXTENDED | REG_ICASE)!=0 ||
+ regcomp(&reg_img, "\\.(gif|png|tif|tiff|jpg|jpeg)$", REG_EXTENDED | REG_ICASE)!=0 ||
+ regcomp(&reg_off, "\\.(doc|docx|rtf|dot|xls|xlsx|ppt|pptx|off)$", REG_EXTENDED | REG_ICASE)!=0 ||
+ regcomp(&reg_iso, "\\.(iso|flp|img|nrg|dmg)$", REG_EXTENDED | REG_ICASE)!=0 ||
+ regcomp(&reg_med, "\\.(mp3|mp4|vaw|mov|avi|ivr|mkv)$", REG_EXTENDED | REG_ICASE)!=0 ||
+ regcomp(&reg_pdf, "\\.(pdf|ps|eps|ai)$", REG_EXTENDED | REG_ICASE)!=0 ||
+ regcomp(&reg_txt, "\\.(txt|asc|nfo|me|log|htm|html|shtml|js|jsp|php|xml|dtd|css|bas|c|h|cpp|cmd|bat|sh|ksh|awk|reg|log|bak|cfg|py)$", REG_EXTENDED | REG_ICASE)!=0 ||
+ regcomp(&reg_exe, "\\.(exe|cmd|vbs|bat|com|pif)$", REG_EXTENDED | REG_ICASE)!=0
+ ) error("Unable to compile regex.");
+
+}
+
+
+//
+// Display directory list main panel
+//
+void dirlist(void) {
+ ASDIR *direntry;
+ off_t size, totalsize=0;
+ char highlight[VIRT_FILENAME_SIZE];
+ char namepfx[1024], sizepfx[1024], datepfx[1024];
+ char rtime[64], mtime[64], atime[64];
+ char *stime;
+ char sortby[64];
+ char *name, *icon, *linecolor;
+ int nentr=0, e;
+ int editable;
+ time_t now;
+
+ time(&now);
+
+ cgiFormStringNoNewlines("highlight", highlight, VIRT_FILENAME_SIZE-1);
+ cgiFormStringNoNewlines("sortby", sortby, 63);
+ if(strlen(sortby)<4)
+ snprintf(sortby, 63, "name");
+
+ //
+ // Get and Print Directory Entries
+ //
+ if(strcmp(sortby, "name")==0)
+ nentr=asscandir(phys_dirname, &direntry, namesort);
+ else if(strcmp(sortby, "rname")==0)
+ nentr=asscandir(phys_dirname, &direntry, rnamesort);
+ else if(strcmp(sortby, "size")==0)
+ nentr=asscandir(phys_dirname, &direntry, sizesort);
+ else if(strcmp(sortby, "rsize")==0)
+ nentr=asscandir(phys_dirname, &direntry, rsizesort);
+ else if(strcmp(sortby, "date")==0)
+ nentr=asscandir(phys_dirname, &direntry, timesort);
+ else if(strcmp(sortby, "rdate")==0)
+ nentr=asscandir(phys_dirname, &direntry, rtimesort);
+ else
+ nentr=asscandir(phys_dirname, &direntry, namesort);
+
+ dir_icoinita();
+
+ cgiHeaderContentType("text/html");
+
+ //
+ // HTML HEADER
+ //
+ fprintf(cgiOut,
+ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n\"http://www.w3.org/TR/html4/loose.dtd\">\n"
+ "\n%s"
+ "<HTML>"
+ "<HEAD>\n"
+ "<TITLE>%s : %c%s</TITLE>\n"
+ "<SCRIPT LANGUAGE=\"JavaScript\" TYPE=\"text/javascript\">\n"
+ "<!--\n"
+ " function checkUncheckAll(checkAllState, cbGroup) {\n"
+ " if(cbGroup.length > 0) {\n"
+ " for (i = 0; i < cbGroup.length; i++) {\n"
+ " cbGroup[i].checked = checkAllState.checked;\n"
+ " }\n"
+ " }\n"
+ " else {\n"
+ " cbGroup.checked = checkAllState.checked;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " function xmlhttpPost(strURL) {\n"
+ " var xmlHttpReq = false;\n"
+ " var self = this;\n"
+ " var method = \"GET\";\n"
+ "\n"
+ " if (window.XMLHttpRequest) {\n"
+ " self.xmlHttpReq = new XMLHttpRequest();\n"
+ " }\n"
+ " else if (window.ActiveXObject) {\n"
+ " self.xmlHttpReq = new ActiveXObject(\"Microsoft.XMLHTTP\");\n"
+ " }\n"
+ " if (self.xmlHttpReq != null) {\n"
+ " self.xmlHttpReq.open(method, strURL, true);\n"
+ " self.xmlHttpReq.onreadystatechange = function () {\n"
+ " if (self.xmlHttpReq.readyState == 4) {\n"
+ " var result = document.getElementById(\"Upload_Status\");\n"
+ " result.value = self.xmlHttpReq.responseText;\n"
+ " }\n"
+ " }\n"
+ " self.xmlHttpReq.send(null);\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " function start() {\n"
+ " setInterval('xmlhttpPost(\"%s?ea=upstat&upload_id=%s\");', \"100\");\n"
+ " }\n"
+ "\n"
+ "//-->\n"
+ "</SCRIPT>\n"
+ "<STYLE TYPE=\"text/css\">\n"
+ "<!--\n"
+ "A:link {text-decoration: none; color:#0000CE; } \n"
+ "A:visited {text-decoration: none; color:#0000CE; } \n"
+ "A:active {text-decoration: none; color:#FF0000; } \n"
+ "A:hover {text-decoration: none; color:#FF0000; } \n"
+ "html, body, table { width:100%%; margin:0px; padding:0px; border:none; } \n"
+ "img { vertical-align:middle; } \n"
+ "td, th { font-family: Tahoma, Arial, Geneva, sans-serif; font-size:11px; margin:0px; padding:2px; border:none; } \n"
+ "input { border-color:#000000;border-style:solid; \n"
+ "font-family: Tahoma, Arial, Geneva, sans-serif; font-size:11px; }\n"
+ ".hovout { border: none; padding: 0px; background-color: transparent; color: #0000CE; }\n"
+ ".hovin { border: none; padding: 0px; background-color: transparent; color: #FF0000; }\n"
+ "//-->\n"
+ "</STYLE>\n"
+ "<META HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html;charset=UTF-8\">\n"
+ "<LINK REL=\"icon\" TYPE=\"image/gif\" HREF=\"%swfmicon.gif\">\n"
+ "</HEAD>\n"
+ "<BODY BGCOLOR=\"#FFFFFF\">\n"
+ "<FORM ACTION=\"%s\" METHOD=\"POST\" ENCTYPE=\"multipart/form-data\" onsubmit=\"start()\">\n",
+ copyright, TAGLINE, (strlen(virt_dirname)>0) ? ' ' : '/', virt_dirname, cgiScriptName, "1234", ICONSURL, cgiScriptName);
+
+
+
+
+ //
+ // TITLE
+ //
+ fprintf(cgiOut,
+ "<!-- TITLE --> \n"
+ "<TABLE WIDTH=\"100%%\" BGCOLOR=\"#FFFFFF\" CELLPADDING=\"0\" CELLSPACING=\"0\" BORDER=\"0\" STYLE=\"height:28px;\">\n"
+ "<TR>\n"
+ "<TD WIDTH=\"100%%\" BGCOLOR=\"#0072c6\" VALIGN=\"MIDDLE\" ALIGN=\"LEFT\" STYLE=\"color:#FFFFFF; font-weight:bold;\">\n"
+ "&nbsp;<IMG SRC=\"%swfmicon.gif\" ALIGN=\"MIDDLE\" ALT=\"WFM\">\n"
+ "%s : %c%s \n"
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"RIGHT\" STYLE=\"color:#000000; font-weight:bold; white-space:nowrap\">\n"
+ ,
+ ICONSURL, TAGLINE, (strlen(virt_dirname)>0) ? ' ' : '/', virt_dirname
+ );
+
+
+ // lock / unlock
+ if(!access_as_user && users_defined)
+ fprintf(cgiOut,
+ "<A HREF=\"%s?action=login&amp;directory=%s\">"\
+ "&nbsp;<IMG SRC=\"%s%s.gif\" ALIGN=\"MIDDLE\" BORDER=\"0\" ALT=\"Access\"></A>&nbsp;%s\n",
+ cgiScriptName, virt_dirname, ICONSURL, access_string[access_level], access_string[access_level]);
+ else
+ fprintf(cgiOut,
+ "<A HREF=\"%s?directory=%s\"><IMG SRC=\"%s%s.gif\" BORDER=\"0\" ALIGN=\"MIDDLE\" ALT=\"Access\">"
+ "</A>&nbsp;%s&nbsp;<IMG SRC=\"%suser.gif\" ALIGN=\"MIDDLE\" ALT=\"User\">&nbsp;%s&nbsp;\n",
+ cgiScriptName, virt_dirname, ICONSURL, access_string[access_level], access_string[access_level], ICONSURL, loggedinuser);
+
+ // about / version
+ fprintf(cgiOut,
+ "</TD><TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"RIGHT\" STYLE=\"color:#000000; font-weight:bold; white-space:nowrap\">"
+ "&nbsp;<IMG SRC=\"%snet.gif\" ALIGN=\"MIDDLE\" ALT=\"Client IP\">&nbsp;%s&nbsp;</TD>"
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"RIGHT\" STYLE=\"color:#000000; font-weight:bold; white-space:nowrap\">"
+ "<A HREF=\"%s?action=about&amp;directory=%s&amp;token=%s\"><IMG BORDER=\"0\" SRC=\"%sver.gif\" ALIGN=\"MIDDLE\" ALT=\"Version\"></A>&nbsp;v%s&nbsp;"
+ "</TD>\n"\
+ "</TR>\n"\
+ "</TABLE>\n",
+ ICONSURL, cgiRemoteAddr, cgiScriptName, virt_dirname, token, ICONSURL, VERSION
+ );
+
+
+
+ //
+ // TOOLBAR
+ //
+ fprintf(cgiOut,
+ "<!-- TOOLBAR -->\n"\
+ "<TABLE WIDTH=\"100%%\" BGCOLOR=\"#FFFFFF\" CELLPADDING=\"0\" CELLSPACING=\"0\" BORDER=\"0\" STYLE=\"height:28px;\">\n"\
+ "<TR>\n"\
+ "<!-- DIR-UP -->\n"\
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"\
+ "<A HREF=\"%s?sortby=%s&amp;directory=%s&amp;token=%s\">\n"\
+ "<IMG SRC=\"%sdir_up.gif\" BORDER=0 ALIGN=\"MIDDLE\" WIDTH=\"16\" HEIGHT=\"16\" ALT=\"Dir Up\">&nbsp;Up\n"\
+ "</A>\n"\
+ "</TD>\n",
+ cgiScriptName, sortby, virt_parent, token, ICONSURL);
+
+ fprintf(cgiOut,
+ "<!-- HOME -->\n"\
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"\
+ "<A HREF=\"%s?sortby=%s&amp;directory=/&amp;token=%s\">\n"\
+ "<IMG SRC=\"%shome.gif\" BORDER=0 ALIGN=\"MIDDLE\" WIDTH=\"16\" HEIGHT=\"16\" ALT=\"Home\">&nbsp;Home\n"\
+ "</A>\n"\
+ "</TD>\n",
+ cgiScriptName, sortby, token, ICONSURL);
+
+ fprintf(cgiOut,
+ "<!-- RELOAD -->\n"\
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"\
+ "<A HREF=\"%s?sortby=%s&amp;directory=%s&amp;token=%s\">\n"\
+ "<IMG SRC=\"%sreload.gif\" BORDER=0 ALIGN=\"MIDDLE\" ALT=\"Reload\">&nbsp;Refresh\n"\
+ "</A>\n"\
+ "</TD>\n",
+ cgiScriptName, sortby, virt_dirname, token, ICONSURL);
+
+ fprintf(cgiOut,
+ "<!-- MULTI DELETE -->\n"\
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"\
+ "<INPUT TYPE=\"IMAGE\" SRC=\"%sdelete.gif\" STYLE=\"border: none; padding: 0px; vertical-align:middle;\" ALT=\"Delete\" ALIGN=\"MIDDLE\" NAME=\"multi_delete_prompt\" VALUE=\"Delete\">\n"
+ "<INPUT TYPE=\"SUBMIT\" CLASS=\"hovout\" NAME=\"multi_delete_prompt\" VALUE=\"Delete\" onMouseOver=\"this.className='hovin';\" onMouseOut=\"this.className='hovout';\">\n"
+ "</TD>\n",
+ ICONSURL);
+
+ fprintf(cgiOut,
+ "<!-- MULTI MOVE -->\n"\
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"\
+ "<INPUT TYPE=\"IMAGE\" SRC=\"%smove.gif\" STYLE=\"border: none; padding: 0px; vertical-align:middle; \" ALT=\"Move\" ALIGN=\"MIDDLE\" NAME=\"multi_move_prompt\" VALUE=\"Move\">\n"
+ "<INPUT TYPE=\"SUBMIT\" CLASS=\"hovout\" NAME=\"multi_move_prompt\" VALUE=\"Move\" onMouseOver=\"this.className='hovin';\" onMouseOut=\"this.className='hovout';\">\n"
+ "</TD>\n",
+ ICONSURL);
+
+ fprintf(cgiOut,
+ "<!-- NEWDIR -->\n"\
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"\
+ "<A HREF=\"%s?action=mkdir_prompt&amp;directory=%s&amp;token=%s\" >\n"\
+ "<IMG SRC=\"%smkdir.gif\" BORDER=0 ALIGN=\"MIDDLE\" ALT=\"New Folder\">&nbsp;New Folder\n"\
+ "</A>\n"\
+ "</TD>\n",
+ cgiScriptName, virt_dirname, token, ICONSURL);
+
+
+ fprintf(cgiOut,
+ "<!-- NEWFILE -->\n"\
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"\
+ "<A HREF=\"%s?action=mkfile_prompt&amp;directory=%s&amp;token=%s\" >\n"\
+ "<IMG SRC=\"%smkfile.gif\" BORDER=0 ALIGN=\"MIDDLE\" ALT=\"New File\">&nbsp;New File\n"\
+ "</A>\n"\
+ "</TD>\n",
+ cgiScriptName, virt_dirname, token, ICONSURL);
+
+
+
+ fprintf(cgiOut,
+ "<!-- UPLOAD -->\n"\
+ "<TD BGCOLOR=\"#F1F1F1\" VALIGN=\"MIDDLE\" ALIGN=\"CENTER\">\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"directory\" VALUE=\"%s\">\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"token\" VALUE=\"%s\">\n"
+ "<INPUT TYPE=\"hidden\" NAME=\"upload_id\" VALUE=\"%s\">\n"
+ "<INPUT TYPE=\"file\" NAME=\"filename\">&nbsp;\n"
+ "<INPUT TYPE=\"submit\" NAME=\"upload\" ID=\"Upload_Status\" VALUE=\"Upload\" >\n"
+ "</TD>\n"
+ "</TR>\n"
+ "</TABLE>\n",
+ virt_dirname, token, "1234");
+
+ //
+ // SORT BY
+ //
+ if(strcmp(sortby, "size")==0) {
+ snprintf(namepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=name\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Filename</A>", cgiScriptName, virt_dirname, token);
+ snprintf(sizepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=rsize\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Size</A>&nbsp;%s", cgiScriptName, virt_dirname, token, ADNIMG);
+ snprintf(datepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=date\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Modified</A>", cgiScriptName, virt_dirname, token);
+ } else if(strcmp(sortby, "rsize")==0) {
+ snprintf(namepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=name\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Filename</A>", cgiScriptName, virt_dirname, token);
+ snprintf(sizepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=size\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Size</A>&nbsp;%s", cgiScriptName, virt_dirname, token, AUPIMG);
+ snprintf(datepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=date\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Modified</A>", cgiScriptName, virt_dirname, token);
+ } else if(strcmp(sortby, "date")==0) {
+ snprintf(namepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=name\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Filename</A>", cgiScriptName, virt_dirname, token);
+ snprintf(sizepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=size\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Size</A>", cgiScriptName, virt_dirname, token);
+ snprintf(datepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=rdate\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Modified</A>&nbsp;%s", cgiScriptName, virt_dirname, token, ADNIMG);
+ } else if(strcmp(sortby, "rdate")==0) {
+ snprintf(namepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=name\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Filename</A>", cgiScriptName, virt_dirname, token);
+ snprintf(sizepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=size\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Size</A>", cgiScriptName, virt_dirname, token);
+ snprintf(datepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=date\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Modified</A>&nbsp;%s", cgiScriptName, virt_dirname, token, AUPIMG);
+ } else if(strcmp(sortby, "name")==0) {
+ snprintf(namepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=rname\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Filename</A>&nbsp;%s", cgiScriptName, virt_dirname, token, ADNIMG);
+ snprintf(sizepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=size\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Size</A>", cgiScriptName, virt_dirname, token);
+ snprintf(datepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=date\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Modified</A>", cgiScriptName, virt_dirname, token);
+ } else if(strcmp(sortby, "rname")==0) {
+ snprintf(namepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=name\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Filename</A>&nbsp;%s", cgiScriptName, virt_dirname, token, AUPIMG);
+ snprintf(sizepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=size\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Size</A>", cgiScriptName, virt_dirname, token);
+ snprintf(datepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=date\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Modified</A>", cgiScriptName, virt_dirname, token);
+ } else {
+ snprintf(namepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=name\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Filename</A>", cgiScriptName, virt_dirname, token);
+ snprintf(sizepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=size\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Size</A>", cgiScriptName, virt_dirname, token);
+ snprintf(datepfx, 1024, "&nbsp;<A HREF=\"%s?directory=%s&amp;token=%s&amp;sortby=date\" STYLE=\"text-decoration: none; color:#FFFFFF;\">Modified</A>", cgiScriptName, virt_dirname, token);
+ }
+
+
+ // SORTBY ROW + dir files display
+ fprintf(cgiOut,
+ "<!-- MAIN FILE TABLE --> \n"\
+ "<TABLE WIDTH=\"100%%\" BGCOLOR=\"#FFFFFF\" CELLPADDING=0 CELLSPACING=0 BORDER=0 >\n"\
+ "<!-- SORTBY LINE -->\n"\
+ "<TR BGCOLOR=\"#FFFFFF\" >\n"\
+ "<TD ALIGN=\"left\" WIDTH=\"50%%\" BGCOLOR=\"#A0A0A0\">\n"\
+ "<FONT COLOR=\"#FFFFFF\">\n"\
+ "<INPUT TYPE=\"CHECKBOX\" NAME=\"CHECKALL\" STYLE=\"padding: 0px; border: none;\" ONCLICK=\"checkUncheckAll(this, multiselect_filename);\">\n"
+ "%s\n"\
+ "</FONT>\n"\
+ "</TD>\n"\
+ "<TD ALIGN=\"right\" BGCOLOR=\"#A0A0A0\">\n"\
+ "<FONT COLOR=\"#FFFFFF\">\n"\
+ "%s\n"\
+ "</FONT>\n"\
+ "</TD>\n"\
+ "<TD ALIGN=\"right\" BGCOLOR=\"#A0A0A0\">\n"\
+ "<FONT COLOR=\"#FFFFFF\">\n"\
+ "%s\n"\
+ "</FONT>\n"\
+ "</TD>\n"\
+ "<TD ALIGN=\"right\" BGCOLOR=\"#A0A0A0\">\n"\
+ "&nbsp;"\
+ "</TD>"\
+ "<TD ALIGN=\"left\" BGCOLOR=\"#A0A0A0\">\n"\
+ "<FONT COLOR=\"#FFFFFF\">\n"\
+ "&nbsp;\n"\
+ "</FONT>\n"\
+ "</TD>\n"\
+ "</TR>\n"
+ "<!-- End of Header -->\n\n",
+ namepfx, sizepfx, datepfx);
+
+
+ // Directories
+ for(e=0; e<nentr; e++) {
+ if(!S_ISDIR(direntry[e].type))
+ continue;
+ if(direntry[e].name[0]=='.')
+ continue;
+
+ name=direntry[e].name;
+ if(recursive_du) {
+ snprintf(phys_filename, PHYS_FILENAME_SIZE, "%s/%s", phys_dirname, direntry[e].name);
+ size=du(phys_filename);
+ }
+ else {
+ size=-1;
+ }
+
+ ctime_r(&direntry[e].atime, atime);
+ ctime_r(&direntry[e].mtime, mtime);
+ ctime_r(&direntry[e].rtime, rtime);
+
+ if(now-direntry[e].mtime < 3600) stime=M_HR;
+ else if(now-direntry[e].mtime < 24*3600) stime=M_DAY;
+ else if(now-direntry[e].mtime < 7*24*3600) stime=M_WK;
+// else if(now-direntry[e].mtime < 14*24*3600) stime=M_2WK;
+ else if(now-direntry[e].mtime < 31*24*3600) stime=M_MO;
+// else if(now-direntry[e].mtime < 62*24*3600) stime=M_2MO;
+// else if(now-direntry[e].mtime < 182*24*3600) stime=M_6MO;
+ else if(now-direntry[e].mtime < 365*24*3600) stime=M_YR;
+ else stime=M_OLD;
+
+ if(strcmp(highlight, name)==0) {
+ icon=NEWIMG;
+ linecolor=tHIGH_COLOR;
+ }
+ else {
+ icon=DIRIMG;
+ linecolor=tNORMAL_COLOR;
+ }
+
+
+ // directory name / date
+ fprintf(cgiOut,
+ "<!-- Directory Entry -->\n"\
+ "<TR BGCOLOR=\"#%s\" onMouseOver=\"this.bgColor='#%s';\" onMouseOut=\"this.bgColor='#%s';\">\n"\
+ "<TD ALIGN=\"LEFT\">\n"
+ "<INPUT TYPE=\"CHECKBOX\" NAME=\"multiselect_filename\" STYLE=\"border: none;\" VALUE=\"%s\">",
+ linecolor, HL_COLOR, linecolor, name);
+
+ fprintf(cgiOut,
+ "<A HREF=\"%s?sortby=%s&amp;directory=%s/%s&amp;token=%s\">%s %s</A></TD> \n"\
+ "<TD ALIGN=\"RIGHT\">%s</TD>\n"\
+ "<TD ALIGN=\"RIGHT\"><SPAN TITLE=\"Created:%s\n Modified:%s\n Accessed:%s\n\">%s&nbsp;%s</FONT></SPAN></TD>\n"\
+ "<TD>&nbsp;</TD>"\
+ "<TD ALIGN=\"LEFT\">",
+ cgiScriptName, sortby, (strcmp(virt_dirname, "/")==0) ? "" : virt_dirname, name, token, icon, name,
+ buprintf(size, TRUE), rtime, mtime, atime, stime, mtime);
+
+ // rename
+ fprintf(cgiOut, "\n"\
+ "<A HREF=\"%s?action=rename_prompt&amp;directory=%s&amp;filename=%s&amp;token=%s\" TITLE=\"Rename '%s'\">\n"\
+ "<IMG SRC=\"%srename.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Rename File\">\n"\
+ "</A>\n",
+ cgiScriptName, virt_dirname, name, token, name, ICONSURL);
+
+ // move
+ fprintf(cgiOut, "\n"\
+ "<A HREF=\"%s?action=move_prompt&amp;directory=%s&amp;filename=%s&amp;token=%s\" TITLE=\"Move '%s'\">\n"\
+ "<IMG SRC=\"%smove.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Move File\">\n"\
+ "</A>\n",
+ cgiScriptName, virt_dirname, name, token, name, ICONSURL);
+
+ // delete
+ fprintf(cgiOut, "\n"\
+ "<A HREF=\"%s?action=delete_prompt&amp;directory=%s&amp;filename=%s&amp;token=%s\" TITLE=\"Delete '%s'\">\n"\
+ "<IMG SRC=\"%sdelete.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Delete Directory\">\n"\
+ "</A>\n"\
+ "</TD>\n"\
+ "</TR>\n\n\n",
+ cgiScriptName, virt_dirname, name, token, name, ICONSURL);
+
+ totalsize+=size;
+ }
+
+
+ // regular files
+ for(e=0; e<nentr; e++) {
+ if(S_ISDIR(direntry[e].type))
+ continue;
+ if(direntry[e].name[0]=='.')
+ continue;
+
+ name=direntry[e].name;
+ size=direntry[e].size;
+
+ ctime_r(&direntry[e].atime, atime);
+ ctime_r(&direntry[e].mtime, mtime);
+ ctime_r(&direntry[e].rtime, rtime);
+
+ if(now-direntry[e].mtime < 3600) stime=M_HR;
+ else if(now-direntry[e].mtime < 24*3600) stime=M_DAY;
+ else if(now-direntry[e].mtime < 7*24*3600) stime=M_WK;
+// else if(now-direntry[e].mtime < 14*24*3600) stime=M_2WK;
+ else if(now-direntry[e].mtime < 31*24*3600) stime=M_MO;
+// else if(now-direntry[e].mtime < 62*24*3600) stime=M_2MO;
+// else if(now-direntry[e].mtime < 182*24*3600) stime=M_6MO;
+ else if(now-direntry[e].mtime < 365*24*3600) stime=M_YR;
+ else stime=M_OLD;
+
+ if(regexec(&reg_zip, name, 0, 0, 0)==0) { icon=ZIPIMG; editable=0; }
+ else if(regexec(&reg_img, name, 0, 0, 0)==0) { icon=IMGIMG; editable=0; }
+ else if(regexec(&reg_off, name, 0, 0, 0)==0) { icon=OFFIMG; editable=0; }
+ else if(regexec(&reg_pdf, name, 0, 0, 0)==0) { icon=PDFIMG; editable=0; }
+ else if(regexec(&reg_txt, name, 0, 0, 0)==0) { icon=TXTIMG; editable=1; }
+ else if(regexec(&reg_exe, name, 0, 0, 0)==0) { icon=EXEIMG; editable=0; }
+ else if(regexec(&reg_med, name, 0, 0, 0)==0) { icon=MEDIMG; editable=0; }
+ else if(regexec(&reg_iso, name, 0, 0, 0)==0) { icon=ISOIMG; editable=0; }
+ else { icon=GENIMG; editable=0; }
+
+ if(edit_any_file) { editable=1; }
+
+ if(strcmp(highlight, name)==0) {
+ icon=NEWIMG;
+ linecolor=tHIGH_COLOR;
+ }
+ else {
+ linecolor=tNORMAL_COLOR;
+ }
+
+ // filename
+ fprintf(cgiOut,
+ "<!-- File Entry -->\n"
+ "<TR BGCOLOR=\"#%s\" onMouseOver=\"this.bgColor='#%s';\" onMouseOut=\"this.bgColor='#%s';\">\n"
+ "<TD ALIGN=\"LEFT\"><INPUT TYPE=\"CHECKBOX\" NAME=\"multiselect_filename\" STYLE=\"border: none;\" VALUE=\"%s\">"
+ "<A HREF=\"%s?action=%s&amp;directory=%s&amp;filename=%s&amp;token=%s\" TITLE=\"Open '%s'\">%s %s</A></TD>\n",
+ linecolor, HL_COLOR, linecolor, name, cgiScriptName, (edit_by_default && editable) ? "edit" : "sendfile",
+ virt_dirname, name, token, name, icon, name);
+
+
+ // size / date
+ fprintf(cgiOut,
+ "\n"
+ "<TD ALIGN=\"RIGHT\" >%s</TD>\n"
+ "<TD ALIGN=\"RIGHT\" ><SPAN TITLE=\"Created:%s\n Modified:%s\n Accessed:%s\n\">%s&nbsp;%s</FONT></SPAN></TD>\n",
+ buprintf(size, TRUE), rtime, mtime, atime, stime, mtime);
+
+
+ // file tools
+ fprintf(cgiOut, "\n<TD>&nbsp;</TD><TD ALIGN=\"LEFT\">\n");
+
+
+ // rename
+ fprintf(cgiOut,
+ "<A HREF=\"%s?action=rename_prompt&amp;directory=%s&amp;filename=%s&amp;token=%s\" TITLE=\"Rename '%s'\">\n"
+ "<IMG SRC=\"%srename.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Rename File\">\n"
+ "</A>\n",
+ cgiScriptName, virt_dirname, name, token, name, ICONSURL);
+
+ // move
+ fprintf(cgiOut,
+ "\n"
+ "<A HREF=\"%s?action=move_prompt&amp;directory=%s&amp;filename=%s&amp;token=%s\" TITLE=\"Move '%s'\">"
+ "<IMG SRC=\"%smove.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Move '%s'\">\n"
+ "</A>\n",
+ cgiScriptName, virt_dirname, name, token, name, ICONSURL, name);
+
+ // delete
+ fprintf(cgiOut,
+ "\n"
+ "<A HREF=\"%s?action=delete_prompt&amp;directory=%s&amp;filename=%s&amp;token=%s\" "
+ "TITLE=\"Remove '%s'\"> \n"
+ "<IMG SRC=\"%sdelete.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Delete File\">\n"
+ "</A>\n",
+ cgiScriptName, virt_dirname, name, token, name, ICONSURL);
+
+
+ // view
+ if(strlen(HOMEURL)>4)
+ fprintf(cgiOut,
+ "\n"
+ "<A HREF=\"%s%s%s/%s\" TITLE=\"Preview '%s' In Browser\">\n"
+ "<IMG SRC=\"%sext.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Preview '%s' In Browser\" >\n"
+ "</A>\n",
+ HOMEURL, (virt_dirname[0]!='/') ? "/" : "", (strcmp(virt_dirname, "/")==0) ? "" : virt_dirname, name, name, ICONSURL, name);
+
+
+ // edit for text files..
+ if(editable) {
+ if(edit_by_default)
+ fprintf(cgiOut,
+ "\n"
+ "<A HREF=\"%s?action=sendfile&amp;directory=%s&amp;filename=%s&amp;token=%s\" TITLE=\"Download '%s'\">\n"
+ "<IMG SRC=\"%sdisk.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Download File\">\n"
+ "</A>\n"
+ "</TD>\n"
+ "</TR>\n\n",
+ cgiScriptName, virt_dirname, name, token, name, ICONSURL);
+ else
+ fprintf(cgiOut,
+ "\n"
+ "<A HREF=\"%s?action=edit&amp;directory=%s&amp;filename=%s&amp;token=%s\" TITLE=\"Edit '%s'\">\n"
+ "<IMG SRC=\"%sedit.gif\" BORDER=0 WIDTH=16 HEIGHT=16 ALT=\"Edit File\">\n"
+ "</A>\n"
+ "</TD>\n"
+ "</TR>\n\n",
+ cgiScriptName, virt_dirname, name, token, name, ICONSURL);
+ }
+ else {
+ fprintf(cgiOut,
+ "\n"
+ "&nbsp;\n"
+ "</TD>\n"
+ "</TR>\n\n"
+ );
+ }
+
+ totalsize+=size;
+
+ }
+
+ tstop();
+
+ //
+ // footer line
+ //
+ fprintf(cgiOut,
+ "<!-- FOOTER -->\n"
+ "<TR>\n"
+ "<TD BGCOLOR=\"#%s\">&nbsp;</TD>\n"
+ "<TD BGCOLOR=\"#%s\" ALIGN=\"right\" STYLE=\"border-top:1px solid grey\">total %s </TD>\n"
+ "<TD BGCOLOR=\"#%s\" ALIGN=\"right\" STYLE=\"color:#D0D0D0;\">%.1f ms</TD>\n"
+ "<TD BGCOLOR=\"#%s\">&nbsp;</TD>\n"
+ "<TD BGCOLOR=\"#%s\">&nbsp;</TD>\n"
+ "</TR>\n"
+ "</TABLE>\n</FORM>\n</BODY>\n<!-- Page generated in %f seconds -->\n</HTML>\n\n",
+ NORMAL_COLOR, NORMAL_COLOR, buprintf(totalsize, TRUE), NORMAL_COLOR, (t2-t1)*1000, NORMAL_COLOR, NORMAL_COLOR, t2-t1
+ );
+
+}
+
diff --git a/fileio.c b/fileio.c
new file mode 100644
index 0000000..a6c24c9
--- /dev/null
+++ b/fileio.c
@@ -0,0 +1,518 @@
+// WFM File I/O Routines
+
+#include "wfm.h"
+
+//
+// Send file to client browser
+// Called by cgiMain action=sendfile
+//
+void sendfile(void) {
+ char buff[1024];
+ FILE *in;
+ int rd, tot, size, pos, blk;
+
+ checkfilename(NULL);
+
+ // TODO: 2gb file limit?
+ in=fopen(phys_filename, "rb");
+ if(!in)
+ error("Unable to open file.<BR>%s", strerror(errno));
+
+ fseek(in, 0, SEEK_END);
+ size=ftell(in);
+ fseek(in, 0, SEEK_SET);
+
+ fprintf(cgiOut,
+ "Content-Type: application/octet-stream\r\n"
+ "Content-Disposition: attachment; filename=\"%s\"; size=%d\r\n"
+ "Content-Length: %d\r\n\r\n",
+ virt_filename, size, size
+ );
+
+ blk=sizeof(buff);
+
+ reread:
+ pos=ftell(in);
+ rd=fread(buff, blk, 1, in);
+
+ if(rd) {
+ tot=tot+rd*blk;
+// printf("rw=%u size=%u total=%u remaining=%u\n", rd*blk, size, tot, size-pos-(rd*blk));
+ fwrite(buff, blk, 1, cgiOut);
+ goto reread;
+ }
+
+ if(pos<size) {
+ blk=size-pos;
+ fseek(in, pos, SEEK_SET);
+ goto reread;
+ }
+
+ fclose(in);
+
+}
+
+//
+// Receive file from client browser via upload form
+// Called by cgiMain action=upload
+//
+void receivefile(void) {
+ cgiFilePtr input;
+ FILE *output;
+ char *buff;
+ int size=0, got=0;
+
+ if(cgiFormFileSize("filename", &size) != cgiFormSuccess)
+ error("No file size specified.");
+
+ if(cgiFormFileOpen("filename", &input) != cgiFormSuccess)
+ error("Unable to access uploaded file.");
+
+ buff=(char *) malloc(size+1);
+ if(buff==NULL)
+ error("Unable to allocate memory.");
+
+ // TODO: check for freeze - return value?
+ if(cgiFormFileRead(input, buff, size, &got) != cgiFormSuccess)
+ error("Reading file.");
+
+ cgiFormFileClose(input);
+
+ if(got != size)
+ error("Wrong file size. Size=%d Received=%d.", size, got);
+
+ checkfilename(NULL);
+
+ output=fopen(phys_filename, "wb");
+ if(!output)
+ error("Unable to open file %s for writing.<BR>%s", virt_filename, strerror(errno));
+
+ if(fwrite(buff, size, 1, output) != 1)
+ error("While writing file.<BR>%s", strerror(errno));
+
+ fclose(output);
+ free(buff);
+
+ redirect("?highlight=%s&directory=%s&token=%s", virt_filename, virt_dirname, token);
+
+}
+
+//
+// Create a new new empty file
+// Called by cgiMain action=mkfile
+//
+void mkfile(void) {
+ FILE *output;
+
+ checkfilename(NULL);
+
+ output=fopen(phys_filename, "a");
+
+ if(!output)
+ error("Unable to create file.<BR>%s", strerror(errno));
+
+ fclose(output);
+
+ redirect("?highlight=%s&directory=%s&token=%s", virt_filename, virt_dirname, token);
+
+}
+
+//
+// Create a new empty folder
+// Called by cgiMain action=newdir
+//
+void newdir(void) {
+
+ checkfilename(NULL);
+
+ if(mkdir(phys_filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH )!=0)
+ error("Unable to create directory.<BR>%s", strerror(errno));
+
+ redirect("?highlight=%s&directory=%s&token=%s", virt_filename, virt_dirname, token);
+
+}
+
+//
+// Save file from textarea editor
+// Called by cgiMain action=edit_save
+//
+void edit_save(void) {
+ int size=0;
+ int tmpfd;
+ char *buff;
+ char tempname[64];
+ //FILE *output;
+ FILE *tempf;
+ char backup[4];
+ char backup_filename[PHYS_DESTINATION_SIZE];
+ regex_t re;
+ regmatch_t pmatch;
+ struct stat tmpstat;
+
+
+ checkfilename(NULL);
+
+ // the size should be updated by onclick from content.value.lenght just before submission
+ // it's used to verify that received data length is consistent with editor contents
+ cgiFormInteger("size", &size, 0);
+
+ if(size>100*1024*1024)
+ error("Input size too large.");
+
+ buff=(char *) malloc(size);
+ if(buff==NULL)
+ error("Unable to allocate memory.");
+
+ memset(buff, 0, size);
+
+ cgiFormString("content", buff, size);
+
+ if(strlen(buff)+1 != size) // +1 because size was also given +1 via front end
+ error("Received wrong size. <BR>ContentLen=%d DataLen=%d. <BR> The file was not changed.", size, strlen(buff));
+
+ // rename to .bak if requested
+ cgiFormStringNoNewlines("backup", backup, sizeof(backup));
+
+ if(strcmp(backup, "yes")==0) {
+ regcomp(&re, "\\.(.+)$", REG_EXTENDED|REG_ICASE);
+ if(regexec(&re, phys_filename, 1, &pmatch, 0)==0) {
+ if(pmatch.rm_so+4 < PHYS_DESTINATION_SIZE) {
+ strcpy(backup_filename, phys_filename);
+ strcpy(backup_filename+pmatch.rm_so+1, "bak\0");
+ if(rename(phys_filename, backup_filename)!=0)
+ error("Unable to create .bak file.<BR>%s was not modified.<BR>%s", virt_filename, strerror(errno));
+ }
+ }
+ }
+
+ // write to temporary file
+ snprintf(tempname, sizeof(tempname), "%s/.wfmXXXXXX", phys_dirname);
+
+ tmpfd=mkstemp(tempname);
+
+ if(!tmpfd)
+ error("Unable to create temporary file %s.<BR>%s", basename(tempname), strerror(errno));
+
+ tempf=fdopen(tmpfd, "w");
+
+ if(!tempf)
+ error("Unable to open temporary file %s.<BR>%s", basename(tempname), strerror(errno));
+
+ if(fwrite(buff, strlen(buff), 1, tempf) != 1)
+ error("Unable to write to temporary file %s.<BR>%s", basename(tempname), strerror(errno));
+
+ fclose(tempf);
+
+ if(stat(tempname, &tmpstat)!=0)
+ error("Unable to check temporary file size.<BR>%s<BR>%s", basename(tempname), strerror(errno));
+
+ if(tmpstat.st_size != strlen(buff))
+ error("Temprary file has a wrong length. Giving up.<BR>%s size=%d, buff len=%d", virt_filename, tmpstat.st_size);
+
+ if(chmod(tempname, 00644)!=0)
+ error("Unable to set file permissions.<BR>%s", strerror(errno));
+
+ // finally rename to desination file
+ if(rename(tempname, phys_filename)!=0)
+ error("Unable to rename temp file.<BR>%s - %s<BR>%s<BR>", basename(tempname), virt_filename, strerror(errno));
+
+
+ free(buff);
+
+ redirect("?highlight=%s&directory=%s&token=%s", virt_filename, virt_dirname, token);
+
+}
+
+//
+// Recursively Delete Folders - Internal Routine
+// Called by fileio_delete when directory is encountered
+//
+void fileio_re_rmdir(char *dirname) {
+ DIR *dir;
+ struct dirent *direntry;
+ struct stat fileinfo;
+ char tempfullpath[PHYS_FILENAME_SIZE];
+
+ dir=opendir(dirname);
+ if(!dir)
+ error("Unable to remove directory..");
+
+
+ direntry=readdir(dir);
+ while(direntry!=0) {
+ if(strncmp(direntry->d_name, ".", 1) && strncmp(direntry->d_name, "..", 2)) {
+ snprintf(tempfullpath, PHYS_FILENAME_SIZE, "%s/%s", dirname, direntry->d_name);
+
+ if(lstat(tempfullpath, &fileinfo)!=0)
+ error("Unable to get file status.<BR>%s", strerror(errno));
+
+ if(S_ISDIR(fileinfo.st_mode)) {
+ fileio_re_rmdir(tempfullpath);
+ if(rmdir(tempfullpath)!=0)
+ error("Unable to remove directory...<BR>%s", strerror(errno));
+ } else {
+ if(unlink(tempfullpath)!=0)
+ error("Unable to remove directory....<BR>%s", strerror(errno));
+ }
+
+ }
+ direntry=readdir(dir);
+ }
+
+ closedir(dir);
+
+}
+
+//
+// Delete Files Internal Routine
+// Called by delete()
+//
+void fileio_delete(void) {
+ struct stat fileinfo;
+
+ if(lstat(phys_filename, &fileinfo)==0) {
+ if(S_ISDIR(fileinfo.st_mode)) {
+ fileio_re_rmdir(phys_filename);
+ if(rmdir(phys_filename)!=0)
+ error("Unable to remove directory.<BR>%s", strerror(errno));
+ }
+ else {
+ if(unlink(phys_filename)!=0)
+ error("Unable to remove file.<BR>%s", strerror(errno));
+ }
+ }
+
+}
+
+//
+// Delete File or Directory Handler - Allows Multiselect
+// Called by cgiMain action=delete
+//
+void delete(void) {
+ int i;
+ char **responses;
+
+ // Single
+ if(cgiFormStringMultiple("multiselect_filename", &responses) == cgiFormNotFound) {
+ checkfilename(NULL);
+ fileio_delete();
+ }
+ // Multi
+ else {
+ for(i=0; responses[i]; i++) {
+ checkfilename(responses[i]);
+ fileio_delete();
+ }
+ }
+
+ redirect("?directory=%s&token=%s", virt_dirname, token);
+
+}
+
+//
+// Move File/Directory Internal Routine
+// Called by move()
+//
+void fileio_move(void) {
+ char final_destination[PHYS_DESTINATION_SIZE];
+ struct stat fileinfo;
+
+ // If moving file to a different directory we need to append the original file name
+ if( stat(phys_destination, &fileinfo)==0 && S_ISDIR(fileinfo.st_mode) )
+ snprintf(final_destination, sizeof(final_destination), "%s/%s", phys_destination, virt_filename);
+ else
+ strncpy(final_destination, phys_destination, sizeof(final_destination));
+
+ if(rename(phys_filename, final_destination)!=0)
+ error("Unable to move file. <BR>[%d: %s]<BR>[SRC=%s] [DST=%s]", errno, strerror(errno), phys_filename, final_destination);
+}
+
+//
+// Move File/Directory - Handler
+// Called by cgiMain action=move
+//
+void move(void) {
+ int i;
+ char **responses;
+
+
+ checkdestination();
+
+ // Single
+ if(cgiFormStringMultiple("multiselect_filename", &responses) == cgiFormNotFound) {
+ checkfilename(NULL);
+ fileio_move();
+ }
+ // Multi
+ else {
+ for(i=0; responses[i]; i++) {
+ checkfilename(responses[i]);
+ fileio_move();
+ }
+ }
+
+ redirect("?highlight=%s&directory=%s&token=%s", virt_destination, virt_dirname, token);
+}
+
+
+//
+// Recursive Dir Size
+//
+// WARNING: will not count directories starting with .
+off_t du(char *pdir) {
+ DIR *dir;
+ struct dirent *direntry;
+ struct stat fileinfo;
+ char child[PHYS_DIRNAME_SIZE];
+ off_t tot=0;
+
+ if(lstat(pdir, &fileinfo)==0)
+ if(S_ISLNK(fileinfo.st_mode))
+ return -1;
+
+ dir=opendir(pdir);
+ if(dir) {
+ direntry=readdir(dir);
+ while(direntry) {
+ snprintf(child, PHYS_DIRNAME_SIZE, "%s/%s", pdir, direntry->d_name);
+ if(lstat(child, &fileinfo)==0) {
+ if(S_ISDIR(fileinfo.st_mode) && (direntry->d_name[0]!='.')) //TODO: count other ".files" except . & ..
+ tot+=du(child);
+ else
+ tot+=fileinfo.st_size;
+ }
+ direntry=readdir(dir);
+ }
+ closedir(dir);
+ }
+
+ return tot;
+}
+
+//
+// Recursive folder list
+// Called by for move_ui()
+//
+void re_dir_ui(char *vdir, int level) {
+ struct dirent **direntry;
+ struct stat fileinfo;
+ char child[VIRT_DIRNAME_SIZE];
+ char phy_child[PHYS_DIRNAME_SIZE];
+ char re_phys_dirname[PHYS_DIRNAME_SIZE];
+ int n;
+ int nentr, e;
+
+ snprintf(re_phys_dirname, PHYS_DIRNAME_SIZE, "%s/%s", HOMEDIR, vdir);
+
+ if(strlen(re_phys_dirname)<2 || strlen(re_phys_dirname)>(PHYS_DIRNAME_SIZE-2))
+ error("Invalid directory name.");
+
+ if(regexec(&dotdot, re_phys_dirname, 0, 0, 0)==0) error("Invalid directory name.");
+ if(strlen(re_phys_dirname) < strlen(HOMEDIR)) error("Invalid directory name.");
+
+ nentr=scandir(re_phys_dirname, &direntry, 0, alphasort);
+
+ for(e=0; e<nentr; e++) {
+ snprintf(phy_child, PHYS_DIRNAME_SIZE, "%s/%s/%s", HOMEDIR, vdir, direntry[e]->d_name);
+ if((direntry[e]->d_name[0]!='.') && (lstat(phy_child, &fileinfo)==0) && S_ISDIR(fileinfo.st_mode)) {
+
+
+ snprintf(child, VIRT_DIRNAME_SIZE, "%s/%s", vdir, direntry[e]->d_name);
+
+ fprintf(cgiOut, "<OPTION VALUE=\"%s\">", child);
+
+ for (n=0; n<(level-1); n++)
+ fprintf(cgiOut, "&nbsp;&nbsp;&nbsp;");
+
+ fprintf(cgiOut, "&lfloor;&nbsp;%s</OPTION>\n", direntry[e]->d_name);
+
+ // recurse
+ re_dir_ui(child,level+1);
+ }
+ free(direntry[e]);
+ }
+
+}
+
+//
+// Scandir replacement function
+//
+int namesort(const void *d1, const void *d2) {
+ return(strcmp( ((ASDIR*)d1)->name, ((ASDIR*)d2)->name));
+}
+
+int rnamesort(const void *d1, const void *d2) {
+ return(strcmp( ((ASDIR*)d2)->name, ((ASDIR*)d1)->name));
+}
+
+int sizesort(const void *d1, const void *d2) {
+ if(((ASDIR*)d1)->size < ((ASDIR*)d2)->size) return -1;
+ else if(((ASDIR*)d1)->size > ((ASDIR*)d2)->size) return 1;
+ else return 0;
+}
+
+int rsizesort(const void *d1, const void *d2) {
+ if(((ASDIR*)d1)->size > ((ASDIR*)d2)->size) return -1;
+ else if(((ASDIR*)d1)->size < ((ASDIR*)d2)->size) return 1;
+ else return 0;
+}
+
+int timesort(const void *d1, const void *d2) {
+ if(((ASDIR*)d1)->mtime < ((ASDIR*)d2)->mtime) return -1;
+ else if(((ASDIR*)d1)->mtime > ((ASDIR*)d2)->mtime) return 1;
+ else return 0;
+}
+
+int rtimesort(const void *d1, const void *d2) {
+ if(((ASDIR*)d1)->mtime > ((ASDIR*)d2)->mtime) return -1;
+ else if(((ASDIR*)d1)->mtime < ((ASDIR*)d2)->mtime) return 1;
+ else return 0;
+}
+
+int asscandir(const char *dir, ASDIR **namelist, int (*compar)(const void *, const void *)) {
+ DIR *dirh;
+ ASDIR *names;
+ struct dirent *entry;
+ struct stat fileinfo;
+ char filename[PATH_MAX];
+ int entries=0;
+
+ dirh=opendir(dir);
+ if(dirh==NULL)
+ return -1;
+
+ names=(ASDIR*)malloc(sizeof(ASDIR));
+ if(names==NULL)
+ return -1;
+
+ entry=readdir(dirh);
+ while(entry!=NULL) {
+ snprintf(filename, sizeof(filename), "%s/%s", dir, entry->d_name);
+ if(stat(filename, &fileinfo)!=0)
+ return -1;
+
+ memset(&names[entries], 0, sizeof(ASDIR));
+ strcpy(names[entries].name, entry->d_name);
+ names[entries].type=fileinfo.st_mode;
+ if(S_ISDIR(fileinfo.st_mode) && recursive_du)
+ names[entries].size=du(filename);
+ else
+ names[entries].size=fileinfo.st_size;
+ names[entries].atime=fileinfo.st_atime;
+ names[entries].mtime=fileinfo.st_mtime;
+ names[entries].rtime=fileinfo.st_ctime;
+
+ names=(ASDIR*)realloc((ASDIR*)names, sizeof(ASDIR)*(entries+2));
+ if(names==NULL)
+ return -1;
+ entries++;
+ entry=readdir(dirh);
+ }
+ closedir(dirh);
+
+ if(entries)
+ qsort(&names[0], entries, sizeof(ASDIR), compar);
+
+ *namelist=names;
+ return entries;
+} \ No newline at end of file
diff --git a/icons/adn.gif b/icons/adn.gif
new file mode 100755
index 0000000..46dc0b7
--- /dev/null
+++ b/icons/adn.gif
Binary files differ
diff --git a/icons/arr.gif b/icons/arr.gif
new file mode 100755
index 0000000..1c964c7
--- /dev/null
+++ b/icons/arr.gif
Binary files differ
diff --git a/icons/aup.gif b/icons/aup.gif
new file mode 100755
index 0000000..35c106d
--- /dev/null
+++ b/icons/aup.gif
Binary files differ
diff --git a/icons/clr.gif b/icons/clr.gif
new file mode 100755
index 0000000..a5ef053
--- /dev/null
+++ b/icons/clr.gif
Binary files differ
diff --git a/icons/delete.gif b/icons/delete.gif
new file mode 100755
index 0000000..02327ed
--- /dev/null
+++ b/icons/delete.gif
Binary files differ
diff --git a/icons/dir.gif b/icons/dir.gif
new file mode 100755
index 0000000..9b11305
--- /dev/null
+++ b/icons/dir.gif
Binary files differ
diff --git a/icons/dir_up.gif b/icons/dir_up.gif
new file mode 100755
index 0000000..3f8af1f
--- /dev/null
+++ b/icons/dir_up.gif
Binary files differ
diff --git a/icons/disk.gif b/icons/disk.gif
new file mode 100644
index 0000000..c4fcab2
--- /dev/null
+++ b/icons/disk.gif
Binary files differ
diff --git a/icons/edit.gif b/icons/edit.gif
new file mode 100755
index 0000000..b610312
--- /dev/null
+++ b/icons/edit.gif
Binary files differ
diff --git a/icons/exe.gif b/icons/exe.gif
new file mode 100755
index 0000000..bf41d1a
--- /dev/null
+++ b/icons/exe.gif
Binary files differ
diff --git a/icons/ext.gif b/icons/ext.gif
new file mode 100755
index 0000000..3da6e7e
--- /dev/null
+++ b/icons/ext.gif
Binary files differ
diff --git a/icons/gen.gif b/icons/gen.gif
new file mode 100755
index 0000000..529d299
--- /dev/null
+++ b/icons/gen.gif
Binary files differ
diff --git a/icons/home.gif b/icons/home.gif
new file mode 100755
index 0000000..278dba7
--- /dev/null
+++ b/icons/home.gif
Binary files differ
diff --git a/icons/img.gif b/icons/img.gif
new file mode 100755
index 0000000..6940784
--- /dev/null
+++ b/icons/img.gif
Binary files differ
diff --git a/icons/iso.gif b/icons/iso.gif
new file mode 100755
index 0000000..4a43b2e
--- /dev/null
+++ b/icons/iso.gif
Binary files differ
diff --git a/icons/lck.gif b/icons/lck.gif
new file mode 100755
index 0000000..0e54b95
--- /dev/null
+++ b/icons/lck.gif
Binary files differ
diff --git a/icons/med.gif b/icons/med.gif
new file mode 100755
index 0000000..e156fae
--- /dev/null
+++ b/icons/med.gif
Binary files differ
diff --git a/icons/mkdir.gif b/icons/mkdir.gif
new file mode 100755
index 0000000..694b0af
--- /dev/null
+++ b/icons/mkdir.gif
Binary files differ
diff --git a/icons/mkfile.gif b/icons/mkfile.gif
new file mode 100755
index 0000000..0565051
--- /dev/null
+++ b/icons/mkfile.gif
Binary files differ
diff --git a/icons/move.gif b/icons/move.gif
new file mode 100755
index 0000000..368f4db
--- /dev/null
+++ b/icons/move.gif
Binary files differ
diff --git a/icons/net.gif b/icons/net.gif
new file mode 100755
index 0000000..321d83c
--- /dev/null
+++ b/icons/net.gif
Binary files differ
diff --git a/icons/off.gif b/icons/off.gif
new file mode 100755
index 0000000..9010f2d
--- /dev/null
+++ b/icons/off.gif
Binary files differ
diff --git a/icons/pdf.gif b/icons/pdf.gif
new file mode 100755
index 0000000..add1dc4
--- /dev/null
+++ b/icons/pdf.gif
Binary files differ
diff --git a/icons/readonly.gif b/icons/readonly.gif
new file mode 100755
index 0000000..2ac4e9e
--- /dev/null
+++ b/icons/readonly.gif
Binary files differ
diff --git a/icons/readwrite.gif b/icons/readwrite.gif
new file mode 100755
index 0000000..0da0a44
--- /dev/null
+++ b/icons/readwrite.gif
Binary files differ
diff --git a/icons/reload.gif b/icons/reload.gif
new file mode 100755
index 0000000..1828b5c
--- /dev/null
+++ b/icons/reload.gif
Binary files differ
diff --git a/icons/rename.gif b/icons/rename.gif
new file mode 100755
index 0000000..440a726
--- /dev/null
+++ b/icons/rename.gif
Binary files differ
diff --git a/icons/time.gif b/icons/time.gif
new file mode 100755
index 0000000..9dcdd12
--- /dev/null
+++ b/icons/time.gif
Binary files differ
diff --git a/icons/txt.gif b/icons/txt.gif
new file mode 100755
index 0000000..3a624ea
--- /dev/null
+++ b/icons/txt.gif
Binary files differ
diff --git a/icons/user.gif b/icons/user.gif
new file mode 100755
index 0000000..6896994
--- /dev/null
+++ b/icons/user.gif
Binary files differ
diff --git a/icons/ver.gif b/icons/ver.gif
new file mode 100755
index 0000000..d3729de
--- /dev/null
+++ b/icons/ver.gif
Binary files differ
diff --git a/icons/wfmicon.gif b/icons/wfmicon.gif
new file mode 100755
index 0000000..6314cd4
--- /dev/null
+++ b/icons/wfmicon.gif
Binary files differ
diff --git a/icons/zip.gif b/icons/zip.gif
new file mode 100755
index 0000000..c0252dd
--- /dev/null
+++ b/icons/zip.gif
Binary files differ
diff --git a/md5.c b/md5.c
new file mode 100644
index 0000000..c35d96c
--- /dev/null
+++ b/md5.c
@@ -0,0 +1,381 @@
+/*
+ Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ L. Peter Deutsch
+ ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+ Independent implementation of MD5 (RFC 1321).
+
+ This code implements the MD5 Algorithm defined in RFC 1321, whose
+ text is available at
+ http://www.ietf.org/rfc/rfc1321.txt
+ The code is derived from the text of the RFC, including the test suite
+ (section A.5) but excluding the rest of Appendix A. It does not include
+ any code or documentation that is identified in the RFC as being
+ copyrighted.
+
+ The original and principal author of md5.c is L. Peter Deutsch
+ <ghost@aladdin.com>. Other authors are noted in the change history
+ that follows (in reverse chronological order):
+
+ 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+ either statically or dynamically; added missing #include <string.h>
+ in library.
+ 2002-03-11 lpd Corrected argument list for main(), and added int return
+ type, in test program and T value program.
+ 2002-02-21 lpd Added missing #include <stdio.h> in test program.
+ 2000-07-03 lpd Patched to eliminate warnings about "constant is
+ unsigned in ANSI C, signed in traditional"; made test program
+ self-checking.
+ 1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+ 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+ 1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+# define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3 0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6 0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9 0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13 0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16 0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19 0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22 0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25 0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28 0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31 0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35 0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38 0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41 0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44 0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47 0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50 0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53 0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57 0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60 0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63 0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+ md5_word_t
+ a = pms->abcd[0], b = pms->abcd[1],
+ c = pms->abcd[2], d = pms->abcd[3];
+ md5_word_t t;
+#if BYTE_ORDER > 0
+ /* Define storage only for big-endian CPUs. */
+ md5_word_t X[16];
+#else
+ /* Define storage for little-endian or both types of CPUs. */
+ md5_word_t xbuf[16];
+ const md5_word_t *X;
+#endif
+
+ {
+#if BYTE_ORDER == 0
+ /*
+ * Determine dynamically whether this is a big-endian or
+ * little-endian machine, since we can use a more efficient
+ * algorithm on the latter.
+ */
+ static const int w = 1;
+
+ if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0 /* little-endian */
+ {
+ /*
+ * On little-endian machines, we can process properly aligned
+ * data without copying it.
+ */
+ if (!((data - (const md5_byte_t *)0) & 3)) {
+ /* data are properly aligned */
+ X = (const md5_word_t *)data;
+ } else {
+ /* not aligned */
+ memcpy(xbuf, data, 64);
+ X = xbuf;
+ }
+ }
+#endif
+#if BYTE_ORDER == 0
+ else /* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0 /* big-endian */
+ {
+ /*
+ * On big-endian machines, we must arrange the bytes in the
+ * right order.
+ */
+ const md5_byte_t *xp = data;
+ int i;
+
+# if BYTE_ORDER == 0
+ X = xbuf; /* (dynamic only) */
+# else
+# define xbuf X /* (static only) */
+# endif
+ for (i = 0; i < 16; ++i, xp += 4)
+ xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+ }
+#endif
+ }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+ /* Round 1. */
+ /* Let [abcd k s i] denote the operation
+ a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + F(b,c,d) + X[k] + Ti;\
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 0, 7, T1);
+ SET(d, a, b, c, 1, 12, T2);
+ SET(c, d, a, b, 2, 17, T3);
+ SET(b, c, d, a, 3, 22, T4);
+ SET(a, b, c, d, 4, 7, T5);
+ SET(d, a, b, c, 5, 12, T6);
+ SET(c, d, a, b, 6, 17, T7);
+ SET(b, c, d, a, 7, 22, T8);
+ SET(a, b, c, d, 8, 7, T9);
+ SET(d, a, b, c, 9, 12, T10);
+ SET(c, d, a, b, 10, 17, T11);
+ SET(b, c, d, a, 11, 22, T12);
+ SET(a, b, c, d, 12, 7, T13);
+ SET(d, a, b, c, 13, 12, T14);
+ SET(c, d, a, b, 14, 17, T15);
+ SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+ /* Round 2. */
+ /* Let [abcd k s i] denote the operation
+ a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + G(b,c,d) + X[k] + Ti;\
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 1, 5, T17);
+ SET(d, a, b, c, 6, 9, T18);
+ SET(c, d, a, b, 11, 14, T19);
+ SET(b, c, d, a, 0, 20, T20);
+ SET(a, b, c, d, 5, 5, T21);
+ SET(d, a, b, c, 10, 9, T22);
+ SET(c, d, a, b, 15, 14, T23);
+ SET(b, c, d, a, 4, 20, T24);
+ SET(a, b, c, d, 9, 5, T25);
+ SET(d, a, b, c, 14, 9, T26);
+ SET(c, d, a, b, 3, 14, T27);
+ SET(b, c, d, a, 8, 20, T28);
+ SET(a, b, c, d, 13, 5, T29);
+ SET(d, a, b, c, 2, 9, T30);
+ SET(c, d, a, b, 7, 14, T31);
+ SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+ /* Round 3. */
+ /* Let [abcd k s t] denote the operation
+ a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + H(b,c,d) + X[k] + Ti;\
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 5, 4, T33);
+ SET(d, a, b, c, 8, 11, T34);
+ SET(c, d, a, b, 11, 16, T35);
+ SET(b, c, d, a, 14, 23, T36);
+ SET(a, b, c, d, 1, 4, T37);
+ SET(d, a, b, c, 4, 11, T38);
+ SET(c, d, a, b, 7, 16, T39);
+ SET(b, c, d, a, 10, 23, T40);
+ SET(a, b, c, d, 13, 4, T41);
+ SET(d, a, b, c, 0, 11, T42);
+ SET(c, d, a, b, 3, 16, T43);
+ SET(b, c, d, a, 6, 23, T44);
+ SET(a, b, c, d, 9, 4, T45);
+ SET(d, a, b, c, 12, 11, T46);
+ SET(c, d, a, b, 15, 16, T47);
+ SET(b, c, d, a, 2, 23, T48);
+#undef SET
+
+ /* Round 4. */
+ /* Let [abcd k s t] denote the operation
+ a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + I(b,c,d) + X[k] + Ti;\
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 0, 6, T49);
+ SET(d, a, b, c, 7, 10, T50);
+ SET(c, d, a, b, 14, 15, T51);
+ SET(b, c, d, a, 5, 21, T52);
+ SET(a, b, c, d, 12, 6, T53);
+ SET(d, a, b, c, 3, 10, T54);
+ SET(c, d, a, b, 10, 15, T55);
+ SET(b, c, d, a, 1, 21, T56);
+ SET(a, b, c, d, 8, 6, T57);
+ SET(d, a, b, c, 15, 10, T58);
+ SET(c, d, a, b, 6, 15, T59);
+ SET(b, c, d, a, 13, 21, T60);
+ SET(a, b, c, d, 4, 6, T61);
+ SET(d, a, b, c, 11, 10, T62);
+ SET(c, d, a, b, 2, 15, T63);
+ SET(b, c, d, a, 9, 21, T64);
+#undef SET
+
+ /* Then perform the following additions. (That is increment each
+ of the four registers by the value it had before this block
+ was started.) */
+ pms->abcd[0] += a;
+ pms->abcd[1] += b;
+ pms->abcd[2] += c;
+ pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+ pms->count[0] = pms->count[1] = 0;
+ pms->abcd[0] = 0x67452301;
+ pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+ pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+ pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+ const md5_byte_t *p = data;
+ int left = nbytes;
+ int offset = (pms->count[0] >> 3) & 63;
+ md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+ if (nbytes <= 0)
+ return;
+
+ /* Update the message length. */
+ pms->count[1] += nbytes >> 29;
+ pms->count[0] += nbits;
+ if (pms->count[0] < nbits)
+ pms->count[1]++;
+
+ /* Process an initial partial block. */
+ if (offset) {
+ int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+ memcpy(pms->buf + offset, p, copy);
+ if (offset + copy < 64)
+ return;
+ p += copy;
+ left -= copy;
+ md5_process(pms, pms->buf);
+ }
+
+ /* Process full blocks. */
+ for (; left >= 64; p += 64, left -= 64)
+ md5_process(pms, p);
+
+ /* Process a final partial block. */
+ if (left)
+ memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+ static const md5_byte_t pad[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ md5_byte_t data[8];
+ int i;
+
+ /* Save the length before padding. */
+ for (i = 0; i < 8; ++i)
+ data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+ /* Pad to 56 bytes mod 64. */
+ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+ /* Append the length. */
+ md5_append(pms, data, 8);
+ for (i = 0; i < 16; ++i)
+ digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
diff --git a/md5.h b/md5.h
new file mode 100644
index 0000000..698c995
--- /dev/null
+++ b/md5.h
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ L. Peter Deutsch
+ ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+ Independent implementation of MD5 (RFC 1321).
+
+ This code implements the MD5 Algorithm defined in RFC 1321, whose
+ text is available at
+ http://www.ietf.org/rfc/rfc1321.txt
+ The code is derived from the text of the RFC, including the test suite
+ (section A.5) but excluding the rest of Appendix A. It does not include
+ any code or documentation that is identified in the RFC as being
+ copyrighted.
+
+ The original and principal author of md5.h is L. Peter Deutsch
+ <ghost@aladdin.com>. Other authors are noted in the change history
+ that follows (in reverse chronological order):
+
+ 2002-04-13 lpd Removed support for non-ANSI compilers; removed
+ references to Ghostscript; clarified derivation from RFC 1321;
+ now handles byte order either statically or dynamically.
+ 1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+ 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+ added conditionalization for C++ compilation from Martin
+ Purschke <purschke@bnl.gov>.
+ 1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+# define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+ md5_word_t count[2]; /* message length in bits, lsw first */
+ md5_word_t abcd[4]; /* digest buffer */
+ md5_byte_t buf[64]; /* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+} /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
diff --git a/mkicons.sh b/mkicons.sh
new file mode 100755
index 0000000..1a594b8
--- /dev/null
+++ b/mkicons.sh
@@ -0,0 +1,11 @@
+
+cat /dev/null > wfmiconres.h
+echo "if (0) ;" > wfmicondis.h
+
+for file in icons/*.gif
+do
+ ./bin2c -c $file wfmiconres.h
+ filename=$(basename $file)
+ filedef=$(echo $filename | tr "." "_")
+ echo "else if(strcmp(icon_name, \"${filename}\")==0) fwrite(${filedef}, sizeof(${filedef}), 1, cgiOut);" >> wfmicondis.h
+done
diff --git a/wfm.c b/wfm.c
new file mode 100644
index 0000000..622189a
--- /dev/null
+++ b/wfm.c
@@ -0,0 +1,462 @@
+#include "wfm.h"
+
+//
+// Dispense Common HTML Header
+// Used by all (?) functions that display HTML pages
+//
+void html_title(char *msg) {
+ fprintf(cgiOut,
+ HTML_HEADER
+ "<LINK REL=\"icon\" TYPE=\"image/gif\" HREF=\"%swfmicon.gif\">\n"
+ "<TITLE>%s : %s</TITLE>\n",
+ ICONSURL, TAGLINE, msg); // (strlen(virt_dirname)>0) ? ' ' : '/', TAGLINE, virt_dirname
+}
+
+
+//
+// Dispense embedded icons
+// Called on early action=icon
+//
+int icon(void) {
+ char icon_name[32];
+
+ cgiFormStringNoNewlines("name", icon_name, sizeof(icon_name));
+
+ fprintf(cgiOut, "Cache-Control: max-age=29030400, public\r\n");
+ cgiHeaderContentType("image/gif");
+
+#include "wfmicondis.h"
+
+ exit(0);
+}
+
+
+//
+// Display upload status in SHM via key_id
+// Called by early action=upstat
+//
+void upload_status(void) {
+ int shm_key=0;
+ int shm_id=0;
+ char *shm_addr='\0';
+
+ if(cgiFormInteger("upload_id", &shm_key, 0) == cgiFormSuccess && shm_key) {
+ shm_id = shmget(shm_key, SHM_SIZE, 0666);
+ if(shm_id > 0)
+ shm_addr = shmat(shm_id, NULL, 0);
+ }
+
+ fprintf(cgiOut, "Cache-Control: no-cache\r\n");
+ cgiHeaderContentType("text/plain");
+
+ if(shm_id && shm_addr && *shm_addr)
+ fprintf(cgiOut, "%s\r\n", shm_addr);
+ else
+ fprintf(cgiOut, "-------\r\n");
+
+ if (shm_id && shm_addr)
+ shmdt(shm_addr);
+
+ exit(0);
+}
+
+
+//
+// Generate auth token
+// Used by access_check() to compare tokens and login() to generate token from a web form
+//
+char *mktoken(char *str) {
+ md5_state_t state;
+ md5_byte_t digest[16];
+ char *outstr;
+ int i;
+
+ outstr=(char*) malloc((sizeof(digest)*2)+2);
+ memset(outstr, 0, (sizeof(digest)*2)+2);
+
+ md5_init(&state);
+ md5_append(&state, (const md5_byte_t *)str, strlen(str));
+ md5_finish(&state, digest);
+
+ for (i = 0; i < sizeof(digest); i++)
+ sprintf(outstr + i * 2, "%02x", digest[i]);
+
+ return outstr;
+}
+
+//
+// WFM Login Procedure
+// Called from WFM main procedure if no sufficient access permission available
+//
+void login(void) {
+ char username[64];
+ char password[64];
+ char token_inp[256];
+
+ cgiFormStringNoNewlines("username", username, sizeof(username)); // only used if JavaScript not
+ cgiFormStringNoNewlines("password", password, sizeof(password)); // available in the browser
+
+ if(strlen(username)) {
+ snprintf(token_inp, sizeof(token_inp), "%s:%s:%s", cgiRemoteAddr, username, password);
+ redirect("?directory=%s&login=server&token=%s", virt_dirname, mktoken(token_inp)); // generate MD5 as if it was the client
+ }
+ else
+ login_ui(); // display actual login page, which normally generates token in JavaScript
+
+}
+
+//
+// Access_check
+// Called by cfg read routine during initialization
+//
+void access_check(char *access_string) {
+ char ipaddr[32];
+ char user[32];
+ char pass[32];
+ char type[4];
+ char token_inp[64];
+
+ memset(ipaddr, 0, sizeof(ipaddr));
+ memset(user, 0, sizeof(user));
+ memset(pass, 0, sizeof(pass));
+ memset(type, 0, sizeof(type));
+
+ if(sscanf(access_string, "access-ip=%2s:%30s", type, ipaddr)==2) {
+
+ if(ipaddr[0]=='*') {
+ if(strcmp(type, "ro")==0)
+ access_level=PERM_RO;
+ else if(strcmp(type, "rw")==0)
+ access_level=PERM_RW;
+ }
+ else if(strcmp(cgiRemoteAddr, ipaddr)==0) {
+ if(strcmp(type, "ro")==0)
+ access_level=PERM_RO;
+ else if(strcmp(type, "rw")==0)
+ access_level=PERM_RW;
+ }
+
+ }
+ else if(sscanf(access_string, "access-user=%2[^':']:%30[^':']:%30s", type, user, pass)==3) {
+ users_defined=1;
+
+ snprintf(token_inp, sizeof(token_inp), "%s:%s:%s", cgiRemoteAddr, user, pass);
+ // perform user auth by comparing user supplied token with system generated token
+ if(strcmp(mktoken(token_inp), token)==0) {
+ if(strcmp(type, "ro")==0) {
+ access_level=PERM_RO;
+ access_as_user=1;
+ strncpy(loggedinuser, user, sizeof(loggedinuser));
+ }
+ else if(strcmp(type, "rw")==0) {
+ access_level=PERM_RW;
+ access_as_user=1;
+ strncpy(loggedinuser, user, sizeof(loggedinuser));
+ }
+ }
+ }
+}
+
+
+//
+// Check filename
+// Should be used by every function that uses filename
+//
+void checkfilename(char *inp_filename) {
+ char temp_dirname[PHYS_FILENAME_SIZE];
+ char *bname;
+
+ if(inp_filename && strlen(inp_filename)) {
+ strncpy(virt_filename, inp_filename, VIRT_FILENAME_SIZE);
+ }
+ else if(cgiFormFileName("filename", virt_filename, VIRT_FILENAME_SIZE) == cgiFormSuccess) {
+
+ }
+ else if(cgiFormStringNoNewlines("filename", virt_filename, VIRT_FILENAME_SIZE) == cgiFormSuccess) {
+
+ }
+ else
+ error("No filename specified.");
+
+ // We only want basename from the client!
+ bname=strrchr(virt_filename, '/');
+ if(!bname)
+ bname=strrchr(virt_filename, '\\');
+
+ if(!bname)
+ bname=virt_filename;
+ else
+ (void) *bname++;
+
+ strip(bname, VIRT_FILENAME_SIZE, VALIDCHRS);
+ strncpy(virt_filename, bname, VIRT_FILENAME_SIZE);
+ snprintf(phys_filename, PHYS_FILENAME_SIZE, "%s/%s", phys_dirname, virt_filename);
+
+ if(!strlen(phys_filename) || strlen(phys_filename)>(PHYS_FILENAME_SIZE-2)) error("Invalid pfilename lenght");
+ if(!strlen(virt_filename) || strlen(virt_filename)>(VIRT_FILENAME_SIZE-2)) error("Invalid vfilename lenght");
+ if(regexec(&dotdot, phys_filename, 0, 0, 0)==0) error("Double dots in pfilename");
+ if(regexec(&dotdot, virt_filename, 0, 0, 0)==0) error("Double dots in vfilename");
+
+ strncpy(temp_dirname, phys_filename, PHYS_FILENAME_SIZE);
+ if(strlen(dirname(temp_dirname)) < strlen(HOMEDIR)) error("Invalid directory name.");
+}
+
+//
+// Check destination
+// Only used by move()
+//
+void checkdestination(void) {
+ int absolute_destination;
+
+ cgiFormStringNoNewlines("destination", virt_destination, VIRT_DESTINATION_SIZE);
+ strip(virt_destination, VIRT_DESTINATION_SIZE, VALIDCHRS_DST);
+ cgiFormInteger("absdst", &absolute_destination, 0); // move operation relies on absolute paths
+ if(absolute_destination)
+ snprintf(phys_destination, PHYS_DESTINATION_SIZE, "%s/%s", HOMEDIR, virt_destination);
+ else
+ snprintf(phys_destination, PHYS_DESTINATION_SIZE, "%s/%s", phys_dirname, virt_destination);
+
+ if(strlen(phys_destination)<1 || strlen(phys_destination)>(PHYS_DESTINATION_SIZE-2)) error("Invalid vfilename lenght");
+ if(strlen(virt_destination)<1 || strlen(virt_destination)>(VIRT_DESTINATION_SIZE-2)) error("Invalid pfilename lenght");
+ if(regexec(&dotdot, phys_destination, 0, 0, 0)==0) error("Double dots in pfilename");
+ if(regexec(&dotdot, virt_destination, 0, 0, 0)==0) error("Double dots in vfilename");
+}
+
+//
+// Check directory
+// Only used by cgiMain during initialization
+//
+void checkdirectory(void) {
+ char temp[VIRT_DIRNAME_SIZE];
+
+ cgiFormStringNoNewlines("directory", virt_dirname, VIRT_DIRNAME_SIZE);
+ strip(virt_dirname, VIRT_DIRNAME_SIZE, VALIDCHRS_DST);
+ snprintf(phys_dirname, PHYS_DIRNAME_SIZE, "%s/%s", HOMEDIR, virt_dirname);
+
+ if(strlen(phys_dirname)<2 || strlen(phys_dirname)>(PHYS_DIRNAME_SIZE-2))
+ error("Invalid directory name.");
+
+ if(regexec(&dotdot, phys_dirname, 0, 0, 0)==0) error("Invalid directory name.");
+ if(strlen(phys_dirname) < strlen(HOMEDIR)) error("Invalid directory name.");
+
+ if(!strlen(virt_dirname)) strcpy(virt_dirname, "/");
+
+ // parent
+ strncpy(temp, virt_dirname, VIRT_DIRNAME_SIZE);
+ strncpy(virt_parent, dirname(temp), VIRT_DIRNAME_SIZE);
+}
+
+
+void tstart(void) {
+ gettimeofday(&mt, 0);
+ t1=mt.tv_sec+(mt.tv_usec/1000000.0);
+}
+
+void tstop(void) {
+ gettimeofday(&mt, 0);
+ t2=mt.tv_sec+(mt.tv_usec/1000000.0);
+}
+
+// strip unwanted characters from string
+// deny all by default
+// allow a=alpha n=numeric u=spaces as underscore
+// use: allow="an.-@" for email
+// use: allow="anu.-_!" for filenames
+// ! is a cmd termination char
+// use !! to allow only ! char
+// use !a to allow only letter a
+int strip(char *str, int len, char *allow) {
+ int n,a;
+ int alpha=0, number=0, spctou=0;
+ char *dst;
+
+ if(!str || !strlen(str) || !allow || !strlen(allow))
+ return -1;
+
+ if(*allow == 'a') {
+ alpha=1;
+ allow++;
+ }
+ if(*allow == 'n') {
+ number=1;
+ allow++;
+ }
+ if(*allow == 'u') {
+ spctou=1;
+ allow++;
+ }
+ if(*allow == '!') {
+ allow++;
+ }
+
+ dst=str;
+
+ for(n=0; n<len && *str!='\0'; n++, str++) {
+ if(alpha && isalpha(*str))
+ *(dst++)=*str;
+ else if(number && isdigit(*str))
+ *(dst++)=*str;
+ else if(spctou && *str==' ')
+ *(dst++)='_';
+ else if(strlen(allow))
+ for(a=0; a<strlen(allow); a++)
+ if(*str==allow[a])
+ *(dst++)=*str;
+ }
+
+ *dst='\0';
+
+ return 0; //strlen(dst);
+}
+
+//
+// Byte unit printf
+//
+char *buprintf(float v, int bold) {
+ float size;
+ char unit;
+ char *buffer;
+
+ if(v == -1)
+ return "&nbsp;";
+
+ if(v >= P1024_1 && v < P1024_2 ) { size = v / P1024_1; unit = 'K'; }
+ else if(v >= P1024_2 && v < P1024_3 ) { size = v / P1024_2; unit = 'M'; }
+ else if(v >= P1024_3 && v < P1024_4 ) { size = v / P1024_3; unit = 'G'; }
+ else { size = v; unit = ' '; }
+
+ buffer=(char *)calloc(128, sizeof(char));
+
+ if(unit == 'G' && bold)
+ snprintf(buffer, 128, "<B> %5.1f %cB </B>", size, unit);
+ else if(unit == 'M' && bold)
+ snprintf(buffer, 128, "%5.1f %cB", size, unit);
+ else if(bold)
+ snprintf(buffer, 128, "<FONT COLOR=\"#909090\"> %5.1f %cB </FONT>", size, unit);
+ else
+ snprintf(buffer, 128, " %5.1f %cB", size, unit);
+
+ return (char *)buffer;
+}
+
+//
+// redirect browser
+//
+void redirect(char *location, ...) {
+ va_list ap;
+ char buff[1024];
+
+ va_start(ap, location);
+ vsnprintf(buff, sizeof(buff), location, ap);
+ va_end(ap);
+
+ cgiHeaderLocation(buff);
+}
+
+//
+// CGI entry
+//
+int cgiMain(void) {
+ char action[32];
+ char ea[8];
+ FILE *cfgfile;
+ char cfgname[128];
+ char cfgline[256];
+ char c_tagline[]="tagline=";
+ char c_homeurl[]="browser-url=";
+ char c_homedir[]="directory=";
+ char c_editdef[]="txt-default-edit=true";
+ char c_editany[]="edit-any-file=true";
+ char c_du[]="recursive-du=true";
+ char c_access[]="access";
+
+ // early action - simple actions before cfg is read or access check performed
+ cgiFormStringNoNewlines("ea", ea, sizeof(ea));
+ if(strcmp(ea, "icon")==0) icon();
+ if(strcmp(ea, "upstat")==0) upload_status();
+
+ // normal initialization
+ tstart();
+
+ if(regcomp(&dotdot, "\\.\\.", REG_EXTENDED | REG_ICASE)!=0)
+ error("Regex compilation problem.<BR>%s", strerror(errno));
+
+ fprintf(cgiOut, "Cache-Control: max-age=0, private\r\nExpires: -1\r\n");
+
+ snprintf(ICONSURL, sizeof(ICONSURL), "%s?ea=icon&amp;name=", cgiScriptName);
+
+
+ // read config file and authenticate
+ access_level=PERM_NO; // no access by default
+ access_as_user=0;
+ users_defined=0;
+ edit_by_default=0; // for .txt files
+ edit_any_file=0;
+ recursive_du=0;
+
+ memset(HOMEDIR, 0, sizeof(HOMEDIR));
+ memset(HOMEURL, 0, sizeof(HOMEURL));
+ memset(TAGLINE, 0, sizeof(TAGLINE));
+
+ cgiFormStringNoNewlines("token", token, sizeof(token));
+ snprintf(cfgname, sizeof(cfgname), "%s.cfg", basename(cgiScriptName));
+ cfgfile=fopen(cfgname, "r");
+ if(!cfgfile)
+ error("Unable to open configuration file %s.<BR>%s", cfgname, strerror(errno));
+
+ while(fgets(cfgline, sizeof(cfgline), cfgfile)) {
+ if((*cfgline==';')||(*cfgline=='/')||(*cfgline=='#')||(*cfgline=='\n')) continue;
+ else if(strncmp(cfgline, c_homedir, strlen(c_homedir))==0) strncpy(HOMEDIR, cfgline+strlen(c_homedir), sizeof(HOMEDIR));
+ else if(strncmp(cfgline, c_homeurl, strlen(c_homeurl))==0) strncpy(HOMEURL, cfgline+strlen(c_homeurl), sizeof(HOMEURL));
+ else if(strncmp(cfgline, c_tagline, strlen(c_tagline))==0) strncpy(TAGLINE, cfgline+strlen(c_tagline), sizeof(TAGLINE));
+ else if(strncmp(cfgline, c_editdef, strlen(c_editdef))==0) edit_by_default=1;
+ else if(strncmp(cfgline, c_editany, strlen(c_editany))==0) edit_any_file=1;
+ else if(strncmp(cfgline, c_du, strlen(c_du))==0) recursive_du=1;
+ else if(strncmp(cfgline, c_access, strlen(c_access))==0) access_check(cfgline);
+ }
+ fclose(cfgfile);
+
+ // remove newlines
+ if(strlen(HOMEDIR)>2) HOMEDIR[strlen(HOMEDIR)-1]='\0';
+ if(strlen(HOMEURL)>2) HOMEURL[strlen(HOMEURL)-1]='\0';
+ if(strlen(TAGLINE)>2) TAGLINE[strlen(TAGLINE)-1]='\0';
+
+ // do checks
+ if(strlen(HOMEDIR) < 4 || *HOMEDIR!='/')
+ error("Home directory not defined.");
+
+ if(!strlen(TAGLINE))
+ strcpy(TAGLINE, "Web File Manager");
+
+ checkdirectory();
+
+ // main routine - regular actions
+ cgiFormStringNoNewlines("action", action, sizeof(action));
+ if(cgiFormSubmitClicked("multi_delete_prompt")==cgiFormSuccess && access_level >= PERM_RO) multiprompt_ui("delete");
+ else if(cgiFormSubmitClicked("multi_move_prompt")==cgiFormSuccess && access_level >= PERM_RO) multiprompt_ui("move");
+ else if(cgiFormSubmitClicked("upload")==cgiFormSuccess && access_level >= PERM_RW) receivefile();
+ else if(strcmp(action, "sendfile")==0 && access_level >= PERM_RO) sendfile();
+ else if(strcmp(action, "delete")==0 && access_level >= PERM_RW) delete();
+ else if(strcmp(action, "delete_prompt")==0 && access_level >= PERM_RW) multiprompt_ui("delete");
+ else if(strcmp(action, "move_prompt")==0 && access_level >= PERM_RW) multiprompt_ui("move");
+ else if(strcmp(action, "rename_prompt")==0 && access_level >= PERM_RW) singleprompt_ui("move");
+ else if(strcmp(action, "move")==0 && access_level >= PERM_RW) move();
+ else if(strcmp(action, "edit")==0 && access_level >= PERM_RO) edit_ui();
+ else if(strcmp(action, "edit_save")==0 && access_level >= PERM_RW) edit_save();
+ else if(strcmp(action, "mkfile")==0 && access_level >= PERM_RW) mkfile();
+ else if(strcmp(action, "mkfile_prompt")==0 && access_level >= PERM_RW) singleprompt_ui("mkfile");
+ else if(strcmp(action, "mkdir")==0 && access_level >= PERM_RW) newdir();
+ else if(strcmp(action, "mkdir_prompt")==0 && access_level >= PERM_RW) singleprompt_ui("mkdir");
+ else if(strcmp(action, "about")==0 && access_level >= PERM_RO) about();
+ else if(strcmp(action, "login")==0 ) login();
+ else if( access_level >= PERM_RO) dirlist();
+ else
+ if(users_defined) // if users present but supplied credentials didn't match, or credentials not specified
+ redirect("?action=login");
+ else
+ error("Access Denied.");
+
+ return 0;
+}
+
+
diff --git a/wfm.cfg b/wfm.cfg
new file mode 100644
index 0000000..a5e11f0
--- /dev/null
+++ b/wfm.cfg
@@ -0,0 +1,28 @@
+# tagline or application name
+tagline=Web File Manager
+
+# home directory
+directory=/home/tenox/wfm10
+
+# recursively summarize directory sizes - only enable if you have
+# fast disk (SSD) or a small directory tree structure
+#recursive-du=true
+
+# When clicking on txt file - edit instead of download by default
+#txt-default-edit=true
+
+# Edit any file as it was txt
+edit-any-file=false
+
+# optional browser url prefix - aka "external link" - if defined, file
+# names will be glued to it giving option to be opened directly with the
+# external link button without going through cgi routines
+browser-url=http://x.x.x.x/files/
+
+# access lists - ace type is either access-ip or access-user
+# mixable, eg access-ip=ro:* with number of access-user=rw
+# level is ro|rw, one host or username per line * denotes all hosts
+# user is username:password combination
+access-ip=ro:*
+access-user=ro:guest:secret
+access-user=rw:admin:password
diff --git a/wfm.h b/wfm.h
new file mode 100644
index 0000000..228bf49
--- /dev/null
+++ b/wfm.h
@@ -0,0 +1,149 @@
+#define VERSION "1.0.0"
+#define copyright "<!-- WFM Version " VERSION ", Mountain View, CA, " __DATE__ " [" __TIME__ "] -->\n<!-- Copyright (c) 1994-2015 by Antoni Sawicki -->\n"
+
+#define CSS_STYLE \
+ " <STYLE TYPE=\"text/css\"><!-- \n" \
+ " A:link {text-decoration: none; color:#0000CE; } \n" \
+ " A:visited {text-decoration: none; color:#0000CE; } \n" \
+ " A:active {text-decoration: none; color:#FF0000; } \n" \
+ " A:hover {text-decoration: none; color:#FF0000; } \n" \
+ " body, td, th, input { font-family: Tahoma, Sans-Serif; font-size:11px; } \n" \
+ " html, body { box-sizing: border-box; width:100%%; height:100%%; margin:0px; padding:0px; } \n" \
+ " input { border-color:#000000; border-style:solid; }\n" \
+ " img { vertical-align: middle; }\n" \
+ " .tbr { border-width: 1px; border-style: solid solid solid solid; border-color: #AAAAAA #555555 #555555 #AAAAAA; }\n" \
+ " .twh { width:100%%; height:100%%; }\n" \
+ " --></STYLE>\n"
+
+#define HTML_HEADER \
+ "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n" \
+ " \"http://www.w3.org/TR/html4/loose.dtd\">\n" \
+ "<HTML>\n" \
+ copyright \
+ "<HEAD>\n" \
+ " <META HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html;charset=UTF-8\">\n" \
+ CSS_STYLE
+
+
+#define _FILE_OFFSET_BITS 64
+
+#ifdef __sun__
+#define _POSIX_PTHREAD_SEMANTICS
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <regex.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/shm.h>
+//#include <sys/dir.h>
+#include "md5.h"
+#include "cgic.h"
+#include "wfmiconres.h"
+
+#define VALIDCHRS "anu-_."
+#define VALIDCHRS_DST "anu-_./"
+
+
+#define P1024_1 1024.0f
+#define P1024_2 1048576.0f
+#define P1024_3 1073741824.0f
+#define P1024_4 1099511627776.0f
+
+#define SHM_SIZE 16
+
+#define NORMAL_COLOR "FFFFFF"
+#define HIGH_COLOR "33CC33"
+#define HL_COLOR "FFD700"
+
+
+#define VIRT_DIRNAME_SIZE NAME_MAX
+#define PHYS_DIRNAME_SIZE 1024
+#define VIRT_FILENAME_SIZE NAME_MAX
+#define PHYS_FILENAME_SIZE 1280
+#define VIRT_DESTINATION_SIZE NAME_MAX
+#define PHYS_DESTINATION_SIZE 1280
+
+char virt_dirname[VIRT_DIRNAME_SIZE];
+char phys_dirname[PHYS_DIRNAME_SIZE];
+char virt_filename[VIRT_FILENAME_SIZE];
+char phys_filename[PHYS_FILENAME_SIZE];
+char virt_destination[VIRT_DESTINATION_SIZE];
+char phys_destination[PHYS_DESTINATION_SIZE];
+char virt_parent[VIRT_DIRNAME_SIZE];
+
+char ICONSURL[1024];
+char HOMEDIR[1024];
+char HOMEURL[1024];
+char TAGLINE[1024];
+
+char token[256];
+char loggedinuser[64];
+
+regex_t dotdot;
+int access_level;
+int access_as_user;
+int users_defined;
+int edit_by_default;
+int edit_any_file;
+int recursive_du;
+
+double t1, t2;
+struct timeval mt;
+
+enum { FALSE, TRUE };
+enum { PERM_NO, PERM_RO, PERM_RW };
+
+typedef struct asdir_ {
+ char name[NAME_MAX];
+ mode_t type;
+ off_t size;
+ time_t atime, mtime, rtime;
+} ASDIR;
+
+int namesort(const void *, const void *);
+int rnamesort(const void *, const void *);
+int sizesort(const void *, const void *);
+int rsizesort(const void *, const void *);
+int timesort(const void *, const void *);
+int rtimesort(const void *, const void *);
+int asscandir(const char *, ASDIR **, int (*compar)(const void *, const void *));
+
+void error(char *, ...);
+void redirect(char *, ...);
+char *buprintf(float, int);
+int strip(char *, int, char *);
+void checkfilename(char *);
+void checkdestination(void);
+void mkfile(void);
+void newdir(void);
+void edit_save(void);
+void delete(void);
+void move(void);
+void dirlist(void);
+void edit_ui(void);
+void rename_ui(void);
+void mkdir_ui(void);
+void mkfile_ui(void);
+void multiprompt_ui(char *);
+void about(void);
+void sendfile(void);
+void receivefile(void);
+off_t du(char *);
+void re_dir_ui(char *, int);
+void login_ui(void);
+void tstop(void);
+void html_title(char *);
+void singleprompt_ui(char *);
+