r6998: - added support for application[] data, which is global to all clients using...
authorAndrew Tridgell <tridge@samba.org>
Fri, 27 May 2005 00:29:58 +0000 (00:29 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 18:17:03 +0000 (13:17 -0500)
  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.

source/web_server/ejs/miniMpr.c
source/web_server/esp/esp.h
source/web_server/http.c
source/web_server/web_server.c
source/web_server/web_server.h

index 38241e5c7125c4f717849fa9fcdb4ed72a3439e2..46d9579c7e9f7204b51ff9fdd2b5701487bbcca0 100644 (file)
 
 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)
index 5d343db96ef829e465fc2f3a845bd6cc7b701409..9e58bdf066fd0f508ad376c276cdad77a485c394 100644 (file)
@@ -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);
index 25595a8ad7b7b5a3dc0825c98f5daad4803d0aa7..5dd9e35edd13377dabbf851c99dbab5566eb2e65 100644 (file)
@@ -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"
 #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<p>%s<p>\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;i<ARRAY_SIZE(esp->variables);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 && i<ARRAY_SIZE(mime_types);i++) {
                if (strcmp(mime_types[i].extension, p+1) == 0) {
                        file_type = mime_types[i].mime_type;
+                       esp_enable = mime_types[i].esp_enable;
                }
        }
        if (file_type == NULL) {
@@ -596,11 +763,32 @@ void http_process_input(struct websrv_context *web)
        http_setHeader(web, "Connection: close", 0);
        http_setHeader(web, talloc_asprintf(esp, "Content-Type: %s", file_type), 0);
 
-       if (strcmp(file_type, "text/html") == 0) {
+       if (esp_enable) {
                esp_request(esp);
        } else {
                http_simple_request(web);
        }
+
+       /* copy any application data to long term storage in edata */
+       talloc_free(edata->application_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;
+}
index 656e101c11c38dc12d5902a74e46d946eed71e46..9bbf423d29fcf802e72d5f5a7def6b14aa6e64b1 100644 (file)
@@ -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:
index 0202c91ef7e1eacdcacf9f8ce1de137b5e1247b3..f23ea90d551a5fd3ba1ea187af05bc408ac436d0 100644 (file)
@@ -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;
 };
+
+