#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <locale.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sbase.h>
#ifdef HAVE_ZLIB
#include <zlib.h>
#endif
#ifdef HAVE_BZ2LIB
#include <bzlib.h>
#endif
#include "iniparser.h"
#include "http.h"
#include "mime.h"
#include "trie.h"
#include "stime.h"
#include "logger.h"
#define XHTTPD_VERSION "0.0.1"
#define HTTP_RESP_OK "HTTP/1.1 200 OK"
#define HTTP_BAD_REQUEST "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n"
#define HTTP_NOT_FOUND "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"
#define HTTP_NOT_MODIFIED "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"
#define HTTP_NO_CONTENT "HTTP/1.1 206 No Content\r\nContent-Length: 0\r\n\r\n"
#define LL(x) ((long long)x)
#define UL(x) ((unsigned long int)x)
static SBASE *sbase = NULL;
static SERVICE *service = NULL;
static dictionary *dict = NULL;
static char *httpd_home = "/tmp/xhttpd/html";
static char *http_indexes[HTTP_INDEX_MAX];
static int http_indexes_view = 0;
static int nindexes = 0;
static char *http_default_charset = "UTF-8";
static int httpd_compress = 1;
static char *httpd_compress_cachedir = "/tmp/xhttpd/cache";
static HTTP_VHOST httpd_vhosts[HTTP_VHOST_MAX];
static int nvhosts = 0;
static int httpd_proxy_timeout = 0;
static void *namemap = NULL;
static void *hostmap = NULL;
static void *urlmap = NULL;
static void *http_headers_map = NULL;
static void *logger = NULL;
/* mkdir recursive */
int xhttpd_mkdir(char *path, int mode)
{
char *p = NULL, fullpath[HTTP_PATH_MAX];
int ret = 0, level = -1;
struct stat st;
if(path)
{
strcpy(fullpath, path);
p = fullpath;
while(*p != '\0')
{
if(*p == '/' )
{
while(*p != '\0' && *p == '/' && *(p+1) == '/')++p;
if(level > 0)
{
*p = '\0';
memset(&st, 0, sizeof(struct stat));
ret = stat(fullpath, &st);
if(ret == 0 && !S_ISDIR(st.st_mode)) return -1;
if(ret != 0 && mkdir(fullpath, mode) != 0) return -1;
*p = '/';
}
level++;
}
++p;
}
return 0;
}
return -1;
}
/* xhttpd packet reader */
int xhttpd_packet_reader(CONN *conn, CB_DATA *buffer)
{
return buffer->ndata;
}
/* xhttpd index view */
int xhttpd_index_view(CONN *conn, HTTP_REQ *http_req, char *file, char *root, char *end)
{
char buf[HTTP_BUF_SIZE], url[HTTP_PATH_MAX], *p = NULL, *e = NULL, *pp = NULL;
struct dirent *ent = NULL;
unsigned char *s = NULL;
struct stat st = {0};
int len = 0, n = 0, keepalive = 0;
DIR *dirp = NULL;
if(conn && file && root && end && (dirp = opendir(file)))
{
if((p = pp = (char *)calloc(1, HTTP_INDEXES_MAX)))
{
p += sprintf(p, "<html><head><title>Indexes Of %s</title>"
"<head><body><h1 align=center>xhttpd</h1>", root);
if(*end == '/') --end;
while(end > root && *end != '/')--end;
if(end == root)
p += sprintf(p, "<a href='/' >/</a><br>");
else if(end > root)
{
*end = '\0';
p += sprintf(p, "<a href='%s/' >..</a><br>", root);
}
p += sprintf(p, "<hr noshade><table><tr align=left>"
"<th width=500>Name</th><th width=200>Size</th>"
"<th>Last-Modified</th></tr>");
end = p;
while((ent = readdir(dirp)) != NULL)
{
if(ent->d_name[0] != '.' && ent->d_reclen > 0)
{
p += sprintf(p, "<tr>");
s = (unsigned char *)ent->d_name;
e = url;
while(*s != '\0')
{
if(*s == 0x20 && *s > 127)
{
e += sprintf(e, "%%%02x", *s);
}else *e++ = *s++;
}
*e = '\0';
if(ent->d_type == DT_DIR)
{
p += sprintf(p, "<td><a href='%s/' >%s/</a></td>",
url, ent->d_name);
}
else
{
p += sprintf(p, "<td><a href='%s' >%s</a></td>",
url, ent->d_name);
}
sprintf(buf, "%s/%s", file, ent->d_name);
if(ent->d_type != DT_DIR && lstat(buf, &st) == 0)
{
if(st.st_size >= (off_t)HTTP_BYTE_G)
p += sprintf(p, "<td> %.1fG </td>",
(double)st.st_size/(double) HTTP_BYTE_G);
else if(st.st_size >= (off_t)HTTP_BYTE_M)
p += sprintf(p, "<td> %lldM </td>",
LL(st.st_size/(off_t)HTTP_BYTE_M));
else if(st.st_size >= (off_t)HTTP_BYTE_K)
p += sprintf(p, "<td> %lldK </td>",
LL(st.st_size/(off_t)HTTP_BYTE_K));
else
p += sprintf(p, "<td> %lldB </td>", LL(st.st_size));
}
else p += sprintf(p, "<td> - </td>");
p += sprintf(p, "<td>");
p += strdate(st.st_mtime, p);
p += sprintf(p, "</td>");
p += sprintf(p, "</tr>");
}
}
p += sprintf(p, "</table>");
if(end != p) p += sprintf(p, "<hr noshade>");
p += sprintf(p, "<em></body></html>");
len = (p - pp);
p = buf;
p += sprintf(p, "HTTP/1.1 200 OK\r\nContent-Length:%lld\r\n"
"Content-Type: text/html; charset=%s\r\n",
LL(len), http_default_charset);
if((n = http_req->headers[HEAD_GEN_CONNECTION]) > 0)
{
p += sprintf(p, "Connection: %s\r\n", http_req->hlines + n);
if((strncasecmp(http_req->hlines + n, "close", 5)) !=0 )
keepalive = 1;
}
else
{
p += sprintf(p, "Connection: close\r\n");
}
p += sprintf(p, "Date: ");p += GMTstrdate(time(NULL), p);p += sprintf(p, "\r\n");
p += sprintf(p, "Server: xhttpd/%s\r\n\r\n", XHTTPD_VERSION);
conn->push_chunk(conn, buf, (p - buf));
conn->push_chunk(conn, pp, len);
//fprintf(stdout, "buf:%s pp:%s\n", buf, pp);
free(pp);
pp = NULL;
if(!keepalive) conn->over(conn);
}
closedir(dirp);
return 0;
}
else
{
fprintf(stderr, "%s::%d open dir:%s file:%p root:%p end:%p failed, %s\n", __FILE__, __LINE__, file, file, root, end, strerror(errno));
}
return -1;
}
#ifdef HAVE_ZLIB
int xhttpd_gzip(unsigned char **zstream, unsigned char *in, int inlen, time_t mtime)
{
unsigned char *c = NULL, *out = NULL;
unsigned long crc = 0;
int outlen = 0;
z_stream z = {0};
if(in && inlen > 0)
{
z.zalloc = Z_NULL;
z.zfree = Z_NULL;
z.opaque = Z_NULL;
if(deflateInit2(&z, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
-MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK)
{
return -1;
}
z.next_in = (unsigned char *)in;
z.avail_in = inlen;
z.total_in = 0;
outlen = (inlen * 1.1) + 12 + 18;
if((*zstream = out = (unsigned char *)calloc(1, outlen)))
{
c = out;
c[0] = 0x1f;
c[1] = 0x8b;
c[2] = Z_DEFLATED;
c[3] = 0; /* options */
c[4] = (mtime >> 0) & 0xff;
c[5] = (mtime >> 8) & 0xff;
c[6] = (mtime >> 16) & 0xff;
c[7] = (mtime >> 24) & 0xff;
c[8] = 0x00; /* extra flags */
c[9] = 0x03; /* UNIX */
z.next_out = out + 10;
z.avail_out = outlen - 10 - 8;
z.total_out = 0;
if(deflate(&z, Z_FINISH) != Z_STREAM_END)
{
deflateEnd(&z);
free(*zstream);
*zstream = NULL;
return -1;
}
//crc
crc = http_crc32(in, inlen);
c = *zstream + 10 + z.total_out;
c[0] = (crc >> 0) & 0xff;
c[1] = (crc >> 8) & 0xff;
c[2] = (crc >> 16) & 0xff;
c[3] = (crc >> 24) & 0xff;
c[4] = (z.total_in >> 0) & 0xff;
c[5] = (z.total_in >> 8) & 0xff;
c[6] = (z.total_in >> 16) & 0xff;
c[7] = (z.total_in >> 24) & 0xff;
outlen = (10 + 8 + z.total_out);
if(deflateEnd(&z) != Z_OK)
{
free(*zstream);
*zstream = NULL;
return -1;
}
return outlen;
}
}
return -1;
}
/* deflate */
int xhttpd_deflate(unsigned char **zstream, unsigned char *in, int inlen)
{
unsigned char *out = NULL;
z_stream z = {0};
int outlen = 0;
if(in && inlen > 0)
{
z.zalloc = Z_NULL;
z.zfree = Z_NULL;
z.opaque = Z_NULL;
if(deflateInit2(&z, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
-MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK)
{
return -1;
}
z.next_in = (unsigned char *)in;
z.avail_in = inlen;
z.total_in = 0;
outlen = (inlen * 1.1) + 12 + 18;
if((*zstream = out = (unsigned char *)calloc(1, outlen)))
{
z.next_out = out;
z.avail_out = outlen;
z.total_out = 0;
if(deflate(&z, Z_FINISH) != Z_STREAM_END)
{
deflateEnd(&z);
free(*zstream);
*zstream = NULL;
return -1;
}
outlen = z.total_out;
if(deflateEnd(&z) != Z_OK)
{
free(*zstream);
*zstream = NULL;
return -1;
}
return outlen;
}
}
return -1;
}
#endif
#ifdef HAVE_BZ2LIB
int xhttpd_bzip2(unsigned char **zstream, unsigned char *in, int inlen)
{
unsigned char *out = NULL;
bz_stream bz = {0};
int outlen = 0;
if(in && inlen > 0)
{
bz.bzalloc = NULL;
bz.bzfree = NULL;
bz.opaque = NULL;
if(BZ2_bzCompressInit(&bz, 9, 0, 0) != BZ_OK)
{
return -1;
}
bz.next_in = (char *)in;
bz.avail_in = inlen;
bz.total_in_lo32 = 0;
bz.total_in_hi32 = 0;
outlen = (inlen * 1.1) + 12;
if((*zstream = out = (unsigned char *)calloc(1, outlen)))
{
bz.next_out = (char *)out;
bz.avail_out = outlen;
bz.total_out_lo32 = 0;
bz.total_out_hi32 = 0;
if(BZ2_bzCompress(&bz, BZ_FINISH) != BZ_STREAM_END)
{
BZ2_bzCompressEnd(&bz);
free(*zstream);
*zstream = NULL;
return -1;
}
if(bz.total_out_hi32)
{
free(*zstream);
*zstream = NULL;
return -1;
}
outlen = bz.total_out_lo32;
if(BZ2_bzCompressEnd(&bz) != BZ_OK)
{
free(*zstream);
*zstream = NULL;
return -1;
}
}
}
return -1;
}
#endif
/* httpd file compress */
int xhttpd_compress_handler(CONN *conn, HTTP_REQ *http_req, char *host, int is_need_compress, int mimeid,
char *file, char *root, off_t from, off_t to, struct stat *st)
{
char zfile[HTTP_PATH_MAX], zoldfile[HTTP_PATH_MAX], linkfile[HTTP_PATH_MAX],
buf[HTTP_BUF_SIZE], *encoding = NULL, *outfile = NULL, *p = NULL;
unsigned char *block = NULL, *in = NULL, *zstream = NULL;
int fd = 0, inlen = 0, zlen = 0, i = 0, id = 0, keepalive = 0;
off_t offset = 0, len = 0;
struct stat zst = {0};
if(is_need_compress)
{
if(httpd_compress)
{
for(i = 0; i < HTTP_ENCODING_NUM; i++)
{
if(is_need_compress & ((id = (1 << i))))
{
encoding = (char *)http_encodings[i];
if(from == 0 && to == st->st_size)
{
sprintf(linkfile, "%s/%s%s.%s", httpd_compress_cachedir,
host, root, encoding);
}
else
{
sprintf(linkfile, "%s/%s%s-%lld-%lld.%s", httpd_compress_cachedir,
host, root, LL(from), LL(to), encoding);
}
sprintf(zfile, "%s.%lu", linkfile, UL(st->st_mtime));
if(access(zfile, F_OK) == 0)
{
lstat(zfile, &zst);
outfile = zfile;
from = 0;
len = zst.st_size;
goto OVER;
}
else
{
if(access(linkfile, F_OK))
{
xhttpd_mkdir(linkfile, 0755);
}
else
{
if(readlink(linkfile, zoldfile, (HTTP_PATH_MAX - 1)))
unlink(zoldfile);
unlink(linkfile);
}
goto COMPRESS;
}
break;
}
}
}
COMPRESS:
offset = (from/(off_t)HTTP_MMAP_MAX) * HTTP_MMAP_MAX;
len = to - offset;
if(len < HTTP_MMAP_MAX && (fd = open(file, O_RDONLY)) > 0)
{
if((block = (unsigned char *)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0)))
{
in = block + (from%(off_t)HTTP_MMAP_MAX);
inlen = to - from;
#ifdef HAVE_ZLIB
if(is_need_compress & HTTP_ENCODING_DEFLATE)
{
if((zlen = xhttpd_deflate(&zstream, in, inlen)) <= 0)
goto err;
encoding = "deflate";
//compressed |= HTTP_ENCODING_DEFLATE;
}
else if(is_need_compress & HTTP_ENCODING_GZIP)
{
if((zlen = xhttpd_gzip(&zstream, in, inlen,
st->st_mtime)) <= 0) goto err;
encoding = "gzip";
//compressed |= HTTP_ENCODING_GZIP;
}
/*
else if(is_need_compress & HTTP_ENCODING_COMPRESS)
{
compressed |= HTTP_ENCODING_COMPRESS;
}
*/
#endif
#ifdef HAVE_BZ2LIB
if(encoding == NULL && is_need_compress & HTTP_ENCODING_BZIP2)
{
if((zlen = xhttpd_bzip2(&zstream, in, inlen)) <= 0) goto err;
encoding = "bzip2";
//compressed |= HTTP_ENCODING_BZIP2;
}
#endif
munmap(block, len);
block = NULL;
}
close(fd);
if(encoding == NULL) goto err;
//write to cache file
if(httpd_compress && zstream && zlen > 0)
{
if((fd = open(zfile, O_CREAT|O_WRONLY, 0644)) > 0)
{
if(symlink(zfile, linkfile) != 0 || write(fd, zstream, zlen) <= 0 )
{
FATAL_LOGGER(logger, "symlink/write to %s failed, %s",
linkfile, strerror(errno));
}
close(fd);
}
}
}
OVER:
p = buf;
if(from > 0)
{
p += sprintf(p, "HTTP/1.1 206 Partial Content\r\nAccept-Ranges: bytes\r\n"
"Content-Range: bytes %lld-%lld/%lld\r\n",
LL(from), LL(to - 1), LL(st->st_size));
}
else
p += sprintf(p, "HTTP/1.1 200 OK\r\nAccept-Ranges: bytes\r\n");
p += sprintf(p, "Content-Type: %s; charset=%s\r\n",
http_mime_types[mimeid].s, http_default_charset);
/*
if((n = http_req->headers[HEAD_GEN_CONNECTION]) > 0)
{
p += sprintf(p, "Connection: %s\r\n", http_req->hlines + n);
}
*/
p += sprintf(p, "Last-Modified:");
p += GMTstrdate(st->st_mtime, p);
p += sprintf(p, "%s", "\r\n");//date end
if(zstream && zlen > 0) len = zlen;
if(encoding) p += sprintf(p, "Content-Encoding: %s\r\n", encoding);
p += sprintf(p, "Content-Length: %lld\r\n", LL(len));
p += sprintf(p, "Date: ");p += GMTstrdate(time(NULL), p);p += sprintf(p, "\r\n");
p += sprintf(p, "Connection: close\r\n");
p += sprintf(p, "Server: xhttpd/%s\r\n\r\n", XHTTPD_VERSION);
conn->push_chunk(conn, buf, (p - buf));
if(zstream && zlen > 0)
{
conn->push_chunk(conn, zstream, zlen);
}
else
{
conn->push_file(conn, outfile, from, len);
}
if(zstream) free(zstream);
if(!keepalive)conn->over(conn);
return 0;
}
err:
return -1;
}
/* xhttpd bind proxy */
int xhttpd_bind_proxy(CONN *conn, char *host, int port)
{
CONN *new_conn = NULL;
SESSION session = {0};
char *ip = NULL;
if(conn && host && port > 0)
{
if(ip)
{
session.packet_type = PACKET_PROXY;
session.timeout = httpd_proxy_timeout;
if((new_conn = service->newproxy(service, conn, -1, -1, ip, port, &session)))
{
new_conn->start_cstate(new_conn);
return 0;
}
}
}
return -1;
}