On 04/04/2008 05:58 PM, [EMAIL PROTECTED] wrote:
Author: minfrin
Date: Fri Apr  4 08:58:15 2008
New Revision: 644746

URL: http://svn.apache.org/viewvc?rev=644746&view=rev
Log:
mod_session: Add a generic session interface to unify the different
attempts at saving persistent sessions across requests.

Added:
    httpd/httpd/trunk/docs/manual/mod/mod_session.xml
    httpd/httpd/trunk/modules/session/   (with props)
    httpd/httpd/trunk/modules/session/Makefile.in
    httpd/httpd/trunk/modules/session/config.m4
    httpd/httpd/trunk/modules/session/mod_session.c
    httpd/httpd/trunk/modules/session/mod_session.h
Modified:
    httpd/httpd/trunk/CHANGES
    httpd/httpd/trunk/configure.in
    httpd/httpd/trunk/include/httpd.h
    httpd/httpd/trunk/server/util.c


Modified: httpd/httpd/trunk/include/httpd.h
URL: 
http://svn.apache.org/viewvc/httpd/httpd/trunk/include/httpd.h?rev=644746&r1=644745&r2=644746&view=diff
==============================================================================
--- httpd/httpd/trunk/include/httpd.h (original)
+++ httpd/httpd/trunk/include/httpd.h Fri Apr  4 08:58:15 2008
@@ -1435,6 +1435,13 @@
 AP_DECLARE(int) ap_is_url(const char *u);
/**
+ * Unescape a string
+ * @param url The string to unescape
+ * @return 0 on success, non-zero otherwise
+ */
+AP_DECLARE(int) ap_unescape_all(char *url);
+
+/**

Doesn't this require a minor bump?

  * Unescape a URL
  * @param url The url to unescape
  * @return 0 on success, non-zero otherwise
@@ -1468,6 +1475,14 @@
  * @return The converted URL
  */
 AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *s);
+
+/**
+ * Escape a path segment, as defined in RFC 1808, to a preallocated buffer.
+ * @param c The preallocated buffer to write to
+ * @param s The path to convert
+ * @return The converted URL (c)
+ */
+AP_DECLARE(char *) ap_escape_path_segment_b(char *c, const char *s);

This is not a very descriptive name. Shouldn't it be better 
ap_escape_path_segment_buffer?


Added: httpd/httpd/trunk/modules/session/mod_session.c
URL: 
http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/session/mod_session.c?rev=644746&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/session/mod_session.c (added)
+++ httpd/httpd/trunk/modules/session/mod_session.c Fri Apr  4 08:58:15 2008

+/**
+ * Should the session be included within this URL.
+ *
+ * This function tests whether a session is valid for this URL. It uses the
+ * include and exclude arrays to determine whether they should be included.
+ */
+    static int session_included(request_rec * r, session_dir_conf * conf)
+{
+
+    const char **includes = (const char **) conf->includes->elts;
+    const char **excludes = (const char **) conf->excludes->elts;
+    int included = 1;                /* defaults to included */
+    int i;
+
+    if (conf->includes->nelts) {
+        included = 0;
+        for (i = 0; !included && i < conf->includes->nelts; i++) {
+            const char *include = includes[i];
+            if (strncmp(r->parsed_uri.path, include, strlen(include))) {
+                included = 1;
+            }
+        }
+    }
+
+    if (conf->excludes->nelts) {
+        for (i = 0; included && i < conf->includes->nelts; i++) {
+            const char *exclude = excludes[i];
+            if (strncmp(r->parsed_uri.path, exclude, strlen(exclude))) {
+                included = 0;
+            }
+        }
+    }
+
+    return included;
+}
+
+/**
+ * Get a particular value from the session.
+ * @param r The current request.
+ * @param z The current session. If this value is NULL, the session will be
+ * looked up in the request, created if necessary, and saved to the request
+ * notes.
+ * @param key The key to get.
+ * @param value The buffer to write the value to.
+ */
+AP_DECLARE(void) ap_session_get(request_rec * r, session_rec * z, const char 
*key, const char **value)
+{
+    if (!z) {
+        ap_session_load(r, &z);

Not checking the return value can lead to segfaults as z can be invalid.

+    }
+    *value = apr_table_get(z->entries, key);
+}
+
+/**
+ * Set a particular value to the session.
+ *
+ * Using this method ensures that the dirty flag is set correctly, so that
+ * the session can be saved efficiently.
+ * @param r The current request.
+ * @param z The current session. If this value is NULL, the session will be
+ * looked up in the request, created if necessary, and saved to the request
+ * notes.
+ * @param key The key to set. The existing key value will be replaced.
+ * @param value The value to set.
+ */
+AP_DECLARE(void) ap_session_set(request_rec * r, session_rec * z,
+                                const char *key, const char *value)
+{
+    if (!z) {
+        ap_session_load(r, &z);

Not checking the return value can lead to segfaults as z can be invalid.

+    }
+    if (value) {
+        apr_table_set(z->entries, key, value);
+    }
+    else {
+        apr_table_unset(z->entries, key);
+    }
+    z->dirty = 1;
+}
+
+/**
+ * Load the session.
+ *
+ * If the session doesn't exist, a blank one will be created.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE(int) ap_session_load(request_rec * r, session_rec ** z)
+{
+
+    session_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
+                                                   &session_module);
+    apr_time_t now;
+    session_rec *zz = NULL;

*z should be set to NULL to produce at least clean segfaults if a caller of
this function does not check the return code.

+
+    /* is the session enabled? */
+    if (!dconf->enabled) {
+        return APR_SUCCESS;
+    }
+
+    /* should the session be loaded at all? */
+    if (!session_included(r, dconf)) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, SESSION_PREFIX
+                      "excluded by configuration for: %s", r->uri);
+        return APR_SUCCESS;
+    }
+
+    /* load the session from the session hook */
+    int rv = ap_run_session_load(r, &zz);
+    if (DECLINED == rv) {
+        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX
+                      "session is enabled but no session modules have been 
configured, "
+                      "session not loaded: %s", r->uri);
+        return APR_EGENERAL;
+    }
+    else if (OK != rv) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX
+                      "error while loading the session, "
+                      "session not loaded: %s", r->uri);
+        return rv;
+    }
+
+    /* found a session that hasn't expired? */
+    now = apr_time_now();
+    if (!zz || (zz->expiry && zz->expiry < now)) {
+
+        /* no luck, create a blank session */
+        zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec));
+        zz->pool = r->pool;
+        zz->entries = apr_table_make(zz->pool, 10);
+        zz->uuid = (apr_uuid_t *) apr_pcalloc(zz->pool, sizeof(apr_uuid_t));
+        apr_uuid_get(zz->uuid);
+
+    }
+    else {
+        rv = ap_run_session_decode(r, zz);
+        if (OK != rv) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX
+                          "error while decoding the session, "
+                          "session not loaded: %s", r->uri);
+            return rv;
+        }
+    }
+
+    /* make sure the expiry is set, if present */
+    if (!zz->expiry && dconf->maxage) {
+        zz->expiry = now + dconf->maxage * APR_USEC_PER_SEC;
+        zz->maxage = dconf->maxage;
+    }
+
+    *z = zz;
+
+    return APR_SUCCESS;
+
+}
+
+/**
+ * Save the session.
+ *
+ * In most implementations the session is only saved if the dirty flag is
+ * true. This prevents the session being saved unnecessarily.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE(int) ap_session_save(request_rec * r, session_rec * z)
+{
+    if (z) {
+        apr_time_t now = apr_time_now();
+        int rv = 0;
+
+        /* sanity checks, should we try save at all? */
+        if (z->written) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, SESSION_PREFIX
+                          "attempt made to save the session twice, "
+                          "session not saved: %s", r->uri);
+            return APR_EGENERAL;
+        }
+        if (z->expiry && z->expiry < now) {
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX
+                          "attempt made to save a session when the session had 
already expired, "
+                          "session not saved: %s", r->uri);
+            return APR_EGENERAL;
+        }
+
+        /* encode the session */
+        rv = ap_run_session_encode(r, z);
+        if (OK != rv) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX
+                          "error while encoding the session, "
+                          "session not saved: %s", r->uri);
+            return rv;
+        }
+
+        /* try the save */
+        rv = ap_run_session_save(r, z);
+        if (DECLINED == rv) {
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX
+                          "session is enabled but no session modules have been 
configured, "
+                          "session not saved: %s", r->uri);
+            return APR_EGENERAL;
+        }
+        else if (OK != rv) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX
+                          "error while saving the session, "
+                          "session not saved: %s", r->uri);
+            return rv;
+        }
+        else {
+            z->written = 1;
+        }
+    }
+
+    return APR_SUCCESS;
+
+}
+
+static int identity_count(int *count, const char *key, const char *val)
+{
+    *count += strlen(key) * 3 + strlen(val) * 3 + 1;
+    return 1;
+}
+
+static int identity_concat(char *buffer, const char *key, const char *val)
+{
+    char *slider = buffer;
+    int length = strlen(slider);
+    slider += length;
+    if (length) {
+        *slider = '&';
+        slider++;
+    }
+    ap_escape_path_segment_b(slider, key);
+    slider += strlen(slider);
+    *slider = '=';
+    slider++;
+    ap_escape_path_segment_b(slider, val);
+    return 1;
+}
+
+/**
+ * Default identity encoding for the session.
+ *
+ * By default, the name value pairs in the session are URLEncoded, separated
+ * by equals, and then in turn separated by ampersand, in the format of an
+ * html form.
+ *
+ * This was chosen to make it easy for external code to unpack a session,
+ * should there be a need to do so.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE(int) ap_session_identity_encode(request_rec * r, session_rec * z)
+{
+
+    char *buffer = NULL;
+    int length = 0;
+    if (z->expiry) {
+        char *expiry = apr_psprintf(r->pool, "%" APR_INT64_T_FMT, z->expiry);
+        apr_table_set(z->entries, SESSION_EXPIRY, expiry);
+    }
+    apr_table_do((int (*) (void *, const char *, const char *))
+                 identity_count, &length, z->entries, NULL);;
+    buffer = apr_pcalloc(r->pool, length + 1);
+    apr_table_do((int (*) (void *, const char *, const char *))
+                 identity_concat, buffer, z->entries, NULL);
+    z->encoded = buffer;
+    return OK;
+
+}
+
+/**
+ * Default identity decoding for the session.
+ *
+ * By default, the name value pairs in the session are URLEncoded, separated
+ * by equals, and then in turn separated by ampersand, in the format of an
+ * html form.
+ *
+ * This was chosen to make it easy for external code to unpack a session,
+ * should there be a need to do so.
+ *
+ * This function reverses that process, and populates the session table.
+ *
+ * Name / value pairs that are not encoded properly are ignored.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE(int) ap_session_identity_decode(request_rec * r, session_rec * z)
+{
+
+    char *last = NULL;
+    char *encoded, *pair;
+    const char *sep = "&";
+
+    /* sanity check - anything to decode? */
+    if (!z->encoded) {
+        return OK;
+    }
+
+    /* decode what we have */
+    encoded = apr_pstrcat(r->pool, z->encoded, NULL);

What is the purpose of this? Did you mean apr_pstrdup? And why
doing a copy at all if you set z->encoded to NULL later anyway?

+    pair = apr_strtok(encoded, sep, &last);
+    while (pair && pair[0]) {
+        char *plast = NULL;
+        const char *psep = "=";
+        char *key = apr_strtok(pair, psep, &plast);
+        char *val = apr_strtok(NULL, psep, &plast);
+        if (key && *key) {
+            if (!val || !*val) {
+                apr_table_unset(z->entries, key);
+            }
+            if (!ap_unescape_all(key) && !ap_unescape_all(val)) {
+                if (!strcmp(SESSION_EXPIRY, key)) {
+                    z->expiry = (apr_time_t) apr_atoi64(val);
+                }
+                else {
+                    apr_table_set(z->entries, key, val);
+                }
+            }
+        }
+        pair = apr_strtok(NULL, sep, &last);
+    }
+    z->encoded = NULL;
+    return OK;
+
+}
+
+/**
+ * Ensure any changes to the session are committed.
+ *
+ * This is done in an output filter so that our options for where to
+ * store the session can include storing the session within a cookie:
+ * As an HTTP header, the cookie must be set before the output is
+ * written, but after the handler is run.
+ *
+ * NOTE: It is possible for internal redirects to cause more than one
+ * request to be present, and each request might have a session
+ * defined. We need to go through each session in turn, and save each
+ * one.
+ * + * The same session might appear in more than one request. The first
+ * attempt to save the session will be called
+ */
+static apr_status_t ap_session_output_filter(ap_filter_t * f,
+                                                    apr_bucket_brigade * in)
+{
+
+    /* save all the sessions in all the requests */
+    request_rec *r = f->r->main;
+    if (!r) {
+        r = f->r;
+    }
+    while (r) {
+        session_rec *z = NULL;
+        session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+                                                      &session_module);
+
+        /* load the session, or create one if necessary */
+        ap_session_load(r, &z);
+        if (!z || z->written) {
+            r = r->next;
+            continue;
+        }
+
+        /* if a header was specified, insert the new values from the header */
+        if (conf->header_set) {
+            const char *override = apr_table_get(r->err_headers_out, 
conf->header);
+            if (!override) {
+                override = apr_table_get(r->headers_out, conf->header);
+            }
+            if (override) {
+                z->encoded = override;
+                ap_session_identity_decode(r, z);
+            }
+        }
+
+        /* save away the session, and we're done */
+        ap_session_save(r, z);
+
+        r = r->next;
+    }
+
+    /* remove ourselves from the filter chain */
+    ap_remove_output_filter(f);
+
+    /* send the data up the stack */
+    return ap_pass_brigade(f->next, in);
+
+}
+
+/**
+ * Insert the output filter.
+ */
+static void ap_session_insert_output_filter(request_rec * r)
+{
+    ap_add_output_filter("MOD_SESSION_OUT", NULL, r, r->connection);
+}
+
+/**
+ * Fixups hook.
+ *
+ * Load the session within a fixup - this ensures that the session is
+ * properly loaded prior to the handler being called.
+ * + * The fixup is also responsible for injecting the session into the CGI
+ * environment, should the admin have configured it so.
+ * + * @param r The request
+ */
+AP_DECLARE(int) ap_session_fixups(request_rec * r)
+{
+    session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+                                                  &session_module);
+
+    session_rec *z = NULL;
+    ap_session_load(r, &z);
+
+    if (conf->env) {
+        ap_session_identity_encode(r, z);
+        if (z->encoded) {
+            apr_table_set(r->subprocess_env, HTTP_SESSION, z->encoded);
+            z->encoded = NULL;
+        }
+    }
+
+    return OK;
+
+}
+
+
+static void *create_session_dir_config(apr_pool_t * p, char *dummy)
+{
+    session_dir_conf *new =
+    (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
+
+    new->includes = apr_array_make(p, 10, sizeof(const char **));
+    new->excludes = apr_array_make(p, 10, sizeof(const char **));

Not that it really matters, but aren't the elements of the arrays char * 
instead of char **?

+
+    return (void *) new;
+}
+
+static void *merge_session_dir_config(apr_pool_t * p, void *basev, void *addv)
+{


Added: httpd/httpd/trunk/modules/session/mod_session.h
URL: 
http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/session/mod_session.h?rev=644746&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/session/mod_session.h (added)
+++ httpd/httpd/trunk/modules/session/mod_session.h Fri Apr  4 08:58:15 2008

+/**
+ * Get a particular value from the session.
+ * @param r The current request.
+ * @param z The current session. If this value is NULL, the session will be
+ * looked up in the request, created if necessary, and saved to the request
+ * notes.
+ * @param key The key to get.
+ * @param value The buffer to write the value to.
+ */
+AP_DECLARE(void) ap_session_get(request_rec * r, session_rec * z, const char 
*key, const char **value);
+
+/**
+ * Set a particular value to the session.
+ *
+ * Using this method ensures that the dirty flag is set correctly, so that
+ * the session can be saved efficiently.
+ * @param r The current request.
+ * @param z The current session. If this value is NULL, the session will be
+ * looked up in the request, created if necessary, and saved to the request
+ * notes.
+ * @param key The key to set. The existing key value will be replaced.
+ * @param value The value to set.
+ */
+AP_DECLARE(void) ap_session_set(request_rec * r, session_rec * z,
+                                const char *key, const char *value);
+
+/**
+ * Load the session.
+ *
+ * If the session doesn't exist, a blank one will be created.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE(int) ap_session_load(request_rec * r, session_rec ** z);
+
+/**
+ * Hook to load the session.
+ *
+ * If the session doesn't exist, a blank one will be created.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE_HOOK(int, session_load, (request_rec * r, session_rec ** z))

As others already said, from my POV a provider be better here than a hook.

+
+/**
+ * Save the session.
+ *
+ * In most implementations the session is only saved if the dirty flag is
+ * true. This prevents the session being saved unnecessarily.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE(int) ap_session_save(request_rec * r, session_rec * z);
+
+/**
+ * Hook to save the session.
+ *
+ * In most implementations the session is only saved if the dirty flag is
+ * true. This prevents the session being saved unnecessarily.
+ *
+ * @param r The request
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE_HOOK(int, session_save, (request_rec * r, session_rec * z))

As others already said, from my POV a provider be better here than a hook.

Regards

Rüdiger

Reply via email to