Skip to content

Commit

Permalink
Always assume UTC if no timezone in Retry-After
Browse files Browse the repository at this point in the history
  • Loading branch information
hodbn committed Aug 26, 2020
1 parent d455be9 commit cf6ab7a
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 0 deletions.
2 changes: 2 additions & 0 deletions dev-requirements.txt
Expand Up @@ -15,3 +15,5 @@ gcp-devrel-py-tools==0.0.15

# https://github.com/GoogleCloudPlatform/python-repo-tools/issues/23
pylint<2.0;python_version<="2.7"

python-dateutil==2.8.1
7 changes: 7 additions & 0 deletions src/urllib3/util/retry.py
Expand Up @@ -269,6 +269,13 @@ def parse_retry_after(self, retry_after):
retry_date_tuple = email.utils.parsedate_tz(retry_after)
if retry_date_tuple is None:
raise InvalidHeader("Invalid Retry-After header: %s" % retry_after)
if retry_date_tuple[9] is None: # Python 2
# Assume UTC if no timezone was specified
# On Python2.7, parsedate_tz returns None for a timezone offset
# instead of 0 if no timezone is given, where mktime_tz treats
# a None timezone offset as local time.
retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:]

retry_date = email.utils.mktime_tz(retry_date_tuple)
seconds = retry_date - time.time()

Expand Down
11 changes: 11 additions & 0 deletions test/conftest.py
Expand Up @@ -8,6 +8,8 @@
import trustme
from tornado import web, ioloop

from .tz_stub import stub_timezone_ctx

from dummyserver.handlers import TestingApp
from dummyserver.server import run_tornado_app
from dummyserver.server import HAS_IPV6
Expand Down Expand Up @@ -96,3 +98,12 @@ def ipv6_san_server(tmp_path_factory):

with run_server_in_thread("https", "::1", tmpdir, ca, server_cert) as cfg:
yield cfg


@pytest.yield_fixture
def stub_timezone(request):
"""
A pytest fixture that runs the test with a stub timezone.
"""
with stub_timezone_ctx(request.param):
yield
10 changes: 10 additions & 0 deletions test/test_retry.py
Expand Up @@ -332,6 +332,16 @@ def test_respect_retry_after_header_propagated(self, respect_retry_after_header)
("Mon Jun 3 11:30:12 2019", True, 1812),
],
)
@pytest.mark.parametrize(
"stub_timezone",
[
"UTC",
"Asia/Jerusalem",
None,
],
indirect=True,
)
@pytest.mark.usefixtures("stub_timezone")
def test_respect_retry_after_header_sleep(
self, retry_after_header, respect_retry_after_header, sleep_duration
):
Expand Down
39 changes: 39 additions & 0 deletions test/tz_stub.py
@@ -0,0 +1,39 @@
from contextlib import contextmanager
import time
import datetime
import os
import pytest
from dateutil import tz


@contextmanager
def stub_timezone_ctx(tzname):
"""
Switch to a locally-known timezone specified by `tzname`.
On exit, restore the previous timezone.
If `tzname` is `None`, do nothing.
"""
if tzname is None:
yield
return

# Only supported on Unix
if not hasattr(time, "tzset"):
pytest.skip("Timezone patching is not supported")

# Make sure the new timezone exists, at least in dateutil
new_tz = tz.gettz(tzname)
if new_tz is None:
raise ValueError("Invalid timezone specified: %r" % (tzname,))

# Get the current timezone
local_tz = tz.tzlocal()
if local_tz is None:
raise EnvironmentError("Cannot determine current timezone")
old_tzname = datetime.datetime.now(local_tz).tzname()

os.environ["TZ"] = tzname
time.tzset()
yield
os.environ["TZ"] = old_tzname
time.tzset()

0 comments on commit cf6ab7a

Please sign in to comment.