diff --git a/flask/helpers.py b/flask/helpers.py index f37be677..2bcdc10b 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -1004,3 +1004,17 @@ def is_ip(value): return True return False + + +def patch_vary_header(response, value): + """Add a value to the ``Vary`` header if it is not already present.""" + + header = response.headers.get('Vary', '') + headers = [h for h in (h.strip() for h in header.split(',')) if h] + lower_value = value.lower() + + if not any(h.lower() == lower_value for h in headers): + headers.append(value) + + updated_header = ', '.join(headers) + response.headers['Vary'] = updated_header diff --git a/flask/sessions.py b/flask/sessions.py index 2dbd8b32..47f0b3fd 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -9,18 +9,20 @@ :license: BSD, see LICENSE for more details. """ -import uuid import hashlib +import uuid import warnings -from base64 import b64encode, b64decode +from base64 import b64decode, b64encode from datetime import datetime -from werkzeug.http import http_date, parse_date + +from itsdangerous import BadSignature, URLSafeTimedSerializer from werkzeug.datastructures import CallbackDict +from werkzeug.http import http_date, parse_date + +from flask.helpers import patch_vary_header from . import Markup, json from ._compat import iteritems, text_type -from .helpers import total_seconds, is_ip - -from itsdangerous import URLSafeTimedSerializer, BadSignature +from .helpers import is_ip, total_seconds class SessionMixin(object): @@ -405,7 +407,7 @@ class SecureCookieSessionInterface(SessionInterface): # Add a "Vary: Cookie" header if the session was accessed at all. if session.accessed: - response.headers.add('Vary', 'Cookie') + patch_vary_header(response, 'Cookie') if not self.should_set_cookie(app, session): return diff --git a/tests/test_basic.py b/tests/test_basic.py index de56cb43..e4d6b2f9 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -520,15 +520,31 @@ def test_session_vary_cookie(app, client): def setdefault(): return flask.session.setdefault('test', 'default') + @app.route('/vary-cookie-header-set') + def vary_cookie_header_set(): + response = flask.Response() + response.headers['Vary'] = 'Cookie' + flask.session['test'] = 'test' + return response + + @app.route('/vary-header-set') + def vary_header_set(): + response = flask.Response() + response.headers['Vary'] = 'Accept-Encoding, Accept-Language' + flask.session['test'] = 'test' + return response + @app.route('/no-vary-header') def no_vary_header(): return '' - def expect(path, header=True): + def expect(path, header_value='Cookie'): rv = client.get(path) - if header: - assert rv.headers['Vary'] == 'Cookie' + if header_value: + # The 'Vary' key should exist in the headers only once. + assert len(rv.headers.get_all('Vary')) == 1 + assert rv.headers['Vary'] == header_value else: assert 'Vary' not in rv.headers @@ -536,7 +552,9 @@ def test_session_vary_cookie(app, client): expect('/get') expect('/getitem') expect('/setdefault') - expect('/no-vary-header', False) + expect('/vary-cookie-header-set') + expect('/vary-header-set', 'Accept-Encoding, Accept-Language, Cookie') + expect('/no-vary-header', None) def test_flashes(app, req_ctx):