Cookies

Introduction

Cookies in Plone are read from the request and set on the response. There are methods on request and response for cookie handling. Reading the raw cookie direct from the request header is still possible, but not needed. Same is valid for setting or expiring cookies on the response.

Reading cookies

Incoming cookies are sent by the browser in the request header. The incoming request was already parsed by the publisher. Cookies are available as cookies mapping variable on the request.

self.request.cookies.get("cookie_name", "default_value_if_cookie_not_set")

Setting cookies

Setting cookies is done on the response. Each cookie name there can be a new or an existing one. When sending the response, the information is turned into a properly formatted Set-Cookie header.

self.request.response.setCookie("cookie_name", "cookie_value")

A cookie may have cookie-attributes set. Those are passed as keyword arguments to setCookie. Turning a cookie into a non-session cookie requires an expires='date' keyword and value. Limiting the cookie to a path requires a path='/somepath' keyword and value for the setCookie() method. The usual browser cookie rules apply here.

self.request.response.setCookie(
    "cookie_name",
    "cookie_value",
    quoted=False,  # default is True
    attribute_on_cookie="attribute value",
    another_attribute="another attribute value",
)

Clearing cookies

If a cookie needs to be removed, the browser has to be told to expire it. The expireCookie method does this. It always sets max_age to 0 and the expires date to the past. Additionally the cookie value will be set to deleted.

self.request.response.expireCookie("cookie_name")

The method expireCookie allows additional attributes to be passed as keyword arguments, similar to setCookie. The aforementioned keywords max_age, expires, and value are reserved and are not allowed. A common keyword used here is path.

Default Plone cookies

Typical Plone cookies:

# Logged in cookie
__ac="NjE2NDZkNjk2ZTMyOjcyNzQ3NjQxNjQ2ZDY5NmUzNjM2MzczNw%253D%253D";

# Language chooser
I18N_LANGUAGE="fi";

# Status message
statusmessages="BURUZXJ2ZXR1bG9hISBPbGV0IG55dCBraXJqYXV0dW51dCBzaXPDpMOkbi5pbmZv"

# Google Analytics tracking
__utma=39444192.1440286234.1270737994.1321356818.1321432528.21;
__utmz=39444192.1306272121.6.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none);
__utmb=39444192.3.10.1321432528;
__utmc=39444192;

# Plone copy-paste clipboard
__cp="x%25DA%2515%258AA%250A%25800%250C%2504%25A3%25A0%25E0E%257CF%25FF%25E4%2529%2587%25801%25D5B%25B3-%25F8%257B%25D3%25C3%250E%25CC%25B0i%2526%2522%258D%25D19%2505%25D2%2512%25C0P%25DF%2502%259D%25AB%253E%250C%2514_%25C3%25CAu%258B%25C0%258Fq%2511s%25E8k%25EC%250AH%25FE%257C%258Fh%25AD%25B3qm.9%252B%257E%25FD%25D1%2516%25B3"; Path=/

Sanitizing cookies for the cache

You do not want to store HTTP responses with cookies in a front end cache server, because this would be a leak of other users’ information.

Do not cache pages with cookies set. Also with multilingual sites it makes sense to have unique URLs for different translations as this greatly simplifies caching (you can ignore language cookie).

Note that cookies can be set:

  • by the server (Plone itself)

  • on the client side, by JavaScript (Google Analytics)

… so you might need to clean cookies for both incoming HTTP requests and HTTP responses.

More info in Varnish section of this manual.

Late cleanup of HTTP response cookies

You can do this after all processing is done and before the transaction is committed by subscribing to the ZPublisher.interfaces.IPubBeforeCommit event.

Put the code below in a file cleancookies.py.

"""Clean I18N cookies from non-HTML responses.
E.g. Image content, which has language set, and is cross-linked across page,
do not inadvertently change the language.
"""

from zope.interface import Interface
from zope.component import adapter
from ZPublisher.interfaces import IPubBeforeCommit


@adapter(Interface, IAfterPublicationEvent)
def clean_language(object, event):
    """ Clean up cookies after HTTPResponse object has been constructed completely.

    Post-publication handler.
    """
    request = event.request

    # All non-HTML payloads
    if (
        not event.request.response.headers["content-type"].startswith("text/html"):
        and "I18N_LANGUAGE" in request.response.cookies
    ):
        del request.response.cookies["I18N_LANGUAGE"]

Register the clean_language function as a subscriber in ZCML:

<subscriber handler=".cleancookies.clean_language" />

Signing cookies

Kind of… crude example