From fc45b63e478f6f891f0d04bf49423be30a63617d Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 27 May 2005 00:29:58 +0000 Subject: r6998: - added support for application[] data, which is global to all clients using the web server. This allows for things like application['state'] = "shuttting down" and then every web client can see that the server is going down - added support for session[] data. This allows web pages to store long term data specific to this client. It relies on cookies. Sessions auto timeout (default timeout 5 minutes). The timeout can be set in the scripts. - changed from processing all .html files as esp, to only processing .esp files as esp. This makes it easier to compare the samba web server to appWeb as a reference implementation. - expanded the number of standard variables setup by esp. See the showvars.esp example page for all variables. (This used to be commit c418b23c2ea383da8fad21b62213ec01fd135ebb) --- source4/web_server/ejs/miniMpr.c | 9 +- source4/web_server/esp/esp.h | 6 +- source4/web_server/http.c | 329 +++++++++++++++++++++++++++++++-------- source4/web_server/web_server.c | 7 + source4/web_server/web_server.h | 8 + 5 files changed, 287 insertions(+), 72 deletions(-) (limited to 'source4/web_server') diff --git a/source4/web_server/ejs/miniMpr.c b/source4/web_server/ejs/miniMpr.c index 38241e5c71..46d9579c7e 100644 --- a/source4/web_server/ejs/miniMpr.c +++ b/source4/web_server/ejs/miniMpr.c @@ -37,16 +37,9 @@ static TALLOC_CTX *mpr_ctx; -void mprFreeAll(void) -{ - talloc_free(mpr_ctx); - mpr_ctx = NULL; -} - void mprSetCtx(TALLOC_CTX *ctx) { - talloc_free(mpr_ctx); - mpr_ctx = talloc_new(ctx); + mpr_ctx = ctx; } void mprFree(void *ptr) diff --git a/source4/web_server/esp/esp.h b/source4/web_server/esp/esp.h index 5d343db96e..9e58bdf066 100644 --- a/source4/web_server/esp/esp.h +++ b/source4/web_server/esp/esp.h @@ -96,13 +96,13 @@ typedef struct Esp { int maxScriptSize; void (*createSession)(EspHandle handle, int timeout); void (*destroySession)(EspHandle handle); - char *(*getSessionId)(EspHandle handle); + const char *(*getSessionId)(EspHandle handle); int (*mapToStorage)(EspHandle handle, char *path, int len, const char *uri, int flags); int (*readFile)(EspHandle handle, char **buf, int *len, const char *path); void (*redirect)(EspHandle handle, int code, char *url); - void (*setCookie)(EspHandle handle, char *name, char *value, - int lifetime, char *path, bool secure); + void (*setCookie)(EspHandle handle, const char *name, const char *value, + int lifetime, const char *path, bool secure); void (*setHeader)(EspHandle handle, const char *value, bool allowMultiple); void (*setResponseCode)(EspHandle handle, int code); int (*writeBlock)(EspHandle handle, char *buf, int size); diff --git a/source4/web_server/http.c b/source4/web_server/http.c index 25595a8ad7..5dd9e35edd 100644 --- a/source4/web_server/http.c +++ b/source4/web_server/http.c @@ -21,6 +21,7 @@ */ #include "includes.h" +#include "smbd/service_task.h" #include "web_server/web_server.h" #include "smbd/service_stream.h" #include "lib/events/events.h" @@ -28,23 +29,42 @@ #include "system/iconv.h" #include "system/time.h" #include "web_server/esp/esp.h" +#include "dlinklist.h" -/* state of the esp subsystem */ +#define SWAT_SESSION_KEY "_swat_session_" + +/* + context for long term storage in the web server, to support session[] + and application[] data. Stored in task->private. +*/ +struct esp_data { + struct session_data { + struct session_data *next, *prev; + struct esp_data *edata; + const char *id; + struct MprVar *data; + struct timed_event *te; + int lifetime; + } *sessions; + struct MprVar *application_data; +}; + +/* state of the esp subsystem for a specific request */ struct esp_state { struct websrv_context *web; - struct MprVar variables[ESP_OBJ_MAX]; struct EspRequest *req; + struct MprVar variables[ESP_OBJ_MAX]; + struct session_data *session; }; /* destroy a esp session */ static int esp_destructor(void *ptr) { struct esp_state *esp = talloc_get_type(ptr, struct esp_state); + if (esp->req) { espDestroyRequest(esp->req); } - espClose(); - mprFreeAll(); return 0; } @@ -118,7 +138,7 @@ static const char *http_local_path(struct websrv_context *web, const char *url) if (path == NULL) return NULL; if (directory_exist(path)) { - path = talloc_asprintf_append(path, "/index.html"); + path = talloc_asprintf_append(path, "/index.esp"); } return path; } @@ -199,6 +219,7 @@ static void http_setHeader(EspHandle handle, const char *value, bool allowMultip } web->output.headers = str_list_add(web->output.headers, value); + talloc_steal(web, web->output.headers); } /* @@ -256,16 +277,61 @@ internal_error: } -/* callbacks for esp processing */ -static const struct Esp esp_control = { - .maxScriptSize = 60000, - .writeBlock = http_writeBlock, - .setHeader = http_setHeader, - .redirect = http_redirect, - .setResponseCode = http_setResponseCode, - .readFile = http_readFile, - .mapToStorage = http_mapToStorage -}; +/* + setup a cookie +*/ +static void http_setCookie(EspHandle handle, const char *name, const char *value, + int lifetime, const char *path, bool secure) +{ + struct websrv_context *web = talloc_get_type(handle, struct websrv_context); + char *buf; + + if (lifetime > 0) { + buf = talloc_asprintf(web, "Set-Cookie: %s=%s; path=%s; Expires=%s; %s", + name, value, path?path:"/", + http_timestring(web, time(NULL)+lifetime), + secure?"secure":""); + } else { + buf = talloc_asprintf(web, "Set-Cookie: %s=%s; path=%s; %s", + name, value, path?path:"/", + secure?"secure":""); + } + http_setHeader(handle, "Cache-control: no-cache=\"set-cookie\"", 0); + http_setHeader(handle, buf, 0); + talloc_free(buf); +} + +/* + return the session id +*/ +static const char *http_getSessionId(EspHandle handle) +{ + struct websrv_context *web = talloc_get_type(handle, struct websrv_context); + return web->session->id; +} + +/* + setup a session +*/ +static void http_createSession(EspHandle handle, int timeout) +{ + struct websrv_context *web = talloc_get_type(handle, struct websrv_context); + if (web->session) { + web->session->lifetime = timeout; + http_setCookie(web, SWAT_SESSION_KEY, web->session->id, + web->session->lifetime, "/", 0); + } +} + +/* + destroy a session +*/ +static void http_destroySession(EspHandle handle) +{ + struct websrv_context *web = talloc_get_type(handle, struct websrv_context); + talloc_free(web->session); + web->session = NULL; +} /* @@ -302,6 +368,7 @@ void http_error_unix(struct websrv_context *web, const char *info) code = 403; break; } + info = talloc_asprintf(web, "%s

%s

\n", info, strerror(errno)); http_error(web, code, info); } @@ -347,37 +414,48 @@ static void http_setup_arrays(struct esp_state *esp) struct EspRequest *req = esp->req; char *p; - espSetStringVar(req, ESP_REQUEST_OBJ, "CONTENT_LENGTH", - talloc_asprintf(esp, "%u", web->input.content_length)); - if (web->input.query_string) { - espSetStringVar(req, ESP_REQUEST_OBJ, "QUERY_STRING", - web->input.query_string); - } - espSetStringVar(req, ESP_REQUEST_OBJ, "REQUEST_METHOD", - web->input.post_request?"POST":"GET"); - espSetStringVar(req, ESP_REQUEST_OBJ, "REQUEST_URI", web->input.url); - p = strrchr(web->input.url, '/'); - espSetStringVar(req, ESP_REQUEST_OBJ, "SCRIPT_NAME", p+1); +#define SETVAR(type, name, value) do { \ + const char *v = value; \ + if (v) espSetStringVar(req, type, name, v); \ +} while (0) - if (web->input.referer) { - espSetStringVar(req, ESP_HEADERS_OBJ, "HTT_REFERER", web->input.referer); - } - if (web->input.user_agent) { - espSetStringVar(req, ESP_HEADERS_OBJ, "USER_AGENT", web->input.user_agent); + SETVAR(ESP_REQUEST_OBJ, "CONTENT_LENGTH", + talloc_asprintf(esp, "%u", web->input.content_length)); + SETVAR(ESP_REQUEST_OBJ, "QUERY_STRING", web->input.query_string); + SETVAR(ESP_REQUEST_OBJ, "REQUEST_METHOD", web->input.post_request?"POST":"GET"); + SETVAR(ESP_REQUEST_OBJ, "REQUEST_URI", web->input.url); + p = strrchr(web->input.url, '/'); + SETVAR(ESP_REQUEST_OBJ, "SCRIPT_NAME", p+1); + p = socket_get_peer_name(web->conn->socket, esp); + SETVAR(ESP_REQUEST_OBJ, "REMOTE_HOST", p); + SETVAR(ESP_REQUEST_OBJ, "REMOTE_ADDR", p); + SETVAR(ESP_REQUEST_OBJ, "REMOTE_USER", ""); + SETVAR(ESP_REQUEST_OBJ, "CONTENT_TYPE", web->input.content_type); + if (web->session) { + SETVAR(ESP_REQUEST_OBJ, "SESSION_ID", web->session->id); } - espSetStringVar(req, ESP_SERVER_OBJ, "SERVER_ADDR", - socket_get_my_addr(web->conn->socket, esp)); - espSetStringVar(req, ESP_SERVER_OBJ, "SERVER_PORT", - talloc_asprintf(esp, "%u", socket_get_my_port(web->conn->socket))); - espSetStringVar(req, ESP_SERVER_OBJ, "SERVER_PROTOCOL", "http"); - espSetStringVar(esp->req, ESP_REQUEST_OBJ, "SCRIPT_FILENAME", web->input.url); + SETVAR(ESP_HEADERS_OBJ, "HTT_REFERER", web->input.referer); + SETVAR(ESP_HEADERS_OBJ, "HOST", web->input.host); + SETVAR(ESP_HEADERS_OBJ, "ACCEPT_ENCODING", web->input.accept_encoding); + SETVAR(ESP_HEADERS_OBJ, "ACCEPT_LANGUAGE", web->input.accept_language); + SETVAR(ESP_HEADERS_OBJ, "ACCEPT_CHARSET", web->input.accept_charset); + SETVAR(ESP_HEADERS_OBJ, "COOKIE", web->input.cookie); + SETVAR(ESP_HEADERS_OBJ, "USER_AGENT", web->input.user_agent); + + SETVAR(ESP_SERVER_OBJ, "SERVER_ADDR", socket_get_my_addr(web->conn->socket, esp)); + SETVAR(ESP_SERVER_OBJ, "SERVER_NAME", socket_get_my_addr(web->conn->socket, esp)); + SETVAR(ESP_SERVER_OBJ, "SERVER_HOST", socket_get_my_addr(web->conn->socket, esp)); + SETVAR(ESP_SERVER_OBJ, "DOCUMENT_ROOT", lp_swat_directory()); + SETVAR(ESP_SERVER_OBJ, "SERVER_PORT", + talloc_asprintf(esp, "%u", socket_get_my_port(web->conn->socket))); + SETVAR(ESP_SERVER_OBJ, "SERVER_PROTOCOL", "http"); + SETVAR(ESP_SERVER_OBJ, "SERVER_SOFTWARE", "SWAT"); + SETVAR(ESP_SERVER_OBJ, "GATEWAY_INTERFACE", "CGI/1.1"); + SETVAR(ESP_REQUEST_OBJ, "SCRIPT_FILENAME", web->input.url); } - - - /* process a esp request */ @@ -503,25 +581,90 @@ static NTSTATUS http_parse_get(struct esp_state *esp) } /* - setup some standard variables + called when a session times out */ -static void http_setup_vars(struct esp_state *esp) +static void session_timeout(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private) { - int i; + struct session_data *s = talloc_get_type(private, struct session_data); + talloc_free(s); +} - for (i = 0; i < ESP_OBJ_MAX; i++) { - esp->variables[i] = mprCreateUndefinedVar(); +/* + destroy a session + */ +static int session_destructor(void *ptr) +{ + struct session_data *s = talloc_get_type(ptr, struct session_data); + DLIST_REMOVE(s->edata->sessions, s); + return 0; +} + +/* + setup the session for this request +*/ +static void http_setup_session(struct esp_state *esp) +{ + const char *session_key = SWAT_SESSION_KEY; + char *p; + const char *cookie = esp->web->input.cookie; + const char *key = NULL; + struct esp_data *edata = talloc_get_type(esp->web->task->private, struct esp_data); + struct session_data *s; + + /* look for our session key */ + if (cookie && (p = strstr(cookie, session_key)) && + p[strlen(session_key)] == '=') { + p += strlen(session_key)+1; + key = talloc_strndup(esp, p, strcspn(p, ";")); } - esp->variables[ESP_HEADERS_OBJ] = mprCreateObjVar("headers", ESP_HASH_SIZE); - esp->variables[ESP_FORM_OBJ] = mprCreateObjVar("form", ESP_HASH_SIZE); - esp->variables[ESP_APPLICATION_OBJ] = mprCreateObjVar("application", ESP_HASH_SIZE); - esp->variables[ESP_COOKIES_OBJ] = mprCreateObjVar("cookies", ESP_HASH_SIZE); - esp->variables[ESP_FILES_OBJ] = mprCreateObjVar("files", ESP_HASH_SIZE); - esp->variables[ESP_REQUEST_OBJ] = mprCreateObjVar("request", ESP_HASH_SIZE); - esp->variables[ESP_SERVER_OBJ] = mprCreateObjVar("server", ESP_HASH_SIZE); - esp->variables[ESP_SESSION_OBJ] = mprCreateObjVar("session", ESP_HASH_SIZE); + + if (key == NULL) { + key = generate_random_str_list(esp, 64, "0123456789"); + } + + /* try to find this session in the existing session list */ + for (s=edata->sessions;s;s=s->next) { + if (strcmp(key, s->id) == 0) break; + } + + if (s == NULL) { + /* create a new session */ + s = talloc_zero(edata, struct session_data); + s->id = talloc_steal(s, key); + s->data = NULL; + s->te = NULL; + s->edata = edata; + s->lifetime = lp_parm_int(-1, "http", "sessiontimeout", 300); + DLIST_ADD(edata->sessions, s); + talloc_set_destructor(s, session_destructor); + } + + http_setCookie(esp->web, session_key, key, s->lifetime, "/", 0); + + if (s->data) { + mprCopyVar(&esp->variables[ESP_SESSION_OBJ], s->data, MPR_DEEP_COPY); + } + + esp->web->session = s; } + +/* callbacks for esp processing */ +static const struct Esp esp_control = { + .maxScriptSize = 60000, + .writeBlock = http_writeBlock, + .setHeader = http_setHeader, + .redirect = http_redirect, + .setResponseCode = http_setResponseCode, + .readFile = http_readFile, + .mapToStorage = http_mapToStorage, + .setCookie = http_setCookie, + .createSession = http_createSession, + .destroySession = http_destroySession, + .getSessionId = http_getSessionId +}; + /* process a complete http request */ @@ -529,17 +672,22 @@ void http_process_input(struct websrv_context *web) { NTSTATUS status; struct esp_state *esp; + struct esp_data *edata = talloc_get_type(web->task->private, struct esp_data); char *p; int i; const char *file_type = NULL; + BOOL esp_enable = False; const struct { const char *extension; const char *mime_type; + BOOL esp_enable; } mime_types[] = { {"gif", "image/gif"}, {"png", "image/png"}, {"jpg", "image/jpeg"}, - {"txt", "text/plain"} + {"txt", "text/plain"}, + {"ico", "image/x-icon"}, + {"esp", "text/html", True} }; esp = talloc_zero(web, struct esp_state); @@ -549,12 +697,29 @@ void http_process_input(struct websrv_context *web) mprSetCtx(esp); - talloc_set_destructor(esp, esp_destructor); - if (espOpen(&esp_control) != 0) goto internal_error; - http_setup_vars(esp); - + for (i=0;ivariables);i++) { + esp->variables[i] = mprCreateUndefinedVar(); + } + esp->variables[ESP_HEADERS_OBJ] = mprCreateObjVar("headers", ESP_HASH_SIZE); + esp->variables[ESP_FORM_OBJ] = mprCreateObjVar("form", ESP_HASH_SIZE); + esp->variables[ESP_APPLICATION_OBJ] = mprCreateObjVar("application", ESP_HASH_SIZE); + esp->variables[ESP_COOKIES_OBJ] = mprCreateObjVar("cookies", ESP_HASH_SIZE); + esp->variables[ESP_FILES_OBJ] = mprCreateObjVar("files", ESP_HASH_SIZE); + esp->variables[ESP_REQUEST_OBJ] = mprCreateObjVar("request", ESP_HASH_SIZE); + esp->variables[ESP_SERVER_OBJ] = mprCreateObjVar("server", ESP_HASH_SIZE); + esp->variables[ESP_SESSION_OBJ] = mprCreateObjVar("session", ESP_HASH_SIZE); + + if (edata->application_data) { + mprCopyVar(&esp->variables[ESP_APPLICATION_OBJ], + edata->application_data, MPR_DEEP_COPY); + } + + http_setup_session(esp); + + talloc_set_destructor(esp, esp_destructor); + esp->req = espCreateRequest(web, web->input.url, esp->variables); if (esp->req == NULL) goto internal_error; @@ -562,7 +727,8 @@ void http_process_input(struct websrv_context *web) http_error(web, 400, "You must specify a GET or POST request"); return; } - + + /* parse any form or get variables */ if (web->input.post_request) { status = http_parse_post(esp); if (!NT_STATUS_IS_OK(status)) { @@ -577,11 +743,12 @@ void http_process_input(struct websrv_context *web) } } - /* process all html files as ESP */ + /* work out the mime type */ p = strrchr(web->input.url, '.'); for (i=0;p && iapplication_data); + edata->application_data = talloc_zero(edata, struct MprVar); + mprSetCtx(edata->application_data); + mprCopyVar(edata->application_data, &esp->variables[ESP_APPLICATION_OBJ], MPR_DEEP_COPY); + + /* copy any session data */ + if (web->session) { + talloc_free(web->session->data); + web->session->data = talloc_zero(web->session, struct MprVar); + mprSetCtx(web->session->data); + mprCopyVar(web->session->data, &esp->variables[ESP_SESSION_OBJ], MPR_DEEP_COPY); + + /* setup the timeout for the session data */ + talloc_free(web->session->te); + web->session->te = event_add_timed(web->conn->event.ctx, web->session, + timeval_current_ofs(web->session->lifetime, 0), + session_timeout, web->session); + } + talloc_free(esp); return; @@ -639,6 +827,9 @@ NTSTATUS http_parse_header(struct websrv_context *web, const char *line) PULL_HEADER(referer, "Referer: "); PULL_HEADER(host, "Host: "); PULL_HEADER(accept_encoding, "Accept-Encoding: "); + PULL_HEADER(accept_language, "Accept-Language: "); + PULL_HEADER(accept_charset, "Accept-Charset: "); + PULL_HEADER(cookie, "Cookie: "); } /* ignore all other headers for now */ @@ -646,3 +837,19 @@ NTSTATUS http_parse_header(struct websrv_context *web, const char *line) } +/* + setup the esp processor - called at task initialisation +*/ +NTSTATUS http_setup_esp(struct task_server *task) +{ + struct esp_data *edata; + + edata = talloc(task, struct esp_data); + NT_STATUS_HAVE_NO_MEMORY(edata); + + task->private = edata; + edata->sessions = NULL; + edata->application_data = NULL; + + return NT_STATUS_OK; +} diff --git a/source4/web_server/web_server.c b/source4/web_server/web_server.c index 656e101c11..9bbf423d29 100644 --- a/source4/web_server/web_server.c +++ b/source4/web_server/web_server.c @@ -169,11 +169,13 @@ static void websrv_send(struct stream_connection *conn, uint16_t flags) */ static void websrv_accept(struct stream_connection *conn) { + struct task_server *task = talloc_get_type(conn->private, struct task_server); struct websrv_context *web; web = talloc_zero(conn, struct websrv_context); if (web == NULL) goto failed; + web->task = task; web->conn = conn; conn->private = web; web->output.fd = -1; @@ -228,6 +230,11 @@ static void websrv_task_init(struct task_server *task) if (!NT_STATUS_IS_OK(status)) goto failed; } + /* startup the esp processor - unfortunately we can't do this + per connection as that wouldn't allow for session variables */ + status = http_setup_esp(task); + if (!NT_STATUS_IS_OK(status)) goto failed; + return; failed: diff --git a/source4/web_server/web_server.h b/source4/web_server/web_server.h index 0202c91ef7..f23ea90d55 100644 --- a/source4/web_server/web_server.h +++ b/source4/web_server/web_server.h @@ -25,6 +25,7 @@ context of one open web connection */ struct websrv_context { + struct task_server *task; struct stream_connection *conn; struct { DATA_BLOB partial; @@ -38,6 +39,10 @@ struct websrv_context { const char *referer; const char *host; const char *accept_encoding; + const char *accept_language; + const char *accept_charset; + const char *cookie; + const char *session_key; } input; struct { DATA_BLOB content; @@ -46,4 +51,7 @@ struct websrv_context { int response_code; const char **headers; } output; + struct session_data *session; }; + + -- cgit