switch to a 60 bit hash
[httpd-crcsyncproxy.git] / modules / session / mod_session.c
blob8553d6a42c8435d0c963a222a446075624527851
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"
18 #include "apr_lib.h"
19 #include "apr_strings.h"
20 #include "util_filter.h"
21 #include "http_log.h"
22 #include "http_request.h"
23 #include "http_protocol.h"
25 #define SESSION_PREFIX "mod_session: "
26 #define SESSION_EXPIRY "expiry"
27 #define HTTP_SESSION "HTTP_SESSION"
29 APR_HOOK_STRUCT(
30 APR_HOOK_LINK(session_load)
31 APR_HOOK_LINK(session_save)
32 APR_HOOK_LINK(session_encode)
33 APR_HOOK_LINK(session_decode)
35 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_load,
36 (request_rec * r, session_rec ** z), (r, z), DECLINED)
37 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_save,
38 (request_rec * r, session_rec * z), (r, z), DECLINED)
39 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_encode,
40 (request_rec * r, session_rec * z), (r, z), OK, DECLINED)
41 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_decode,
42 (request_rec * r, session_rec * z), (r, z), OK, DECLINED)
44 static int session_identity_encode(request_rec * r, session_rec * z);
45 static int session_identity_decode(request_rec * r, session_rec * z);
46 static int session_fixups(request_rec * r);
48 /**
49 * Should the session be included within this URL.
51 * This function tests whether a session is valid for this URL. It uses the
52 * include and exclude arrays to determine whether they should be included.
54 static int session_included(request_rec * r, session_dir_conf * conf)
57 const char **includes = (const char **) conf->includes->elts;
58 const char **excludes = (const char **) conf->excludes->elts;
59 int included = 1; /* defaults to included */
60 int i;
62 if (conf->includes->nelts) {
63 included = 0;
64 for (i = 0; !included && i < conf->includes->nelts; i++) {
65 const char *include = includes[i];
66 if (strncmp(r->parsed_uri.path, include, strlen(include))) {
67 included = 1;
72 if (conf->excludes->nelts) {
73 for (i = 0; included && i < conf->includes->nelts; i++) {
74 const char *exclude = excludes[i];
75 if (strncmp(r->parsed_uri.path, exclude, strlen(exclude))) {
76 included = 0;
81 return included;
84 /**
85 * Load the session.
87 * If the session doesn't exist, a blank one will be created.
89 * @param r The request
90 * @param z A pointer to where the session will be written.
92 static int ap_session_load(request_rec * r, session_rec ** z)
95 session_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
96 &session_module);
97 apr_time_t now;
98 session_rec *zz = NULL;
99 int rv = 0;
101 /* is the session enabled? */
102 if (!dconf->enabled) {
103 return APR_SUCCESS;
106 /* should the session be loaded at all? */
107 if (!session_included(r, dconf)) {
108 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, SESSION_PREFIX
109 "excluded by configuration for: %s", r->uri);
110 return APR_SUCCESS;
113 /* load the session from the session hook */
114 rv = ap_run_session_load(r, &zz);
115 if (DECLINED == rv) {
116 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX
117 "session is enabled but no session modules have been configured, "
118 "session not loaded: %s", r->uri);
119 return APR_EGENERAL;
121 else if (OK != rv) {
122 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX
123 "error while loading the session, "
124 "session not loaded: %s", r->uri);
125 return rv;
128 /* found a session that hasn't expired? */
129 now = apr_time_now();
130 if (!zz || (zz->expiry && zz->expiry < now)) {
132 /* no luck, create a blank session */
133 zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec));
134 zz->pool = r->pool;
135 zz->entries = apr_table_make(zz->pool, 10);
136 zz->uuid = (apr_uuid_t *) apr_pcalloc(zz->pool, sizeof(apr_uuid_t));
137 apr_uuid_get(zz->uuid);
140 else {
141 rv = ap_run_session_decode(r, zz);
142 if (OK != rv) {
143 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX
144 "error while decoding the session, "
145 "session not loaded: %s", r->uri);
146 return rv;
150 /* make sure the expiry is set, if present */
151 if (!zz->expiry && dconf->maxage) {
152 zz->expiry = now + dconf->maxage * APR_USEC_PER_SEC;
153 zz->maxage = dconf->maxage;
156 *z = zz;
158 return APR_SUCCESS;
163 * Save the session.
165 * In most implementations the session is only saved if the dirty flag is
166 * true. This prevents the session being saved unnecessarily.
168 * @param r The request
169 * @param z A pointer to where the session will be written.
171 static int ap_session_save(request_rec * r, session_rec * z)
173 if (z) {
174 apr_time_t now = apr_time_now();
175 int rv = 0;
177 /* sanity checks, should we try save at all? */
178 if (z->written) {
179 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, SESSION_PREFIX
180 "attempt made to save the session twice, "
181 "session not saved: %s", r->uri);
182 return APR_EGENERAL;
184 if (z->expiry && z->expiry < now) {
185 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX
186 "attempt made to save a session when the session had already expired, "
187 "session not saved: %s", r->uri);
188 return APR_EGENERAL;
191 /* encode the session */
192 rv = ap_run_session_encode(r, z);
193 if (OK != rv) {
194 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX
195 "error while encoding the session, "
196 "session not saved: %s", r->uri);
197 return rv;
200 /* try the save */
201 rv = ap_run_session_save(r, z);
202 if (DECLINED == rv) {
203 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, SESSION_PREFIX
204 "session is enabled but no session modules have been configured, "
205 "session not saved: %s", r->uri);
206 return APR_EGENERAL;
208 else if (OK != rv) {
209 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, SESSION_PREFIX
210 "error while saving the session, "
211 "session not saved: %s", r->uri);
212 return rv;
214 else {
215 z->written = 1;
219 return APR_SUCCESS;
224 * Get a particular value from the session.
225 * @param r The current request.
226 * @param z The current session. If this value is NULL, the session will be
227 * looked up in the request, created if necessary, and saved to the request
228 * notes.
229 * @param key The key to get.
230 * @param value The buffer to write the value to.
232 static void ap_session_get(request_rec * r, session_rec * z, const char *key, const char **value)
234 if (!z) {
235 ap_session_load(r, &z);
237 if (z && z->entries) {
238 *value = apr_table_get(z->entries, key);
243 * Set a particular value to the session.
245 * Using this method ensures that the dirty flag is set correctly, so that
246 * the session can be saved efficiently.
247 * @param r The current request.
248 * @param z The current session. If this value is NULL, the session will be
249 * looked up in the request, created if necessary, and saved to the request
250 * notes.
251 * @param key The key to set. The existing key value will be replaced.
252 * @param value The value to set.
254 static void ap_session_set(request_rec * r, session_rec * z,
255 const char *key, const char *value)
257 if (!z) {
258 ap_session_load(r, &z);
260 if (z) {
261 if (value) {
262 apr_table_set(z->entries, key, value);
264 else {
265 apr_table_unset(z->entries, key);
267 z->dirty = 1;
271 static int identity_count(int *count, const char *key, const char *val)
273 *count += strlen(key) * 3 + strlen(val) * 3 + 1;
274 return 1;
277 static int identity_concat(char *buffer, const char *key, const char *val)
279 char *slider = buffer;
280 int length = strlen(slider);
281 slider += length;
282 if (length) {
283 *slider = '&';
284 slider++;
286 ap_escape_path_segment_buffer(slider, key);
287 slider += strlen(slider);
288 *slider = '=';
289 slider++;
290 ap_escape_path_segment_buffer(slider, val);
291 return 1;
295 * Default identity encoding for the session.
297 * By default, the name value pairs in the session are URLEncoded, separated
298 * by equals, and then in turn separated by ampersand, in the format of an
299 * html form.
301 * This was chosen to make it easy for external code to unpack a session,
302 * should there be a need to do so.
304 * @param r The request pointer.
305 * @param z A pointer to where the session will be written.
307 static int session_identity_encode(request_rec * r, session_rec * z)
310 char *buffer = NULL;
311 int length = 0;
312 if (z->expiry) {
313 char *expiry = apr_psprintf(r->pool, "%" APR_INT64_T_FMT, z->expiry);
314 apr_table_set(z->entries, SESSION_EXPIRY, expiry);
316 apr_table_do((int (*) (void *, const char *, const char *))
317 identity_count, &length, z->entries, NULL);;
318 buffer = apr_pcalloc(r->pool, length + 1);
319 apr_table_do((int (*) (void *, const char *, const char *))
320 identity_concat, buffer, z->entries, NULL);
321 z->encoded = buffer;
322 return OK;
327 * Default identity decoding for the session.
329 * By default, the name value pairs in the session are URLEncoded, separated
330 * by equals, and then in turn separated by ampersand, in the format of an
331 * html form.
333 * This was chosen to make it easy for external code to unpack a session,
334 * should there be a need to do so.
336 * This function reverses that process, and populates the session table.
338 * Name / value pairs that are not encoded properly are ignored.
340 * @param r The request pointer.
341 * @param z A pointer to where the session will be written.
343 static int session_identity_decode(request_rec * r, session_rec * z)
346 char *last = NULL;
347 char *encoded, *pair;
348 const char *sep = "&";
350 /* sanity check - anything to decode? */
351 if (!z->encoded) {
352 return OK;
355 /* decode what we have */
356 encoded = apr_pstrcat(r->pool, z->encoded, NULL);
357 pair = apr_strtok(encoded, sep, &last);
358 while (pair && pair[0]) {
359 char *plast = NULL;
360 const char *psep = "=";
361 char *key = apr_strtok(pair, psep, &plast);
362 char *val = apr_strtok(NULL, psep, &plast);
363 if (key && *key) {
364 if (!val || !*val) {
365 apr_table_unset(z->entries, key);
367 else if (!ap_unescape_all(key) && !ap_unescape_all(val)) {
368 if (!strcmp(SESSION_EXPIRY, key)) {
369 z->expiry = (apr_time_t) apr_atoi64(val);
371 else {
372 apr_table_set(z->entries, key, val);
376 pair = apr_strtok(NULL, sep, &last);
378 z->encoded = NULL;
379 return OK;
384 * Ensure any changes to the session are committed.
386 * This is done in an output filter so that our options for where to
387 * store the session can include storing the session within a cookie:
388 * As an HTTP header, the cookie must be set before the output is
389 * written, but after the handler is run.
391 * NOTE: It is possible for internal redirects to cause more than one
392 * request to be present, and each request might have a session
393 * defined. We need to go through each session in turn, and save each
394 * one.
396 * The same session might appear in more than one request. The first
397 * attempt to save the session will be called
399 static apr_status_t session_output_filter(ap_filter_t * f,
400 apr_bucket_brigade * in)
403 /* save all the sessions in all the requests */
404 request_rec *r = f->r->main;
405 if (!r) {
406 r = f->r;
408 while (r) {
409 session_rec *z = NULL;
410 session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
411 &session_module);
413 /* load the session, or create one if necessary */
414 ap_session_load(r, &z);
415 if (!z || z->written) {
416 r = r->next;
417 continue;
420 /* if a header was specified, insert the new values from the header */
421 if (conf->header_set) {
422 const char *override = apr_table_get(r->err_headers_out, conf->header);
423 if (!override) {
424 override = apr_table_get(r->headers_out, conf->header);
426 if (override) {
427 z->encoded = override;
428 session_identity_decode(r, z);
432 /* save away the session, and we're done */
433 ap_session_save(r, z);
435 r = r->next;
438 /* remove ourselves from the filter chain */
439 ap_remove_output_filter(f);
441 /* send the data up the stack */
442 return ap_pass_brigade(f->next, in);
447 * Insert the output filter.
449 static void session_insert_output_filter(request_rec * r)
451 ap_add_output_filter("MOD_SESSION_OUT", NULL, r, r->connection);
455 * Fixups hook.
457 * Load the session within a fixup - this ensures that the session is
458 * properly loaded prior to the handler being called.
460 * The fixup is also responsible for injecting the session into the CGI
461 * environment, should the admin have configured it so.
463 * @param r The request
465 static int session_fixups(request_rec * r)
467 session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
468 &session_module);
470 session_rec *z = NULL;
471 ap_session_load(r, &z);
473 if (conf->env) {
474 session_identity_encode(r, z);
475 if (z->encoded) {
476 apr_table_set(r->subprocess_env, HTTP_SESSION, z->encoded);
477 z->encoded = NULL;
481 return OK;
486 static void *create_session_dir_config(apr_pool_t * p, char *dummy)
488 session_dir_conf *new =
489 (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
491 new->includes = apr_array_make(p, 10, sizeof(const char **));
492 new->excludes = apr_array_make(p, 10, sizeof(const char **));
494 return (void *) new;
497 static void *merge_session_dir_config(apr_pool_t * p, void *basev, void *addv)
499 session_dir_conf *new = (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
500 session_dir_conf *add = (session_dir_conf *) addv;
501 session_dir_conf *base = (session_dir_conf *) basev;
503 new->enabled = (add->enabled_set == 0) ? base->enabled : add->enabled;
504 new->enabled_set = add->enabled_set || base->enabled_set;
505 new->maxage = (add->maxage_set == 0) ? base->maxage : add->maxage;
506 new->maxage_set = add->maxage_set || base->maxage_set;
507 new->header = (add->header_set == 0) ? base->header : add->header;
508 new->header_set = add->header_set || base->header_set;
509 new->env = (add->env_set == 0) ? base->env : add->env;
510 new->env_set = add->env_set || base->env_set;
511 new->includes = apr_array_append(p, base->includes, add->includes);
512 new->excludes = apr_array_append(p, base->excludes, add->excludes);
514 return new;
518 static const char *
519 set_session_enable(cmd_parms * parms, void *dconf, int flag)
521 session_dir_conf *conf = dconf;
523 conf->enabled = flag;
524 conf->enabled_set = 1;
526 return NULL;
529 static const char *
530 set_session_maxage(cmd_parms * parms, void *dconf, const char *arg)
532 session_dir_conf *conf = dconf;
534 conf->maxage = atol(arg);
535 conf->maxage_set = 1;
537 return NULL;
540 static const char *
541 set_session_header(cmd_parms * parms, void *dconf, const char *arg)
543 session_dir_conf *conf = dconf;
545 conf->header = arg;
546 conf->header_set = 1;
548 return NULL;
551 static const char *
552 set_session_env(cmd_parms * parms, void *dconf, int flag)
554 session_dir_conf *conf = dconf;
556 conf->env = flag;
557 conf->env_set = 1;
559 return NULL;
562 static const char *add_session_include(cmd_parms * cmd, void *dconf, const char *f)
564 session_dir_conf *conf = dconf;
566 const char **new = apr_array_push(conf->includes);
567 *new = f;
569 return NULL;
572 static const char *add_session_exclude(cmd_parms * cmd, void *dconf, const char *f)
574 session_dir_conf *conf = dconf;
576 const char **new = apr_array_push(conf->excludes);
577 *new = f;
579 return NULL;
583 static const command_rec session_cmds[] =
585 AP_INIT_FLAG("Session", set_session_enable, NULL, RSRC_CONF|OR_AUTHCFG,
586 "on if a session should be maintained for these URLs"),
587 AP_INIT_TAKE1("SessionMaxAge", set_session_maxage, NULL, RSRC_CONF|OR_AUTHCFG,
588 "length of time for which a session should be valid. Zero to disable"),
589 AP_INIT_TAKE1("SessionHeader", set_session_header, NULL, RSRC_CONF|OR_AUTHCFG,
590 "output header, if present, whose contents will be injected into the session."),
591 AP_INIT_FLAG("SessionEnv", set_session_env, NULL, RSRC_CONF|OR_AUTHCFG,
592 "on if a session should be written to the CGI environment. Defaults to off"),
593 AP_INIT_TAKE1("SessionInclude", add_session_include, NULL, RSRC_CONF|OR_AUTHCFG,
594 "URL prefixes to include in the session. Defaults to all URLs"),
595 AP_INIT_TAKE1("SessionExclude", add_session_exclude, NULL, RSRC_CONF|OR_AUTHCFG,
596 "URL prefixes to exclude from the session. Defaults to no URLs"),
597 {NULL}
600 static void register_hooks(apr_pool_t * p)
602 ap_register_output_filter("MOD_SESSION_OUT", session_output_filter,
603 NULL, AP_FTYPE_CONTENT_SET);
604 ap_hook_insert_filter(session_insert_output_filter, NULL, NULL,
605 APR_HOOK_MIDDLE);
606 ap_hook_insert_error_filter(session_insert_output_filter,
607 NULL, NULL, APR_HOOK_MIDDLE);
608 ap_hook_fixups(session_fixups, NULL, NULL, APR_HOOK_MIDDLE);
609 ap_hook_session_encode(session_identity_encode, NULL, NULL,
610 APR_HOOK_REALLY_FIRST);
611 ap_hook_session_decode(session_identity_decode, NULL, NULL,
612 APR_HOOK_REALLY_LAST);
613 APR_REGISTER_OPTIONAL_FN(ap_session_get);
614 APR_REGISTER_OPTIONAL_FN(ap_session_set);
615 APR_REGISTER_OPTIONAL_FN(ap_session_load);
616 APR_REGISTER_OPTIONAL_FN(ap_session_save);
619 module AP_MODULE_DECLARE_DATA session_module =
621 STANDARD20_MODULE_STUFF,
622 create_session_dir_config, /* dir config creater */
623 merge_session_dir_config, /* dir merger --- default is to override */
624 NULL, /* server config */
625 NULL, /* merge server config */
626 session_cmds, /* command apr_table_t */
627 register_hooks /* register hooks */