Skip to content

Commit db6ab53

Browse files
Merge branch 'main' into dis-theme-support
2 parents 57b12b1 + e81960f commit db6ab53

7 files changed

Lines changed: 99 additions & 27 deletions

File tree

Lib/_pydatetime.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -355,19 +355,30 @@ def _find_isoformat_datetime_separator(dtstr):
355355
return 8
356356

357357

358+
def _read_isoformat_component(s, n):
359+
# The caller has verified the string is ASCII, so isdigit() matches only
360+
# the ASCII digits accepted by the C parser.
361+
if len(s) != n or not s.isdigit():
362+
raise ValueError("Invalid isoformat string")
363+
return int(s)
364+
365+
358366
def _parse_isoformat_date(dtstr):
359367
# It is assumed that this is an ASCII-only string of lengths 7, 8 or 10,
360368
# see the comment on Modules/_datetimemodule.c:_find_isoformat_datetime_separator
361369
if len(dtstr) not in (7, 8, 10):
362370
raise ValueError("Invalid isoformat string")
363-
year = int(dtstr[0:4])
371+
if not dtstr.isascii():
372+
raise ValueError("Invalid isoformat string")
373+
374+
year = _read_isoformat_component(dtstr[0:4], 4)
364375
has_sep = dtstr[4] == '-'
365376

366377
pos = 4 + has_sep
367378
if dtstr[pos:pos + 1] == "W":
368379
# YYYY-?Www-?D?
369380
pos += 1
370-
weekno = int(dtstr[pos:pos + 2])
381+
weekno = _read_isoformat_component(dtstr[pos:pos + 2], 2)
371382
pos += 2
372383

373384
dayno = 1
@@ -377,17 +388,17 @@ def _parse_isoformat_date(dtstr):
377388

378389
pos += has_sep
379390

380-
dayno = int(dtstr[pos:pos + 1])
391+
dayno = _read_isoformat_component(dtstr[pos:pos + 1], 1)
381392

382393
return list(_isoweek_to_gregorian(year, weekno, dayno))
383394
else:
384-
month = int(dtstr[pos:pos + 2])
395+
month = _read_isoformat_component(dtstr[pos:pos + 2], 2)
385396
pos += 2
386397
if (dtstr[pos:pos + 1] == "-") != has_sep:
387398
raise ValueError("Inconsistent use of dash separator")
388399

389400
pos += has_sep
390-
day = int(dtstr[pos:pos + 2])
401+
day = _read_isoformat_component(dtstr[pos:pos + 2], 2)
391402

392403
return [year, month, day]
393404

@@ -402,10 +413,7 @@ def _parse_hh_mm_ss_ff(tstr):
402413
time_comps = [0, 0, 0, 0]
403414
pos = 0
404415
for comp in range(0, 3):
405-
if (len_str - pos) < 2:
406-
raise ValueError("Incomplete time component")
407-
408-
time_comps[comp] = int(tstr[pos:pos+2])
416+
time_comps[comp] = _read_isoformat_component(tstr[pos:pos+2], 2)
409417

410418
pos += 2
411419
next_char = tstr[pos:pos+1]
@@ -426,7 +434,7 @@ def _parse_hh_mm_ss_ff(tstr):
426434
raise ValueError("Invalid microsecond separator")
427435
else:
428436
pos += 1
429-
if not all(map(_is_ascii_digit, tstr[pos:])):
437+
if not tstr[pos:].isdigit():
430438
raise ValueError("Non-digit values in fraction")
431439

432440
len_remainder = len_str - pos
@@ -447,6 +455,8 @@ def _parse_isoformat_time(tstr):
447455
len_str = len(tstr)
448456
if len_str < 2:
449457
raise ValueError("Isoformat time too short")
458+
if not tstr.isascii():
459+
raise ValueError("Invalid isoformat string")
450460

451461
# This is equivalent to re.search('[+-Z]', tstr), but faster
452462
tz_pos = (tstr.find('-') + 1 or tstr.find('+') + 1 or tstr.find('Z') + 1)

Lib/curses/textpad.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,16 @@ def gather(self):
200200
result = ""
201201
self._update_max_yx()
202202
for y in range(self.maxy+1):
203-
self.win.move(y, 0)
204-
stop = self._end_of_line(y)
205-
if stop == 0 and self.stripspaces:
206-
continue
207-
for x in range(self.maxx+1):
208-
if self.stripspaces and x > stop:
209-
break
210-
result = result + str(self.win.in_wch(y, x))
203+
# The whole line: in_wstr() reads a double-width character once,
204+
# skipping the continuation cell that holds its other half.
205+
line = self.win.in_wstr(y, 0)
206+
if self.stripspaces:
207+
stripped = line.rstrip(' ')
208+
if not stripped:
209+
continue
210+
# Keep the blank the cursor rests on past the last character.
211+
line = stripped + ' ' if len(stripped) < len(line) else stripped
212+
result = result + line
211213
if self.maxy > 0:
212214
result = result + "\n"
213215
return result

Lib/test/datetimetester.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2106,7 +2106,15 @@ def test_fromisoformat_fails(self):
21062106
'10000-W25-1', # Invalid year
21072107
'2020-W25-0', # Invalid day-of-week
21082108
'2020-W25-8', # Invalid day-of-week
2109-
'٢025-03-09' # Unicode characters
2109+
# gh-152204: each fixed-width field must be exactly N ASCII digits
2110+
'2020+12', # '+' in a basic-format field
2111+
'2020 12', # space in a basic-format field
2112+
'+020-06-15', # leading sign in the year
2113+
'202012+9', # '+' in the day field
2114+
'2020-W 5', # space in the week number
2115+
'2020061', # 7 chars: day slice reads a 1-character tail
2116+
'2020-W2', # 1-digit week number
2117+
'٢025-03-09', # Unicode characters
21102118
'2009\ud80002\ud80028', # Separators are surrogate codepoints
21112119
]
21122120

@@ -3758,6 +3766,15 @@ def test_fromisoformat_fails_datetime(self):
37583766
'2009-04-19T12:30:45-00:90:00', # Time zone field out from range
37593767
'2009-04-19T12:30:45-00:00:90', # Time zone field out from range
37603768
'2020-2020', # Ambiguous 9-char date portion
3769+
# gh-152204: each time field must be exactly N ASCII digits
3770+
'2020-12-12T0٥:02:03', # Unicode digit in the hour
3771+
'2020-12-12T01:0٥:03', # Unicode digit in the minute
3772+
'2020-12-12T01:02:0٥', # Unicode digit in the second
3773+
'2020-12-12T01:02:03.٥', # Unicode digit in the fraction
3774+
'2020-12-12T01:02:03.4_6', # underscore in the fraction
3775+
'2020-12-12T01:02:03+0٥:00', # Unicode digit in the tz hour
3776+
'2020-12-12T01:02:03+01:0٥', # Unicode digit in the tz minute
3777+
'20201212T0102٣٤', # Unicode digits in the basic-format time
37613778
'2009-04-19T12:30:45.+05:00', # Empty fraction before offset
37623779
'2009-04-19T12:30:45.-05:00', # Empty fraction before offset
37633780
'2009-04-19T12:30:45.Z', # Empty fraction before Z

Lib/test/test_curses.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2694,6 +2694,23 @@ def test_textbox_combining(self):
26942694
box.do_command(ch)
26952695
self.assertEqual(box.gather(), text + ' ')
26962696

2697+
@requires_wide_build
2698+
def test_textbox_double_width(self):
2699+
# A double-width (East Asian) character occupies two cells. gather()
2700+
# reads a whole line at a time so that the second cell, which holds
2701+
# the same character, is not reported as another one.
2702+
text = '你好'
2703+
if not self._encodable(text):
2704+
self.skipTest('the locale cannot encode %r' % text)
2705+
box, win = self._make_textbox(1, 12)
2706+
for ch in text:
2707+
box.do_command(ch)
2708+
self.assertEqual(box.gather(), text + ' ')
2709+
box, win = self._make_textbox(1, 12, stripspaces=False)
2710+
for ch in text:
2711+
box.do_command(ch)
2712+
self.assertEqual(box.gather(), text + ' ' * 8)
2713+
26972714
def test_textbox_edit_wide(self):
26982715
# edit() reads characters through get_wch(). Each character is pushed
26992716
# with unget_wch(), which on a narrow build requires it to encode to a
@@ -2967,6 +2984,11 @@ def test_has_extended_color_support(self):
29672984
r = curses.has_extended_color_support()
29682985
self.assertIsInstance(r, bool)
29692986

2987+
def test_err_and_ok(self):
2988+
# ERR is negative; it is not a chtype constant.
2989+
self.assertEqual(curses.ERR, -1)
2990+
self.assertEqual(curses.OK, 0)
2991+
29702992
def test_type_names(self):
29712993
# The curses types report their public module rather than the
29722994
# underscore extension that implements them.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix the pure-Python implementations of :meth:`datetime.date.fromisoformat`,
2+
:meth:`datetime.time.fromisoformat` and :meth:`datetime.datetime.fromisoformat`
3+
silently accepting some malformed ISO 8601 strings, such as non-ASCII digits or
4+
a sign in a fixed-width field (for example ``'2020+12'`` or ``'20201212T0102٣٤'``).
5+
Each field is now required to be exactly *N* ASCII digits, matching the C
6+
implementation.

Modules/_cursesmodule.c

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6657,11 +6657,10 @@ curses_init_dict(PyObject *module)
66576657
}
66586658
/* This was moved from initcurses() because it core dumped on SGI,
66596659
where they're not defined until you've called initscr() */
6660-
/* Use long long, not long: a chtype constant (the A_* attributes, ACS_*
6661-
and key codes) can set bits beyond a 32-bit long, which is what long is
6662-
on LLP64 platforms such as Windows -- A_DIM (0x80000000) would otherwise
6663-
be sign-extended to a negative number. long long is at least 64 bits
6664-
everywhere and still represents the negative ERR (-1). */
6660+
/* Use unsigned long long, not long: a chtype constant (the A_* attributes,
6661+
ACS_* and key codes) can set bits beyond a 32-bit long, which is what
6662+
long is on LLP64 platforms such as Windows -- A_DIM (0x80000000) would
6663+
otherwise be sign-extended to a negative number. */
66656664
#define SetDictInt(NAME, VALUE) \
66666665
do { \
66676666
PyObject *value = PyLong_FromUnsignedLongLong((unsigned long long)(VALUE)); \
@@ -9419,8 +9418,24 @@ cursesmodule_exec(PyObject *module)
94199418
} \
94209419
} while (0)
94219420

9422-
SetDictInt("ERR", ERR);
9423-
SetDictInt("OK", OK);
9421+
/* ERR is -1, so it needs a signed conversion, unlike the chtype
9422+
constants below. */
9423+
#define SetDictSignedInt(NAME, VALUE) \
9424+
do { \
9425+
PyObject *value = PyLong_FromLongLong((long long)(VALUE)); \
9426+
if (value == NULL) { \
9427+
return -1; \
9428+
} \
9429+
int rc = PyDict_SetItemString(module_dict, (NAME), value); \
9430+
Py_DECREF(value); \
9431+
if (rc < 0) { \
9432+
return -1; \
9433+
} \
9434+
} while (0)
9435+
9436+
SetDictSignedInt("ERR", ERR);
9437+
SetDictSignedInt("OK", OK);
9438+
#undef SetDictSignedInt
94249439

94259440
/* Here are some attributes you can add to chars to print */
94269441

Platforms/emscripten/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Any data that can vary between Python versions is to be kept in this file.
22
# This allows for blanket copying of the Emscripten build code between supported
33
# Python versions.
4-
emscripten-version = "6.0.6"
4+
emscripten-version = "6.0.8"
55
node-version = "24"
66
test-args = [
77
"-m", "test",

0 commit comments

Comments
 (0)