summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES5
-rw-r--r--docs/manual/mod/mod_session_cookie.xml156
-rw-r--r--include/util_cookies.h120
-rw-r--r--modules/session/config.m42
-rw-r--r--modules/session/mod_session_cookie.c269
-rw-r--r--server/Makefile.in2
-rw-r--r--server/util_cookies.c256
7 files changed, 808 insertions, 2 deletions
diff --git a/CHANGES b/CHANGES
index 2fd656d349..9011e7e566 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,11 @@
Changes with Apache 2.3.0
[ When backported to 2.2.x, remove entry from this file ]
+ *) mod_session_cookie: Add a session implementation capable of storing
+ session information within cookies on the browser. Useful for high
+ volume sites where server bound sessions are too resource intensive.
+ [Graham Leggett]
+
*) mod_session: Add a generic session interface to unify the different
attempts at saving persistent sessions across requests.
[Graham Leggett]
diff --git a/docs/manual/mod/mod_session_cookie.xml b/docs/manual/mod/mod_session_cookie.xml
new file mode 100644
index 0000000000..e2b2112130
--- /dev/null
+++ b/docs/manual/mod/mod_session_cookie.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0"?>
+<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd">
+<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?>
+<!-- $LastChangedRevision: 634760 $ -->
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<modulesynopsis metafile="mod_session_cookie.xml.meta">
+
+<name>mod_session_cookie</name>
+<description>Cookie based session support</description>
+<status>Extension</status>
+<sourcefile>mod_session_cookie.c</sourcefile>
+<identifier>session_cookie_module</identifier>
+
+<summary>
+ <note type="warning"><title>Warning</title>
+ <p>The session modules make use of HTTP cookies, and as such can fall
+ victim to Cross Site Scripting attacks, or expose potentially private
+ information to clients. Please ensure that the relevant risks have
+ been taken into account before enabling the session functionality on
+ your server.</p>
+ </note>
+
+ <p>This submodule of <module>mod_session</module> provides support for the
+ storage of user sessions on the remote browser within HTTP cookies.</p>
+
+ <p>Using cookies to store a session removes the need for the server or
+ a group of servers to store the session locally, or collaborate to share
+ a session, and can be useful for high traffic environments where a
+ server based session might be too resource intensive.</p>
+
+ <p>If session privacy is required, the <module>mod_session_crypto</module>
+ module can be used to encrypt the contents of the session before writing
+ the session to the client.</p>
+
+ <p>For more details on the session interface, see the documentation for
+ the <module>mod_session</module> module.</p>
+
+</summary>
+<seealso><module>mod_session</module></seealso>
+<seealso><module>mod_session_crypto</module></seealso>
+<seealso><module>mod_session_dbd</module></seealso>
+
+ <section id="basicexamples"><title>Basic Examples</title>
+
+ <p>To create a simple session and store it in a cookie called
+ <var>session</var>, configure the session as follows:</p>
+
+ <example><title>Browser based session</title>
+ Session On<br />
+ SessionCookieName session path=/<br />
+ </example>
+
+ <p>For more examples on how the session can be configured to be read
+ from and written to by a CGI application, see the
+ <module>mod_session</module> examples section.</p>
+
+ <p>For documentation on how the session can be used to store username
+ and password details, see the <module>mod_auth_form</module> module.</p>
+
+ </section>
+
+<directivesynopsis>
+<name>SessionCookieName</name>
+<description>Name and attributes for the RFC2109 cookie storing the session</description>
+<syntax>SessionCookieName <var>name</var> <var>attributes</var></syntax>
+<default>none</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+ <p>The <directive>SessionCookieName</directive> directive specifies the name and
+ optional attributes of an RFC2109 compliant cookie inside which the session will
+ be stored. RFC2109 cookies are set using the <code>Set-Cookie</code> HTTP header.
+ </p>
+
+ <p>An optional list of cookie attributes can be specified, as per the example below.
+ These attributes are inserted into the cookie as is, and are not interpreted by
+ Apache. Ensure that your attributes are defined correctly as per the cookie specification.
+ </p>
+
+ <example><title>Cookie with attributes</title>
+ Session On<br />
+ SessionCookieName session path=/private;domain=example.com;httponly;secure;version=1;<br />
+ </example>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionCookieName2</name>
+<description>Name and attributes for the RFC2965 cookie storing the session</description>
+<syntax>SessionCookieName2 <var>name</var> <var>attributes</var></syntax>
+<default>none</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+ <p>The <directive>SessionCookieName2</directive> directive specifies the name and
+ optional attributes of an RFC2965 compliant cookie inside which the session will
+ be stored. RFC2965 cookies are set using the <code>Set-Cookie2</code> HTTP header.
+ </p>
+
+ <p>An optional list of cookie attributes can be specified, as per the example below.
+ These attributes are inserted into the cookie as is, and are not interpreted by
+ Apache. Ensure that your attributes are defined correctly as per the cookie specification.
+ </p>
+
+ <example><title>Cookie2 with attributes</title>
+ Session On<br />
+ SessionCookieName2 session path=/private;domain=example.com;httponly;secure;version=1;<br />
+ </example>
+
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
+<name>SessionCookieRemove</name>
+<description>Control for whether session cookies should be removed from incoming HTTP headers</description>
+<syntax>SessionCookieRemove On|Off</syntax>
+<default>SessionCookieRemove Off</default>
+<contextlist><context>directory</context>
+</contextlist>
+<compatibility>Available in Apache 2.3.0 and later</compatibility>
+
+<usage>
+ <p>The <directive>SessionCookieRemove</directive> flag controls whether the cookies
+ containing the session will be removed from the headers during request processing.</p>
+
+ <p>In a reverse proxy situation where the Apache server acts as a server frontend for
+ a backend origin server, revealing the contents of the session cookie to the backend
+ could be a potential privacy violation. When set to on, the session cookie will be
+ removed from the incoming HTTP headers.</p>
+
+</usage>
+</directivesynopsis>
+
+</modulesynopsis>
diff --git a/include/util_cookies.h b/include/util_cookies.h
new file mode 100644
index 0000000000..c01b5f4436
--- /dev/null
+++ b/include/util_cookies.h
@@ -0,0 +1,120 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file util_cookies.h
+ * @brief Apache cookie library
+ */
+
+#ifndef UTIL_COOKIES_H
+#define UTIL_COOKIES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup APACHE_CORE_COOKIE Cookies
+ * @ingroup APACHE_CORE
+ *
+ * RFC2109 and RFC2965 compliant HTTP cookies can be read from and written
+ * to using this set of functions.
+ *
+ */
+
+#include "apr_errno.h"
+#include "httpd.h"
+
+#define SET_COOKIE "Set-Cookie"
+#define SET_COOKIE2 "Set-Cookie2"
+#define DEFAULT_ATTRS "HttpOnly;Secure;Version=1"
+#define CLEAR_ATTRS "Max-Age=0;Version=1"
+
+typedef struct {
+ request_rec *r;
+ const char *name;
+ const char *encoded;
+ apr_table_t *new_cookies;
+ int duplicated;
+} ap_cookie_do;
+
+/**
+ * Write an RFC2109 compliant cookie.
+ *
+ * @param r The request
+ * @param name The name of the cookie.
+ * @param val The value to place in the cookie.
+ * @param attrs The string containing additional cookie attributes. If NULL, the
+ * DEFAULT_ATTRS will be used.
+ * @param maxage If non zero, a Max-Age header will be added to the cookie.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_write(request_rec * r, const char *name, const char *val,
+ const char *attrs, long maxage);
+
+/**
+ * Write an RFC2965 compliant cookie.
+ *
+ * @param r The request
+ * @param name2 The name of the cookie.
+ * @param val The value to place in the cookie.
+ * @param attrs2 The string containing additional cookie attributes. If NULL, the
+ * DEFAULT_ATTRS will be used.
+ * @param maxage If non zero, a Max-Age header will be added to the cookie.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_write2(request_rec * r, const char *name2, const char *val,
+ const char *attrs2, long maxage);
+
+/**
+ * Remove an RFC2109 compliant cookie.
+ *
+ * @param r The request
+ * @param name The name of the cookie.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_remove(request_rec * r, const char *name);
+
+/**
+ * Remove an RFC2965 compliant cookie.
+ *
+ * @param r The request
+ * @param name2 The name of the cookie.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_remove2(request_rec * r, const char *name2);
+
+/**
+ * Read a cookie called name, placing its value in val.
+ *
+ * Both the Cookie and Cookie2 headers are scanned for the cookie.
+ *
+ * If the cookie is duplicated, this function returns APR_EGENERAL. If found,
+ * and if remove is non zero, the cookie will be removed from the headers, and
+ * thus kept private from the backend.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_read(request_rec * r, const char *name, const char **val,
+ int remove);
+
+/**
+ * Sanity check a given string that it exists, is not empty,
+ * and does not contain the special characters '=', ';' and '&'.
+ *
+ * It is used to sanity check the cookie names.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_check_string(const char *string);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !UTIL_COOKIES_H */
diff --git a/modules/session/config.m4 b/modules/session/config.m4
index f346bfda71..53548cf076 100644
--- a/modules/session/config.m4
+++ b/modules/session/config.m4
@@ -10,7 +10,7 @@ dnl Session modules; modules that are capable of storing key value pairs in
dnl various places, such as databases, LDAP, or cookies.
dnl
APACHE_MODULE(session, session module, , , most)
-dnl APACHE_MODULE(session_cookie, session cookie module, , , most)
+APACHE_MODULE(session_cookie, session cookie module, , , most)
dnl APACHE_MODULE(session_crypto, session crypto module, , , most)
dnl APACHE_MODULE(session_dbd, session dbd module, , , most)
dnl APACHE_MODULE(session_ldap, session ldap module, , , most)
diff --git a/modules/session/mod_session_cookie.c b/modules/session/mod_session_cookie.c
new file mode 100644
index 0000000000..b69ef2833e
--- /dev/null
+++ b/modules/session/mod_session_cookie.c
@@ -0,0 +1,269 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CORE_PRIVATE
+
+#include "mod_session.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "http_log.h"
+#include "util_cookies.h"
+
+#define LOG_PREFIX "mod_session_cookie: "
+#define MOD_SESSION_COOKIE "mod_session_cookie"
+
+module AP_MODULE_DECLARE_DATA session_cookie_module;
+
+/**
+ * Structure to carry the per-dir session config.
+ */
+typedef struct {
+ const char *name;
+ int name_set;
+ const char *name_attrs;
+ const char *name2;
+ int name2_set;
+ const char *name2_attrs;
+ int remove;
+ int remove_set;
+} session_cookie_dir_conf;
+
+/**
+ * Set the cookie and embed the session within it.
+ *
+ * This function adds an RFC2109 compliant Set-Cookie header for
+ * the cookie specified in SessionCookieName, and an RFC2965 compliant
+ * Set-Cookie2 header for the cookie specified in SessionCookieName2.
+ *
+ * If specified, the optional cookie attributes will be added to
+ * each cookie. If defaults are not specified, DEFAULT_ATTRS
+ * will be used.
+ *
+ * On success, this method will return APR_SUCCESS.
+ *
+ * @param r The request pointer.
+ * @param z A pointer to where the session will be written.
+ */
+AP_DECLARE(int) ap_session_cookie_save(request_rec * r, session_rec * z)
+{
+
+ session_cookie_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_cookie_module);
+
+ /* don't cache auth protected pages */
+ apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
+
+ /* create RFC2109 compliant cookie */
+ if (conf->name_set) {
+ if (z->encoded && z->encoded[0]) {
+ ap_cookie_write(r, conf->name, z->encoded, conf->name_attrs, z->maxage);
+ }
+ else {
+ ap_cookie_remove(r, conf->name);
+ }
+ }
+
+ /* create RFC2965 compliant cookie */
+ if (conf->name2_set) {
+ if (z->encoded && z->encoded[0]) {
+ ap_cookie_write2(r, conf->name2, z->encoded, conf->name2_attrs, z->maxage);
+ }
+ else {
+ ap_cookie_remove2(r, conf->name2);
+ }
+ }
+
+ if (conf->name_set || conf->name2_set) {
+ return OK;
+ }
+ return DECLINED;
+
+}
+
+/**
+ * Isolate the cookie with the name "name", and if present, extract
+ * the payload from the cookie.
+ *
+ * If the cookie is found, the cookie and any other cookies with the
+ * same name are removed from the cookies passed in the request, so
+ * that credentials are not leaked to a backend server or process.
+ *
+ * A missing or malformed cookie will cause this function to return
+ * APR_EGENERAL.
+ *
+ * On success, this returns APR_SUCCESS.
+ */
+AP_DECLARE(int) ap_session_cookie_load(request_rec * r, session_rec ** z)
+{
+
+ session_cookie_dir_conf *conf = ap_get_module_config(r->per_dir_config,
+ &session_cookie_module);
+
+ session_rec *zz = NULL;
+ const char *val = NULL;
+ const char *note = NULL;
+ const char *name = NULL;
+ request_rec *m = r->main ? r->main : r;
+
+ /* is our session in a cookie? */
+ if (conf->name2_set) {
+ name = conf->name2;
+ }
+ else if (conf->name_set) {
+ name = conf->name;
+ }
+ else {
+ return DECLINED;
+ }
+
+ /* first look in the notes */
+ note = apr_pstrcat(r->pool, MOD_SESSION_COOKIE, name, NULL);
+ zz = (session_rec *)apr_table_get(m->notes, note);
+ if (zz) {
+ *z = zz;
+ return OK;
+ }
+
+ /* otherwise, try parse the cookie */
+ ap_cookie_read(r, name, &val, conf->remove);
+
+ /* create a new session and return it */
+ zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec));
+ zz->pool = r->pool;
+ zz->entries = apr_table_make(r->pool, 10);
+ zz->encoded = val;
+ zz->uuid = (apr_uuid_t *) apr_pcalloc(r->pool, sizeof(apr_uuid_t));
+ *z = zz;
+
+ /* put the session in the notes so we don't have to parse it again */
+ apr_table_setn(m->notes, note, (char *)zz);
+
+ return OK;
+
+}
+
+
+
+static void *create_session_cookie_dir_config(apr_pool_t * p, char *dummy)
+{
+ session_cookie_dir_conf *new =
+ (session_cookie_dir_conf *) apr_pcalloc(p, sizeof(session_cookie_dir_conf));
+
+ return (void *) new;
+}
+
+static void *merge_session_cookie_dir_config(apr_pool_t * p, void *basev, void *addv)
+{
+ session_cookie_dir_conf *new = (session_cookie_dir_conf *) apr_pcalloc(p, sizeof(session_cookie_dir_conf));
+ session_cookie_dir_conf *add = (session_cookie_dir_conf *) addv;
+ session_cookie_dir_conf *base = (session_cookie_dir_conf *) basev;
+
+ new->name = (add->name_set == 0) ? base->name : add->name;
+ new->name_attrs = (add->name_set == 0) ? base->name_attrs : add->name_attrs;
+ new->name_set = add->name_set || base->name_set;
+ new->name2 = (add->name2_set == 0) ? base->name2 : add->name2;
+ new->name2_attrs = (add->name2_set == 0) ? base->name2_attrs : add->name2_attrs;
+ new->name2_set = add->name2_set || base->name2_set;
+ new->remove = (add->remove_set == 0) ? base->remove : add->remove;
+ new->remove_set = add->remove_set || base->remove_set;
+
+ return new;
+}
+
+/**
+ * Sanity check a given string that it exists, is not empty,
+ * and does not contain special characters.
+ */
+static const char *check_string(cmd_parms * cmd, const char *string)
+{
+ if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&')) {
+ return apr_pstrcat(cmd->pool, cmd->directive->directive,
+ " cannot be empty, or contain '=' or '&'.",
+ NULL);
+ }
+ return NULL;
+}
+
+static const char *set_cookie_name(cmd_parms * cmd, void *config, const char *args)
+{
+ char *last;
+ char *line = apr_pstrdup(cmd->pool, args);
+ session_cookie_dir_conf *conf = (session_cookie_dir_conf *) config;
+ char *cookie = apr_strtok(line, " \t", &last);
+ conf->name = cookie;
+ conf->name_set = 1;
+ while (apr_isspace(*last)) {
+ last++;
+ }
+ conf->name_attrs = last;
+ return check_string(cmd, cookie);
+}
+
+static const char *set_cookie_name2(cmd_parms * cmd, void *config, const char *args)
+{
+ char *last;
+ char *line = apr_pstrdup(cmd->pool, args);
+ session_cookie_dir_conf *conf = (session_cookie_dir_conf *) config;
+ char *cookie = apr_strtok(line, " \t", &last);
+ conf->name2 = cookie;
+ conf->name2_set = 1;
+ while (apr_isspace(*last)) {
+ last++;
+ }
+ conf->name2_attrs = last;
+ return check_string(cmd, cookie);
+}
+
+static const char *
+ set_remove(cmd_parms * parms, void *dconf, int flag)
+{
+ session_cookie_dir_conf *conf = dconf;
+
+ conf->remove = flag;
+ conf->remove_set = 1;
+
+ return NULL;
+}
+
+static const command_rec session_cookie_cmds[] =
+{
+ AP_INIT_RAW_ARGS("SessionCookieName", set_cookie_name, NULL, OR_AUTHCFG,
+ "The name of the RFC2109 cookie carrying the session"),
+ AP_INIT_RAW_ARGS("SessionCookieName2", set_cookie_name2, NULL, OR_AUTHCFG,
+ "The name of the RFC2965 cookie carrying the session"),
+ AP_INIT_FLAG("SessionCookieRemove", set_remove, NULL, OR_AUTHCFG,
+ "Set to 'On' to remove the session cookie from the headers "
+ "and hide the cookie from a backend server or process"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t * p)
+{
+ ap_hook_session_load(ap_session_cookie_load, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_session_save(ap_session_cookie_save, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+module AP_MODULE_DECLARE_DATA session_cookie_module =
+{
+ STANDARD20_MODULE_STUFF,
+ create_session_cookie_dir_config, /* dir config creater */
+ merge_session_cookie_dir_config, /* dir merger --- default is to
+ * override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ session_cookie_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/server/Makefile.in b/server/Makefile.in
index 5fc286a571..3c96c4061b 100644
--- a/server/Makefile.in
+++ b/server/Makefile.in
@@ -11,7 +11,7 @@ LTLIBRARY_SOURCES = \
config.c log.c main.c vhost.c util.c \
util_script.c util_md5.c util_cfgtree.c util_ebcdic.c util_time.c \
connection.c listen.c util_mutex.c \
- mpm_common.c util_charset.c util_debug.c util_xml.c \
+ mpm_common.c util_charset.c util_cookies.c util_debug.c util_xml.c \
util_expr.c util_filter.c util_pcre.c exports.c \
scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \
eoc_bucket.c eor_bucket.c core_filters.c
diff --git a/server/util_cookies.c b/server/util_cookies.c
new file mode 100644
index 0000000000..dc04b5bbe6
--- /dev/null
+++ b/server/util_cookies.c
@@ -0,0 +1,256 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define CORE_PRIVATE
+
+#include "util_cookies.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "http_log.h"
+
+#define LOG_PREFIX "ap_cookie: "
+
+/**
+ * Write an RFC2109 compliant cookie.
+ *
+ * @param r The request
+ * @param name The name of the cookie.
+ * @param val The value to place in the cookie.
+ * @param attrs The string containing additional cookie attributes. If NULL, the
+ * DEFAULT_ATTRS will be used.
+ * @param maxage If non zero, a Max-Age header will be added to the cookie.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_write(request_rec * r, const char *name, const char *val,
+ const char *attrs, long maxage)
+{
+
+ char *buffer;
+
+ /* handle expiry */
+ buffer = "";
+ if (maxage) {
+ buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";", NULL);
+ }
+
+ /* create RFC2109 compliant cookie */
+ char *rfc2109 = apr_pstrcat(r->pool, name, "=", val, ";",
+ buffer,
+ attrs && strlen(attrs) > 0 ?
+ attrs : DEFAULT_ATTRS, NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX
+ "user '%s' set cookie: '%s'", r->user, rfc2109);
+ apr_table_addn(r->headers_out, SET_COOKIE, rfc2109);
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Write an RFC2965 compliant cookie.
+ *
+ * @param r The request
+ * @param name2 The name of the cookie.
+ * @param val The value to place in the cookie.
+ * @param attrs2 The string containing additional cookie attributes. If NULL, the
+ * DEFAULT_ATTRS will be used.
+ * @param maxage If non zero, a Max-Age header will be added to the cookie.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_write2(request_rec * r, const char *name2, const char *val,
+ const char *attrs2, long maxage)
+{
+
+ char *buffer;
+
+ /* handle expiry */
+ buffer = "";
+ if (maxage) {
+ buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";");
+ }
+
+ /* create RFC2965 compliant cookie */
+ char *rfc2965 = apr_pstrcat(r->pool, name2, "=", val, ";",
+ buffer,
+ attrs2 && strlen(attrs2) > 0 ?
+ attrs2 : DEFAULT_ATTRS, NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX
+ "user '%s' set cookie2: '%s'", r->user, rfc2965);
+ apr_table_addn(r->headers_out, SET_COOKIE2, rfc2965);
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Remove an RFC2109 compliant cookie.
+ *
+ * @param r The request
+ * @param name The name of the cookie.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_remove(request_rec * r, const char *name)
+{
+
+ /* create RFC2109 compliant cookie */
+ char *rfc2109 = apr_pstrcat(r->pool, name, "=;",
+ CLEAR_ATTRS, NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX
+ "user '%s' removed cookie: '%s'", r->user, rfc2109);
+ apr_table_addn(r->headers_out, SET_COOKIE, rfc2109);
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Remove an RFC2965 compliant cookie.
+ *
+ * @param r The request
+ * @param name2 The name of the cookie.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_remove2(request_rec * r, const char *name2)
+{
+
+ /* create RFC2965 compliant cookie */
+ char *rfc2965 = apr_pstrcat(r->pool, name2, "=;",
+ CLEAR_ATTRS, NULL);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX
+ "user '%s' removed cookie2: '%s'", r->user, rfc2965);
+ apr_table_addn(r->headers_out, SET_COOKIE2, rfc2965);
+
+ return APR_SUCCESS;
+
+}
+
+/* Iterate through the cookies, isolate our cookie and then remove it.
+ *
+ * If our cookie appears two or more times, but with different values,
+ * remove it twice and set the duplicated flag to true. Remove any
+ * $path or other attributes following our cookie if present. If we end
+ * up with an empty cookie, remove the whole header.
+ */
+static int extract_cookie_line(ap_cookie_do * v, const char *key, const char *val)
+{
+ char *last1, *last2;
+ char *cookie = apr_pstrdup(v->r->pool, val);
+ const char *name = apr_pstrcat(v->r->pool, v->name ? v->name : "", "=", NULL);
+ size_t len = strlen(name);
+ char *new_cookie = "";
+ const char *comma = ",";
+ char *next1;
+ const char *semi = ";";
+ char *next2;
+ const char *sep = "";
+ int cookies = 0;
+
+ /* find the cookie called name */
+ int eat = 0;
+ next1 = apr_strtok(cookie, comma, &last1);
+ while (next1) {
+ next2 = apr_strtok(next1, semi, &last2);
+ while (next2) {
+ char *trim = next2;
+ while (apr_isspace(*trim)) {
+ trim++;
+ }
+ if (!strncmp(trim, name, len)) {
+ if (v->encoded) {
+ if (strcmp(v->encoded, trim + len)) {
+ v->duplicated = 1;
+ }
+ }
+ v->encoded = apr_pstrdup(v->r->pool, trim + len);
+ eat = 1;
+ }
+ else {
+ if (*trim != '$') {
+ cookies++;
+ eat = 0;
+ }
+ if (!eat) {
+ new_cookie = apr_pstrcat(v->r->pool, new_cookie, sep, next2, NULL);
+ }
+ }
+ next2 = apr_strtok(NULL, semi, &last2);
+ sep = semi;
+ }
+
+ next1 = apr_strtok(NULL, comma, &last1);
+ sep = comma;
+ }
+
+ /* any cookies left over? */
+ if (cookies) {
+ apr_table_addn(v->new_cookies, key, new_cookie);
+ }
+
+ return 1;
+}
+
+/**
+ * Read a cookie called name, placing its value in val.
+ *
+ * Both the Cookie and Cookie2 headers are scanned for the cookie.
+ *
+ * If the cookie is duplicated, this function returns APR_EGENERAL. If found,
+ * and if remove is non zero, the cookie will be removed from the headers, and
+ * thus kept private from the backend.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_read(request_rec * r, const char *name, const char **val,
+ int remove)
+{
+
+ ap_cookie_do v;
+ v.r = r;
+ v.encoded = NULL;
+ v.new_cookies = apr_table_make(r->pool, 10);
+ v.duplicated = 0;
+ v.name = name;
+
+ apr_table_do((int (*) (void *, const char *, const char *))
+ extract_cookie_line, (void *) &v, r->headers_in,
+ "Cookie", "Cookie2", NULL);
+ if (v.duplicated) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX
+ "client submitted cookie '%s' more than once: %s", v.name, r->uri);
+ return APR_EGENERAL;
+ }
+
+ /* remove our cookie(s), and replace them */
+ if (remove) {
+ apr_table_unset(r->headers_in, "Cookie");
+ apr_table_unset(r->headers_in, "Cookie2");
+ r->headers_in = apr_table_overlay(r->pool, r->headers_in, v.new_cookies);
+ }
+
+ *val = v.encoded;
+
+ return APR_SUCCESS;
+
+}
+
+/**
+ * Sanity check a given string that it exists, is not empty,
+ * and does not contain the special characters '=', ';' and '&'.
+ *
+ * It is used to sanity check the cookie names.
+ */
+AP_DECLARE(apr_status_t) ap_cookie_check_string(const char *string)
+{
+ if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&') ||
+ ap_strchr_c(string, ';')) {
+ return APR_EGENERAL;
+ }
+ return APR_SUCCESS;
+}