Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cachecontrol/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,12 @@ def parse_cache_control(self, headers: Mapping[str, str]) -> dict[str, int | Non
retval[directive] = None
if typ:
try:
retval[directive] = typ(parts[1].strip())
value = parts[1].strip()
# Recipients ought to accept both token and quoted-string
# arguments (RFC 9111, section 5.2).
if len(value) >= 2 and value[0] == value[-1] == '"':
value = value[1:-1]
retval[directive] = typ(value)
except IndexError:
if required:
logger.debug(
Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ def cache_60(self, env, start_response):
start_response("200 OK", headers)
return [pformat(env).encode("utf8")]

def cache_60_quoted(self, env, start_response):
headers = [("Cache-Control", 'public, max-age="60"')]
start_response("200 OK", headers)
return [pformat(env).encode("utf8")]

def no_cache(self, env, start_response):
headers = [("Cache-Control", "no-cache")]
start_response("200 OK", headers)
Expand Down
40 changes: 38 additions & 2 deletions tests/test_cache_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,41 @@
TIME_FMT = "%a, %d %b %Y %H:%M:%S GMT"


class TestParseCacheControl:
@pytest.mark.parametrize(
"directive", ["max-age", "s-maxage", "max-stale", "min-fresh"]
)
@pytest.mark.parametrize("value", [0, 600, -1])
def test_quoted_numeric_value(self, directive, value):
controller = CacheController()
expected = {directive: value}

assert (
controller.parse_cache_control({"Cache-Control": f"{directive}={value}"})
== expected
)
assert (
controller.parse_cache_control(
{"Cache-Control": f'{directive}= \t"{value}" \t'}
)
== expected
)

@pytest.mark.parametrize(
"directive", ["max-age", "s-maxage", "max-stale", "min-fresh"]
)
@pytest.mark.parametrize("value", ['"600', '600"', '"bad"', '""', '"600"extra'])
def test_invalid_numeric_value(self, directive, value):
expected = {"max-stale": None} if directive == "max-stale" else {}

assert (
CacheController().parse_cache_control(
{"Cache-Control": f"{directive}={value}"}
)
== expected
)


class TestCacheControllerResponse:
url = "http://url.com/"

Expand Down Expand Up @@ -84,9 +119,10 @@ def test_cache_response_no_cache_control(self, cc):

assert not cc.cache.set.called

def test_cache_response_cache_max_age(self, cc):
@pytest.mark.parametrize("value", ["3600", '"3600"'])
def test_cache_response_cache_max_age(self, cc, value):
now = time.strftime(TIME_FMT, time.gmtime())
resp = self.resp({"cache-control": "max-age=3600", "date": now})
resp = self.resp({"cache-control": f"max-age={value}", "date": now})
req = self.req()
cc.cache_response(req, resp)
cc.serializer.dumps.assert_called_with(req, resp, None)
Expand Down
17 changes: 15 additions & 2 deletions tests/test_max_age.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytest

from requests import Session
from cachecontrol import CacheControl
from cachecontrol.adapter import CacheControlAdapter
from cachecontrol.cache import DictCache
from .utils import NullSerializer
Expand All @@ -21,7 +22,8 @@ def sess(self, url):
)
return sess

def test_client_max_age_0(self, sess):
@pytest.mark.parametrize("value", ["0", '"0"'])
def test_client_max_age_0(self, sess, value):
"""
Making sure when the client uses max-age=0 we don't get a
cached copy even though we're still fresh.
Expand All @@ -31,12 +33,23 @@ def test_client_max_age_0(self, sess):
assert self.cache.get(self.url) == r.raw

print("second request")
r = sess.get(self.url, headers={"Cache-Control": "max-age=0"})
r = sess.get(self.url, headers={"Cache-Control": f"max-age={value}"})

# don't remove from the cache
assert self.cache.get(self.url)
assert not r.from_cache

@pytest.mark.parametrize("path", ["cache_60", "cache_60_quoted"])
def test_server_max_age(self, url, path):
with CacheControl(Session()) as sess:
response = sess.get(url + path)
assert response.from_cache is False
body = response.content

response = sess.get(url + path)
assert response.from_cache is True
assert response.content == body

def test_client_max_age_3600(self, sess):
"""
Verify we get a cached value when the client has a
Expand Down