Skip to content

Commit 1e7b7ce

Browse files
authored
fix python3.13 leap day parsing warning in time.from_http_header() (#1452)
Signed-off-by: rafsaf <[email protected]>
1 parent c3f0254 commit 1e7b7ce

File tree

2 files changed

+159
-1
lines changed

2 files changed

+159
-1
lines changed

minio/time.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ def from_http_header(value: str) -> datetime:
7474
f"time data {value} does not match HTTP header format")
7575
weekday = _WEEK_DAYS.index(value[0:3])
7676

77-
day = datetime.strptime(value[4:8], " %d ").day
77+
if value[4] != " " or value[7] != " ":
78+
raise ValueError(
79+
f"time data {value} does not match HTTP header format"
80+
)
81+
day = int(value[5:7])
7882

7983
if value[8:11] not in _MONTHS:
8084
raise ValueError(

tests/unit/time_test.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# -*- coding: utf-8 -*-
2+
# MinIO Python Library for Amazon S3 Compatible Cloud Storage,
3+
# (C) 2024 MinIO, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import locale
18+
import threading
19+
import unittest
20+
from contextlib import contextmanager
21+
from datetime import datetime, timezone
22+
from typing import Any, Generator
23+
24+
from minio.time import from_http_header, to_http_header
25+
26+
LOCALE_LOCK = threading.Lock()
27+
LAST_MODIFIED_STR = "Mon, 02 Mar 2015 07:28:00 GMT"
28+
LAST_MODIFIED_DATE = datetime(
29+
year=2015,
30+
month=3,
31+
day=2,
32+
hour=7,
33+
minute=28,
34+
second=0,
35+
tzinfo=timezone.utc,
36+
)
37+
38+
39+
@contextmanager
40+
def setlocale(name) -> Generator[str, Any, None]:
41+
with LOCALE_LOCK:
42+
saved = locale.setlocale(locale.LC_ALL)
43+
try:
44+
yield locale.setlocale(locale.LC_ALL, name)
45+
finally:
46+
locale.setlocale(locale.LC_ALL, saved)
47+
48+
49+
class TimeutilsTest(unittest.TestCase):
50+
def test_from_http_header_valid_headers(self) -> None:
51+
for case in [
52+
(
53+
"Wed, 30 Oct 2024 09:35:00 GMT",
54+
datetime(
55+
year=2024,
56+
month=10,
57+
day=30,
58+
hour=9,
59+
minute=35,
60+
second=0,
61+
tzinfo=timezone.utc,
62+
),
63+
),
64+
(
65+
"Tue, 29 Oct 2024 00:35:00 GMT",
66+
datetime(
67+
year=2024,
68+
month=10,
69+
day=29,
70+
hour=0,
71+
minute=35,
72+
second=0,
73+
tzinfo=timezone.utc,
74+
),
75+
),
76+
(
77+
"Tue, 01 Oct 2024 22:35:22 GMT",
78+
datetime(
79+
year=2024,
80+
month=10,
81+
day=1,
82+
hour=22,
83+
minute=35,
84+
second=22,
85+
tzinfo=timezone.utc,
86+
),
87+
),
88+
(
89+
"Mon, 30 Sep 2024 22:35:55 GMT",
90+
datetime(
91+
year=2024,
92+
month=9,
93+
day=30,
94+
hour=22,
95+
minute=35,
96+
second=55,
97+
tzinfo=timezone.utc,
98+
),
99+
),
100+
]:
101+
with self.subTest(case=case):
102+
self.assertEqual(from_http_header(case[0]), case[1])
103+
104+
def test_from_http_header_invalid_headers(self) -> None:
105+
for case in [
106+
("Wed, 30 Oct 2024 09:35:00 GMT ", "invalid length"),
107+
("Wet, 30 Oct 2024 09:35:00 GMT", "invalid weekday"),
108+
("Wed 30 Oct 2024 09:35:00 GMT", "no comma after weekday"),
109+
("Wed,30 Sep 2024 09:35:00 GMT ", "no space after weekday"),
110+
("Wed, 30Sep 2024 09:35:00 GMT ", "no space before month"),
111+
("Wed, 00 Sep 2024 09:35:00 GMT", "invalid day"),
112+
("Wed, 32 Sep 2024 09:35:00 GMT", "invalid day 2"),
113+
("Wed, ab Sep 2024 09:35:00 GMT", "invalid day 3"),
114+
("Wed, 30 Set 2024 09:35:00 GMT", "invalid month"),
115+
("Tue, 30 Set 2024 09:35:00 GMT", "name of day doesn't match"),
116+
]:
117+
with self.subTest(case=case):
118+
self.assertRaises(ValueError, from_http_header, case[0])
119+
120+
def test_from_http_header_default_locale(self) -> None:
121+
result_datetime = from_http_header(LAST_MODIFIED_STR)
122+
123+
self.assertEqual(
124+
result_datetime,
125+
LAST_MODIFIED_DATE,
126+
)
127+
128+
def test_from_http_header_polish_locale(self) -> None:
129+
try:
130+
with setlocale("pl_PL.utf8"):
131+
result_datetime = from_http_header(LAST_MODIFIED_STR)
132+
133+
self.assertEqual(
134+
result_datetime,
135+
LAST_MODIFIED_DATE,
136+
)
137+
except locale.Error:
138+
self.skipTest("pl_PL.utf8 locale is not supported on this machine")
139+
140+
def test_to_http_header_default_locale(self) -> None:
141+
self.assertEqual(
142+
to_http_header(LAST_MODIFIED_DATE),
143+
LAST_MODIFIED_STR,
144+
)
145+
146+
def test_to_http_header_polish_locale(self) -> None:
147+
try:
148+
with setlocale("pl_PL.utf8"):
149+
self.assertEqual(
150+
to_http_header(LAST_MODIFIED_DATE),
151+
LAST_MODIFIED_STR,
152+
)
153+
except locale.Error:
154+
self.skipTest("pl_PL.utf8 locale is not supported on this machine")

0 commit comments

Comments
 (0)