/* * SPL - The SPL Programming Language * Copyright (C) 2004, 2005 Clifford Wolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * httpsrc.c: A simple HTTP server engine with an empedded SPL engine * * This is a HTTP/1.0 web server. Conformance to HTTP/1.1 is a long-term * goal. But we reduce the troubles by returning HTTP/1.0 response headers * for now. * * http://www.w3.org/Protocols/rfc2616/rfc2616.html */ #include #include #include #include #include #include #include #include #include #include #include #include #if !defined USECYGWINAPI && !defined USEMACOSXAPI && !defined USEWIN32API && !defined USEIRIXAPI && !defined USEBSDAPI # include # include // #define USE_MMAP_FOR_LARGE_DATA #endif #include "spl.h" #include "webspl_common.h" #include "compat.h" static char *read_line_from_fd(int fd) { int len = 64, count = 0, rc; char *line = malloc(len + 1); while (1) { if (len <= count) { len *= 2; line = realloc(line, len+1); } rc = read(fd, line+count, 1); if (rc != 1) break; if (line[count] == '\n') { if (count > 0 && line[count-1] == '\r') count--; break; } count++; } line[count] = 0; return line; } static char *read_block_from_fd(int fd, int len) { char *data = malloc(len + 1); int count, rc; for (count = 0; count < len; count += rc) { rc = read(fd, data+count, len-count); if (rc < 1) { free(data); return NULL; } } data[count] = 0; return data; } #ifdef USE_MMAP_FOR_LARGE_DATA static char *read_block_from_fd_mmap(int fd, int len, char **filename) { char tmp_filename[] = "/tmp/webspld_XXXXXX"; if (!mkstemp(tmp_filename)) return NULL; *filename = strdup(tmp_filename); int temp_fd = open(tmp_filename, O_RDWR|O_CREAT|O_TRUNC, 0600); ftruncate(temp_fd, len + 1); char *data = mmap(NULL, len + 1, PROT_READ|PROT_WRITE, MAP_SHARED, temp_fd, 0); close(temp_fd); int count, rc; for (count = 0; count < len; count += rc) { rc = read(fd, data+count, len-count); if (rc < 1) { munmap(data, len+1); return NULL; } } data[count] = 0; return data; } #endif static void free_http_request(struct http_request *req) { free(req->method); free(req->url); free(req->query); free(req->pver); free(req->peerip); #ifdef USE_MMAP_FOR_LARGE_DATA if (req->data_mmap_filename) { munmap(req->data, req->data_len+1); unlink(req->data_mmap_filename); } else free(req->data); #else free(req->data); #endif free(req->data_mmap_filename); free(req->data_type); while (req->headers) { struct http_request_hdr *h = req->headers; if (h->name) free(h->name); if (h->value) free(h->value); req->headers = h->next; free(h); } free(req); } static inline void strtoupper(char *p) { for (; *p; p++) *p = toupper(*p); } static inline void strtolower(char *p) { for (; *p; p++) *p = tolower(*p); } static struct http_request *read_http_request(int fd) { struct http_request *req = calloc(1, sizeof(struct http_request)); char *line, *p; int len; struct sockaddr_in peername; socklen_t peerlen = sizeof(struct sockaddr_in); getpeername(fd, (struct sockaddr*)&peername, &peerlen); req->peerip = strdup(inet_ntoa(peername.sin_addr)); line = p = read_line_from_fd(fd); len = strcspn(p, " \t\r\n"); req->method = my_strndup(line, len); p += len + strspn(p + len, " \t\r\n"); len = strcspn(p, "? \t\r\n"); req->url = my_strndup(p, len); p += len + strspn(p + len, " \t\r\n"); if (*p == '?') { len = strcspn(++p, " \t\r\n"); req->query = my_strndup(p, len); p += len + strspn(p + len, " \t\r\n"); } else { req->query = strdup(""); } len = strcspn(p, " \t\r\n"); req->pver = my_strndup(p, len); p += len + strspn(p + len, " \t\r\n"); free(line); strtoupper(req->method); strtoupper(req->pver); if (*req->pver == 0) { // FIXME: Howdy! This is an HTTP 0.9 request! // It is really hard to suppress the response headers, // so we just skip reading the request headers here.. free(req->pver); req->pver = strdup("HTTP/0.9"); return req; } while (1) { line = p = read_line_from_fd(fd); if (*line == 0) { free(line); break; } struct http_request_hdr *hdr = calloc(1, sizeof(struct http_request_hdr)); len = strcspn(p, ": \t\r\n"); hdr->name = my_strndup(p, len); p += len + strspn(p + len, ": \t\r\n"); len = strcspn(p, " \t\r\n"); hdr->value = strdup(p); strtolower(hdr->name); hdr->next = req->headers; req->headers = hdr; free(line); } return req; } static void http_cache_header(int fd) { int expire_seconds = 600; time_t tm; struct tm *ptr; char buf[100]; tm = time(NULL) + expire_seconds; ptr = gmtime(&tm); strftime(buf, 100, "%a, %d %b %Y %H:%M:%S", ptr); my_dprintf(fd, "Cache-Control: min-fresh = %d\r\n", expire_seconds); my_dprintf(fd, "Expires: %s GMT\r\n", buf); } static int http_response_file(int fd, const char *file, const char *type) { int filefd = open(file, O_RDONLY|MY_O_BINARY); if ( filefd < 0 ) return 0; struct stat filest; fstat(filefd, &filest); printf("Sending file as `%s'.\n", type); my_dprintf(fd, "HTTP/1.0 200 OK\r\n"); http_cache_header(fd); my_dprintf(fd, "Content-Type: %s\r\n\r\n", type); for (off_t i=0; ivm = vm; if ( spl_compiler(as, spl_source, file, spl_malloc_file, 1) ) { spl_asm_destroy(as); free(spl_source); return 0; } spl_asm_add(as, SPL_OP_HALT, "1"); spl_optimizer(as); struct spl_code *code = spl_asm_dump(as); spl_asm_destroy(as); free(spl_source); code->id = strdup(file); return code; } static int http_response_script(int fd, const char *file, struct http_request *req, struct spl_vm *(*vm_pool_get)(const char *session, int create), void (*vm_pool_put)(struct spl_vm *vm), struct spl_code *(*filename_to_codepage)(struct spl_vm *vm, const char *file)) { char *dir = strdup(file); char *lastslash = strrchr(dir, '/'); if (lastslash) *lastslash = 0; else { free(dir); dir = strdup("."); } my_dprintf(fd, "HTTP/1.0 200 OK\r\n"); my_dprintf(fd, "Cache-Control: no-cache, must-revalidate, no-store\r\n"); my_dprintf(fd, "Pragma: nocache\r\n"); my_dprintf(fd, "Expires: 0\r\n"); struct cgi_config *cfg; { char currentdir[1024], script_file[1024]; snprintf(script_file, 1024, "%s/%s", getcwd(currentdir, 1024), file); cfg = cgi_config_read(script_file); } struct cgi_context *ctx = spl_mod_cgi_get_cgi_ctx(req, cfg); ctx->outfile = fdopen(dup(fd), "r+"); struct spl_vm *vm = 0; struct spl_task *task; if (ctx->session[0]) { vm = vm_pool_get(ctx->session, 0); if ( !vm ) { if (cgi_config_get_int(cfg, "spl.respawnsessions")) goto respawn_this_session; const char *expirelocation = cgi_config_get_str(cfg, "spl.expirelocation"); printf("This session timed out!\n"); if (expirelocation) my_dprintf(fd, "Content-Type: text/html\r\n\r\n" "\n" " \n", expirelocation); else my_dprintf(fd, "Content-Type: text/html\r\n\r\n" "\n" "\n" "This session timed out!\n" "\n" "

This session timed out!

\n" "
\n" "
WebSPL Daemon
\n" "\n"); free(dir); dir = 0; goto exit_point; } if (vm->current_dir_name) free(vm->current_dir_name); vm->current_dir_name = dir; if (vm->cgi_ctx) spl_mod_cgi_free_cgi_ctx(vm->cgi_ctx); vm->cgi_ctx = ctx; char *task_name = strchr(ctx->session, ':'); if ( task_name ) task = spl_task_lookup(vm, task_name+1); else task = spl_task_lookup(vm, "main"); if ( !task || !(task->flags & SPL_TASK_FLAG_PUBLIC) ) { spl_report(SPL_REPORT_HOST, vm, "Can't find task or task is not public!\n"); goto exit_point; } task->flags &= ~SPL_TASK_FLAG_PAUSED; printf("Resuming session `%s'.\n", ctx->session); vm->cgi_ctx = ctx; } else { respawn_this_session: free(ctx->session); ctx->session = get_new_session(); printf("New session `%s'.\n", ctx->session); vm = vm_pool_get(ctx->session, 1); vm->current_dir_name = dir; vm->cgi_ctx = ctx; int file_len = strlen(file); if (file_len > 8 && !strcmp(file+file_len-8, ".websplb")) { struct spl_code *code = spl_code_get(0); code->code_type = SPL_CODE_MAPPED; code->code = spl_mmap_file(file, &code->size); if (!code->code) { spl_report(SPL_REPORT_HOST, vm, "Can't open bytecode file!\n"); spl_code_put(code); goto exit_point; } task = spl_task_create(vm, "main"); task->flags |= SPL_TASK_FLAG_PUBLIC; spl_task_setcode(task, code); task->code->id = strdup(file); } else { if (!filename_to_codepage) filename_to_codepage = default_filename_to_codepage; struct spl_code *code = filename_to_codepage(vm, file); if (!code) goto exit_point; task = spl_task_create(vm, "main"); task->flags |= SPL_TASK_FLAG_PUBLIC; spl_task_setcode(task, code); } } while ( task && task->code ) { spl_gc_maybe(vm); task = spl_schedule(task); if ( spl_exec(task) < 0 ) break; } exit_point: fflush(ctx->outfile); fclose(ctx->outfile); ctx->outfile = 0; ctx->req = 0; if (ctx->config) { cgi_config_free(ctx->config); ctx->config = 0; } spl_mod_cgi_free_cgi_ctx(ctx); if (vm && vm->cgi_ctx == ctx) vm->cgi_ctx = 0; if (vm && vm_pool_put) vm_pool_put(vm); return 1; } void handle_http_request(int fd, struct spl_vm *(*vm_pool_get)(const char *session, int create), void (*vm_pool_put)(struct spl_vm *vm), struct spl_code *(*filename_to_codepage)(struct spl_vm *vm, const char *file)) { struct http_request *req = read_http_request(fd); printf("Got a %s %s request for `%s'.\n", req->pver, req->method, req->url); if (!strcmp(req->method, "POST")) { int len = -1; struct http_request_hdr *hdr = req->headers; while (hdr) { if (!strcmp(hdr->name, "content-length")) { len = atoi(hdr->value); } if (!strcmp(hdr->name, "content-type")) { if (req->data_type) free(req->data_type); req->data_type = strdup(hdr->value); } hdr = hdr->next; } printf("Going to read %d bytes POST data of mime-type %s.\n", len, req->data_type ?: "*unknown*"); if (len < 0) { printf(".. explicit length is needed for POST method.\n"); goto error_501; } #ifdef USE_MMAP_FOR_LARGE_DATA if (len > 1024*1024) req->data = read_block_from_fd_mmap(fd, len, &req->data_mmap_filename); else req->data = read_block_from_fd(fd, len); #else req->data = read_block_from_fd(fd, len); #endif if (!req->data) { printf(".. reading data for POST method failed.\n"); goto error_501; } req->data_len = len; } else if (strcmp(req->method, "GET")) { error_501: printf("Sending `501 Method Not Implemented'.\n"); my_dprintf(fd, "HTTP/1.0 501 Method Not Implemented\r\n"); my_dprintf(fd, "Content-Type: text/html\r\n\r\n"); my_dprintf(fd, "\n" "\n" "501 Method Not Implemented\n" "\n" "

Method Not Implemented

\n" "

The HTTP Method `%s' is not implemented.

\n" "
\n" "
WebSPL Daemon
\n" "\n", req->method); goto finished; } #if 0 if (!strcmp(req->url, "/dumprequest")) { printf("Sending Request Dump.\n"); my_dprintf(fd, "HTTP/1.0 200 OK\r\n"); my_dprintf(fd, "Content-Type: text/plain\r\n\r\n"); my_dprintf(fd, "Method: %s\n", req->method); my_dprintf(fd, "URL: %s\n", req->url); my_dprintf(fd, "Query: %s\n", req->query); my_dprintf(fd, "PVer: %s\n", req->pver); my_dprintf(fd, "\n"); struct http_request_hdr *hdr = req->headers; while (hdr) { my_dprintf(fd, "[%s]\n%s\n\n", hdr->name, hdr->value); hdr = hdr->next; } goto finished; } #endif if (strstr(req->url, "/.") || *req->url != '/') goto error_404; int url_len = strlen(req->url); if ( url_len && req->url[url_len - 1] == '/' ) { char buffer[url_len+13]; snprintf(buffer, url_len+13, "%sindex.html", req->url + 1); if (!access(buffer, F_OK)) { if (!http_response_file(fd, buffer, "text/html")) goto error_404; goto finished; } snprintf(buffer, url_len+13, "%sindex.websplb", req->url + 1); if (!access(buffer, F_OK)) { if (!http_response_script(fd, buffer, req, vm_pool_get, vm_pool_put, filename_to_codepage)) goto error_404; goto finished; } snprintf(buffer, url_len+13, "%sindex.webspl", req->url + 1); if (!access(buffer, F_OK)) { if (!http_response_script(fd, buffer, req, vm_pool_get, vm_pool_put, filename_to_codepage)) goto error_404; goto finished; } goto error_404; } if ( url_len > 4 && !strcmp(req->url + url_len - 4, ".gif") ) { if (!http_response_file(fd, req->url + 1, "image/gif")) goto error_404; goto finished; } if ( url_len > 4 && !strcmp(req->url + url_len - 4, ".png") ) { if (!http_response_file(fd, req->url + 1, "image/png")) goto error_404; goto finished; } if ( (url_len > 4 && !strcmp(req->url + url_len - 4, ".jpg")) || (url_len > 5 && !strcmp(req->url + url_len - 5, ".jpeg"))) { if (!http_response_file(fd, req->url + 1, "image/jpeg")) goto error_404; goto finished; } if ( url_len > 4 && !strcmp(req->url + url_len - 4, ".ico") ) { if (!http_response_file(fd, req->url + 1, "image/x-icon")) goto error_404; goto finished; } if ( (url_len > 4 && !strcmp(req->url + url_len - 4, ".htm")) || (url_len > 5 && !strcmp(req->url + url_len - 5, ".html"))) { if (!http_response_file(fd, req->url + 1, "text/html")) goto error_404; goto finished; } if ( url_len > 4 && !strcmp(req->url + url_len - 4, ".css") ) { if (!http_response_file(fd, req->url + 1, "text/css")) goto error_404; goto finished; } if ( url_len > 7 && !strcmp(req->url + url_len - 7, ".webspl")) { if (!http_response_script(fd, req->url + 1, req, vm_pool_get, vm_pool_put, filename_to_codepage)) goto error_404; goto finished; } if ( url_len > 8 && !strcmp(req->url + url_len - 8, ".websplb")) { if (!http_response_script(fd, req->url + 1, req, vm_pool_get, vm_pool_put, filename_to_codepage)) goto error_404; goto finished; } error_404: printf("Sending `404 Not Found'.\n"); my_dprintf(fd, "HTTP/1.0 404 Not Found\r\n"); my_dprintf(fd, "Content-Type: text/html\r\n\r\n"); my_dprintf(fd, "\n" "\n" "404 Not Found\n" "\n" "

Not Found

\n" "

The requested URL was not found on this server.

\n" "
\n" "
WebSPL Daemon
\n" "\n"); finished: free_http_request(req); }