#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; }