summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhodbn <hodbn@users.noreply.github.com>2020-08-26 11:49:28 -0700
committerSeth Michael Larson <sethmichaellarson@gmail.com>2020-10-16 09:49:13 -0500
commit785b901916917375b94ff5338bd4444c6cb27f32 (patch)
treeae7d6b6c285799958103c15d33a303b114151fcc
parent9a2c1584c98455b9fd4ea8d5d448c285a934924c (diff)
downloadurllib3-785b901916917375b94ff5338bd4444c6cb27f32.tar.gz
[1.25] Always assume UTC if no timezone in Retry-After
-rw-r--r--dev-requirements.txt2
-rw-r--r--src/urllib3/util/retry.py7
-rw-r--r--test/conftest.py11
-rw-r--r--test/test_retry.py10
-rw-r--r--test/tz_stub.py39
5 files changed, 69 insertions, 0 deletions
diff --git a/dev-requirements.txt b/dev-requirements.txt
index dac6785e..3e31cc0e 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -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
diff --git a/src/urllib3/util/retry.py b/src/urllib3/util/retry.py
index 7481b677..b639c0c4 100644
--- a/src/urllib3/util/retry.py
+++ b/src/urllib3/util/retry.py
@@ -255,6 +255,13 @@ class Retry(object):
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()
diff --git a/test/conftest.py b/test/conftest.py
index f4bf8370..84e6c18e 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -8,6 +8,8 @@ import pytest
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
@@ -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
diff --git a/test/test_retry.py b/test/test_retry.py
index a7dc2736..777935fa 100644
--- a/test/test_retry.py
+++ b/test/test_retry.py
@@ -316,6 +316,16 @@ class TestRetry(object):
("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
):
diff --git a/test/tz_stub.py b/test/tz_stub.py
new file mode 100644
index 00000000..5b1a8c7e
--- /dev/null
+++ b/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()