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_PREFIX "mod_session: "
26 #define SESSION_EXPIRY "expiry"
27 #define HTTP_SESSION "HTTP_SESSION"
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
);
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 */
62 if (conf
->includes
->nelts
) {
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
))) {
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
))) {
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
,
98 session_rec
*zz
= NULL
;
101 /* is the session enabled? */
102 if (!dconf
->enabled
) {
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
);
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
);
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
);
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
));
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
);
141 rv
= ap_run_session_decode(r
, zz
);
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
);
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
;
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
)
174 apr_time_t now
= apr_time_now();
177 /* sanity checks, should we try save at all? */
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
);
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
);
191 /* encode the session */
192 rv
= ap_run_session_encode(r
, z
);
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
);
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
);
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
);
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
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
)
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
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
)
258 ap_session_load(r
, &z
);
262 apr_table_set(z
->entries
, key
, value
);
265 apr_table_unset(z
->entries
, key
);
271 static int identity_count(int *count
, const char *key
, const char *val
)
273 *count
+= strlen(key
) * 3 + strlen(val
) * 3 + 1;
277 static int identity_concat(char *buffer
, const char *key
, const char *val
)
279 char *slider
= buffer
;
280 int length
= strlen(slider
);
286 ap_escape_path_segment_buffer(slider
, key
);
287 slider
+= strlen(slider
);
290 ap_escape_path_segment_buffer(slider
, val
);
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
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
)
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
);
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
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
)
347 char *encoded
, *pair
;
348 const char *sep
= "&";
350 /* sanity check - anything to decode? */
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]) {
360 const char *psep
= "=";
361 char *key
= apr_strtok(pair
, psep
, &plast
);
362 char *val
= apr_strtok(NULL
, psep
, &plast
);
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
);
372 apr_table_set(z
->entries
, key
, val
);
376 pair
= apr_strtok(NULL
, sep
, &last
);
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
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
;
409 session_rec
*z
= NULL
;
410 session_dir_conf
*conf
= ap_get_module_config(r
->per_dir_config
,
413 /* load the session, or create one if necessary */
414 ap_session_load(r
, &z
);
415 if (!z
|| z
->written
) {
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
);
424 override
= apr_table_get(r
->headers_out
, conf
->header
);
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
);
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
);
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
,
470 session_rec
*z
= NULL
;
471 ap_session_load(r
, &z
);
474 session_identity_encode(r
, z
);
476 apr_table_set(r
->subprocess_env
, HTTP_SESSION
, z
->encoded
);
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 **));
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
);
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;
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;
541 set_session_header(cmd_parms
* parms
, void *dconf
, const char *arg
)
543 session_dir_conf
*conf
= dconf
;
546 conf
->header_set
= 1;
552 set_session_env(cmd_parms
* parms
, void *dconf
, int flag
)
554 session_dir_conf
*conf
= dconf
;
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
);
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
);
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"),
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
,
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 */