1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "mod_session.h"
19 #include "apr_strings.h"
20 #include "util_filter.h"
22 #include "http_request.h"
23 #include "http_protocol.h"
25 #define SESSION_EXPIRY "expiry"
26 #define HTTP_SESSION "HTTP_SESSION"
29 APR_HOOK_LINK(session_load)
30 APR_HOOK_LINK(session_save)
31 APR_HOOK_LINK(session_encode)
32 APR_HOOK_LINK(session_decode)
34 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_load,
35 (request_rec * r, session_rec ** z), (r, z), DECLINED)
36 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_save,
37 (request_rec * r, session_rec * z), (r, z), DECLINED)
38 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_encode,
39 (request_rec * r, session_rec * z), (r, z), OK, DECLINED)
40 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_decode,
41 (request_rec * r, session_rec * z), (r, z), OK, DECLINED)
43 static int session_identity_encode(request_rec * r, session_rec * z);
44 static int session_identity_decode(request_rec * r, session_rec * z);
45 static int session_fixups(request_rec * r);
48 * Should the session be included within this URL.
50 * This function tests whether a session is valid for this URL. It uses the
51 * include and exclude arrays to determine whether they should be included.
53 static int session_included(request_rec * r, session_dir_conf * conf)
56 const char **includes = (const char **) conf->includes->elts;
57 const char **excludes = (const char **) conf->excludes->elts;
58 int included = 1; /* defaults to included */
61 if (conf->includes->nelts) {
63 for (i = 0; !included && i < conf->includes->nelts; i++) {
64 const char *include = includes[i];
65 if (strncmp(r->uri, include, strlen(include))) {
71 if (conf->excludes->nelts) {
72 for (i = 0; included && i < conf->includes->nelts; i++) {
73 const char *exclude = excludes[i];
74 if (strncmp(r->uri, exclude, strlen(exclude))) {
86 * If the session doesn't exist, a blank one will be created.
88 * @param r The request
89 * @param z A pointer to where the session will be written.
91 static apr_status_t ap_session_load(request_rec * r, session_rec ** z)
94 session_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
97 session_rec *zz = NULL;
100 /* is the session enabled? */
101 if (!dconf || !dconf->enabled) {
105 /* should the session be loaded at all? */
106 if (!session_included(r, dconf)) {
107 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01814)
108 "excluded by configuration for: %s", r->uri);
112 /* load the session from the session hook */
113 rv = ap_run_session_load(r, &zz);
114 if (DECLINED == rv) {
115 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01815)
116 "session is enabled but no session modules have been configured, "
117 "session not loaded: %s", r->uri);
121 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01816)
122 "error while loading the session, "
123 "session not loaded: %s", r->uri);
127 /* found a session that hasn't expired? */
128 now = apr_time_now();
129 if (!zz || (zz->expiry && zz->expiry < now)) {
131 /* no luck, create a blank session */
132 zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec));
134 zz->entries = apr_table_make(zz->pool, 10);
138 rv = ap_run_session_decode(r, zz);
140 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01817)
141 "error while decoding the session, "
142 "session not loaded: %s", r->uri);
147 /* make sure the expiry and maxage are set, if present */
150 zz->expiry = now + dconf->maxage * APR_USEC_PER_SEC;
152 zz->maxage = dconf->maxage;
164 * In most implementations the session is only saved if the dirty flag is
165 * true. This prevents the session being saved unnecessarily.
167 * @param r The request
168 * @param z A pointer to where the session will be written.
170 static apr_status_t ap_session_save(request_rec * r, session_rec * z)
173 apr_time_t now = apr_time_now();
176 session_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
179 /* sanity checks, should we try save at all? */
181 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01818)
182 "attempt made to save the session twice, "
183 "session not saved: %s", r->uri);
186 if (z->expiry && z->expiry < now) {
187 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01819)
188 "attempt made to save a session when the session had already expired, "
189 "session not saved: %s", r->uri);
193 /* reset the expiry back to maxage, if the expiry is present */
195 z->expiry = now + dconf->maxage * APR_USEC_PER_SEC;
196 z->maxage = dconf->maxage;
199 /* reset the expiry before saving if present */
200 if (z->dirty && z->maxage) {
201 z->expiry = now + z->maxage * APR_USEC_PER_SEC;
204 /* encode the session */
205 rv = ap_run_session_encode(r, z);
207 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01820)
208 "error while encoding the session, "
209 "session not saved: %s", r->uri);
214 rv = ap_run_session_save(r, z);
215 if (DECLINED == rv) {
216 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01821)
217 "session is enabled but no session modules have been configured, "
218 "session not saved: %s", r->uri);
222 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01822)
223 "error while saving the session, "
224 "session not saved: %s", r->uri);
237 * Get a particular value from the session.
238 * @param r The current request.
239 * @param z The current session. If this value is NULL, the session will be
240 * looked up in the request, created if necessary, and saved to the request
242 * @param key The key to get.
243 * @param value The buffer to write the value to.
245 static apr_status_t ap_session_get(request_rec * r, session_rec * z,
246 const char *key, const char **value)
250 rv = ap_session_load(r, &z);
251 if (APR_SUCCESS != rv) {
255 if (z && z->entries) {
256 *value = apr_table_get(z->entries, key);
263 * Set a particular value to the session.
265 * Using this method ensures that the dirty flag is set correctly, so that
266 * the session can be saved efficiently.
267 * @param r The current request.
268 * @param z The current session. If this value is NULL, the session will be
269 * looked up in the request, created if necessary, and saved to the request
271 * @param key The key to set. The existing key value will be replaced.
272 * @param value The value to set.
274 static apr_status_t ap_session_set(request_rec * r, session_rec * z,
275 const char *key, const char *value)
279 rv = ap_session_load(r, &z);
280 if (APR_SUCCESS != rv) {
286 apr_table_set(z->entries, key, value);
289 apr_table_unset(z->entries, key);
296 static int identity_count(int *count, const char *key, const char *val)
298 *count += strlen(key) * 3 + strlen(val) * 3 + 1;
302 static int identity_concat(char *buffer, const char *key, const char *val)
304 char *slider = buffer;
305 int length = strlen(slider);
311 ap_escape_urlencoded_buffer(slider, key);
312 slider += strlen(slider);
315 ap_escape_urlencoded_buffer(slider, val);
320 * Default identity encoding for the session.
322 * By default, the name value pairs in the session are URLEncoded, separated
323 * by equals, and then in turn separated by ampersand, in the format of an
326 * This was chosen to make it easy for external code to unpack a session,
327 * should there be a need to do so.
329 * @param r The request pointer.
330 * @param z A pointer to where the session will be written.
332 static apr_status_t session_identity_encode(request_rec * r, session_rec * z)
338 char *expiry = apr_psprintf(z->pool, "%" APR_INT64_T_FMT, z->expiry);
339 apr_table_setn(z->entries, SESSION_EXPIRY, expiry);
341 apr_table_do((int (*) (void *, const char *, const char *))
342 identity_count, &length, z->entries, NULL);
343 buffer = apr_pcalloc(r->pool, length + 1);
344 apr_table_do((int (*) (void *, const char *, const char *))
345 identity_concat, buffer, z->entries, NULL);
352 * Default identity decoding for the session.
354 * By default, the name value pairs in the session are URLEncoded, separated
355 * by equals, and then in turn separated by ampersand, in the format of an
358 * This was chosen to make it easy for external code to unpack a session,
359 * should there be a need to do so.
361 * This function reverses that process, and populates the session table.
363 * Name / value pairs that are not encoded properly are ignored.
365 * @param r The request pointer.
366 * @param z A pointer to where the session will be written.
368 static apr_status_t session_identity_decode(request_rec * r, session_rec * z)
372 char *encoded, *pair;
373 const char *sep = "&";
375 /* sanity check - anything to decode? */
380 /* decode what we have */
381 encoded = apr_pstrdup(r->pool, z->encoded);
382 pair = apr_strtok(encoded, sep, &last);
383 while (pair && pair[0]) {
385 const char *psep = "=";
386 char *key = apr_strtok(pair, psep, &plast);
387 char *val = apr_strtok(NULL, psep, &plast);
390 apr_table_unset(z->entries, key);
392 else if (!ap_unescape_urlencoded(key) && !ap_unescape_urlencoded(val)) {
393 if (!strcmp(SESSION_EXPIRY, key)) {
394 z->expiry = (apr_time_t) apr_atoi64(val);
397 apr_table_set(z->entries, key, val);
401 pair = apr_strtok(NULL, sep, &last);
409 * Ensure any changes to the session are committed.
411 * This is done in an output filter so that our options for where to
412 * store the session can include storing the session within a cookie:
413 * As an HTTP header, the cookie must be set before the output is
414 * written, but after the handler is run.
416 * NOTE: It is possible for internal redirects to cause more than one
417 * request to be present, and each request might have a session
418 * defined. We need to go through each session in turn, and save each
421 * The same session might appear in more than one request. The first
422 * attempt to save the session will be called
424 static apr_status_t session_output_filter(ap_filter_t * f,
425 apr_bucket_brigade * in)
428 /* save all the sessions in all the requests */
429 request_rec *r = f->r->main;
434 session_rec *z = NULL;
435 session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
438 /* load the session, or create one if necessary */
439 /* when unset or on error, z will be NULL */
440 ap_session_load(r, &z);
441 if (!z || z->written) {
446 /* if a header was specified, insert the new values from the header */
447 if (conf->header_set) {
448 const char *override = apr_table_get(r->err_headers_out, conf->header);
450 override = apr_table_get(r->headers_out, conf->header);
453 apr_table_unset(r->err_headers_out, conf->header);
454 apr_table_unset(r->headers_out, conf->header);
455 z->encoded = override;
457 session_identity_decode(r, z);
461 /* save away the session, and we're done */
462 /* when unset or on error, we've complained to the log */
463 ap_session_save(r, z);
468 /* remove ourselves from the filter chain */
469 ap_remove_output_filter(f);
471 /* send the data up the stack */
472 return ap_pass_brigade(f->next, in);
477 * Insert the output filter.
479 static void session_insert_output_filter(request_rec * r)
481 ap_add_output_filter("MOD_SESSION_OUT", NULL, r, r->connection);
487 * Load the session within a fixup - this ensures that the session is
488 * properly loaded prior to the handler being called.
490 * The fixup is also responsible for injecting the session into the CGI
491 * environment, should the admin have configured it so.
493 * @param r The request
495 static int session_fixups(request_rec * r)
497 session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
500 session_rec *z = NULL;
502 /* if an error occurs or no session has been configured, we ignore
503 * the broken session and allow it to be recreated from scratch on save
506 ap_session_load(r, &z);
508 if (z && conf->env) {
509 session_identity_encode(r, z);
511 apr_table_set(r->subprocess_env, HTTP_SESSION, z->encoded);
521 static void *create_session_dir_config(apr_pool_t * p, char *dummy)
523 session_dir_conf *new =
524 (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
526 new->includes = apr_array_make(p, 10, sizeof(const char **));
527 new->excludes = apr_array_make(p, 10, sizeof(const char **));
532 static void *merge_session_dir_config(apr_pool_t * p, void *basev, void *addv)
534 session_dir_conf *new = (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
535 session_dir_conf *add = (session_dir_conf *) addv;
536 session_dir_conf *base = (session_dir_conf *) basev;
538 new->enabled = (add->enabled_set == 0) ? base->enabled : add->enabled;
539 new->enabled_set = add->enabled_set || base->enabled_set;
540 new->maxage = (add->maxage_set == 0) ? base->maxage : add->maxage;
541 new->maxage_set = add->maxage_set || base->maxage_set;
542 new->header = (add->header_set == 0) ? base->header : add->header;
543 new->header_set = add->header_set || base->header_set;
544 new->env = (add->env_set == 0) ? base->env : add->env;
545 new->env_set = add->env_set || base->env_set;
546 new->includes = apr_array_append(p, base->includes, add->includes);
547 new->excludes = apr_array_append(p, base->excludes, add->excludes);
554 set_session_enable(cmd_parms * parms, void *dconf, int flag)
556 session_dir_conf *conf = dconf;
558 conf->enabled = flag;
559 conf->enabled_set = 1;
565 set_session_maxage(cmd_parms * parms, void *dconf, const char *arg)
567 session_dir_conf *conf = dconf;
569 conf->maxage = atol(arg);
570 conf->maxage_set = 1;
576 set_session_header(cmd_parms * parms, void *dconf, const char *arg)
578 session_dir_conf *conf = dconf;
581 conf->header_set = 1;
587 set_session_env(cmd_parms * parms, void *dconf, int flag)
589 session_dir_conf *conf = dconf;
597 static const char *add_session_include(cmd_parms * cmd, void *dconf, const char *f)
599 session_dir_conf *conf = dconf;
601 const char **new = apr_array_push(conf->includes);
607 static const char *add_session_exclude(cmd_parms * cmd, void *dconf, const char *f)
609 session_dir_conf *conf = dconf;
611 const char **new = apr_array_push(conf->excludes);
618 static const command_rec session_cmds[] =
620 AP_INIT_FLAG("Session", set_session_enable, NULL, RSRC_CONF|OR_AUTHCFG,
621 "on if a session should be maintained for these URLs"),
622 AP_INIT_TAKE1("SessionMaxAge", set_session_maxage, NULL, RSRC_CONF|OR_AUTHCFG,
623 "length of time for which a session should be valid. Zero to disable"),
624 AP_INIT_TAKE1("SessionHeader", set_session_header, NULL, RSRC_CONF|OR_AUTHCFG,
625 "output header, if present, whose contents will be injected into the session."),
626 AP_INIT_FLAG("SessionEnv", set_session_env, NULL, RSRC_CONF|OR_AUTHCFG,
627 "on if a session should be written to the CGI environment. Defaults to off"),
628 AP_INIT_TAKE1("SessionInclude", add_session_include, NULL, RSRC_CONF|OR_AUTHCFG,
629 "URL prefixes to include in the session. Defaults to all URLs"),
630 AP_INIT_TAKE1("SessionExclude", add_session_exclude, NULL, RSRC_CONF|OR_AUTHCFG,
631 "URL prefixes to exclude from the session. Defaults to no URLs"),
635 static void register_hooks(apr_pool_t * p)
637 ap_register_output_filter("MOD_SESSION_OUT", session_output_filter,
638 NULL, AP_FTYPE_CONTENT_SET);
639 ap_hook_insert_filter(session_insert_output_filter, NULL, NULL,
641 ap_hook_insert_error_filter(session_insert_output_filter,
642 NULL, NULL, APR_HOOK_MIDDLE);
643 ap_hook_fixups(session_fixups, NULL, NULL, APR_HOOK_MIDDLE);
644 ap_hook_session_encode(session_identity_encode, NULL, NULL,
645 APR_HOOK_REALLY_FIRST);
646 ap_hook_session_decode(session_identity_decode, NULL, NULL,
647 APR_HOOK_REALLY_LAST);
648 APR_REGISTER_OPTIONAL_FN(ap_session_get);
649 APR_REGISTER_OPTIONAL_FN(ap_session_set);
650 APR_REGISTER_OPTIONAL_FN(ap_session_load);
651 APR_REGISTER_OPTIONAL_FN(ap_session_save);
654 AP_DECLARE_MODULE(session) =
656 STANDARD20_MODULE_STUFF,
657 create_session_dir_config, /* dir config creater */
658 merge_session_dir_config, /* dir merger --- default is to override */
659 NULL, /* server config */
660 NULL, /* merge server config */
661 session_cmds, /* command apr_table_t */
662 register_hooks /* register hooks */