| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -317,10 +317,13 @@ def parsedate_to_datetime(data): | |||
| 317 | 317 | if parsed_date_tz is None: | |
| 318 | 318 | raise ValueError('Invalid date value or format "%s"' % str(data)) | |
| 319 | 319 | *dtuple, tz = parsed_date_tz | |
| 320 | - if tz is None: | ||
| 321 | - return datetime.datetime(*dtuple[:6]) | ||
| 322 | - return datetime.datetime(*dtuple[:6], | ||
| 323 | - tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) | ||
| 320 | + try: | ||
| 321 | + if tz is None: | ||
| 322 | + return datetime.datetime(*dtuple[:6]) | ||
| 323 | + return datetime.datetime(*dtuple[:6], | ||
| 324 | + tzinfo=datetime.timezone(datetime.timedelta(seconds=tz))) | ||
| 325 | + except OverflowError as exc: | ||
| 326 | + raise ValueError('Invalid date value or format "%s"' % str(data)) from exc | ||
| 324 | 327 | ||
| 325 | 328 | ||
| 326 | 329 | def parseaddr(addr, *, strict=True): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -157,6 +157,9 @@ def reset(self): | |||
| 157 | 157 | self.cdata_elem = None | |
| 158 | 158 | self._support_cdata = True | |
| 159 | 159 | self._escapable = True | |
| 160 | + self._pending = [] | ||
| 161 | + self._pending_len = 0 | ||
| 162 | + self._parse_threshold = 1 | ||
| 160 | 163 | super().reset() | |
| 161 | 164 | ||
| 162 | 165 | def feed(self, data): | |
@@ -165,11 +168,36 @@ def feed(self, data): | |||
| 165 | 168 | Call this as often as you want, with as little or as much text | |
| 166 | 169 | as you want (may include '\n'). | |
| 167 | 170 | """ | |
| 168 | - self.rawdata = self.rawdata + data | ||
| 169 | - self.goahead(0) | ||
| 171 | + # Accumulate new data in a list and only join and parse it once | ||
| 172 | + # enough has piled up. Rescanning an unparsed buffer (e.g. an | ||
| 173 | + # unterminated tag) and concatenating onto it on every call would | ||
| 174 | + # both be quadratic in the input size. | ||
| 175 | + self._pending_len += len(data) | ||
| 176 | + if self._pending_len < self._parse_threshold: | ||
| 177 | + self._pending.append(data) | ||
| 178 | + else: | ||
| 179 | + if not self._pending: | ||
| 180 | + self.rawdata += data | ||
| 181 | + else: | ||
| 182 | + self._pending.append(data) | ||
| 183 | + self.rawdata += ''.join(self._pending) | ||
| 184 | + self._pending.clear() | ||
| 185 | + self._pending_len = 0 | ||
| 186 | + n = len(self.rawdata) | ||
| 187 | + self.goahead(0) | ||
| 188 | + if len(self.rawdata) < n: | ||
| 189 | + # Some data was parsed; resume on the next call. | ||
| 190 | + self._parse_threshold = 1 | ||
| 191 | + else: | ||
| 192 | + # Nothing was parsed; wait until the buffer doubles. | ||
| 193 | + self._parse_threshold = len(self.rawdata) | ||
| 170 | 194 | ||
| 171 | 195 | def close(self): | |
| 172 | 196 | """Handle any buffered data.""" | |
| 197 | + if self._pending: | ||
| 198 | + self.rawdata += ''.join(self._pending) | ||
| 199 | + self._pending.clear() | ||
| 200 | + self._pending_len = 0 | ||
| 173 | 201 | self.goahead(1) | |
| 174 | 202 | ||
| 175 | 203 | __starttag_text = None | |
@@ -387,9 +415,11 @@ def parse_html_declaration(self, i): | |||
| 387 | 415 | def parse_comment(self, i, report=True): | |
| 388 | 416 | rawdata = self.rawdata | |
| 389 | 417 | assert rawdata.startswith('<!--', i), 'unexpected call to parse_comment()' | |
| 390 | - match = commentclose.search(rawdata, i+4) | ||
| 418 | + # An empty comment is abruptly closed by the first ">" or "->", | ||
| 419 | + # taking priority over a later "-->" or "--!>" close. | ||
| 420 | + match = commentabruptclose.match(rawdata, i+4) | ||
| 391 | 421 | if not match: | |
| 392 | - match = commentabruptclose.match(rawdata, i+4) | ||
| 422 | + match = commentclose.search(rawdata, i+4) | ||
| 393 | 423 | if not match: | |
| 394 | 424 | return -1 | |
| 395 | 425 | if report: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -42,6 +42,10 @@ def test_hsv_values(self): | |||
| 42 | 42 | self.assertTripleEqual(hsv, colorsys.rgb_to_hsv(*rgb)) | |
| 43 | 43 | self.assertTripleEqual(rgb, colorsys.hsv_to_rgb(*hsv)) | |
| 44 | 44 | ||
| 45 | + # test 360 phase shift in hue | ||
| 46 | + h, s, v = hsv | ||
| 47 | + self.assertTripleEqual(rgb, colorsys.hsv_to_rgb(h + 1.0, s, v)) | ||
| 48 | + | ||
| 45 | 49 | def test_hls_roundtrip(self): | |
| 46 | 50 | for r in frange(0.0, 1.0, 0.2): | |
| 47 | 51 | for g in frange(0.0, 1.0, 0.2): | |
@@ -89,6 +93,18 @@ def test_yiq_roundtrip(self): | |||
| 89 | 93 | colorsys.yiq_to_rgb(*colorsys.rgb_to_yiq(*rgb)) | |
| 90 | 94 | ) | |
| 91 | 95 | ||
| 96 | + def test_yiq_to_rgb_clamping(self): | ||
| 97 | + values = [ | ||
| 98 | + # rgb, yiq (invalid YIQ values clamped to RGB range) | ||
| 99 | + ((1.0, 0.0, 1.0), (0.0, 0.5, 1.0)), | ||
| 100 | + ((0.0, 1.0, 0.0), (0.25, -1.0, -1.0)), | ||
| 101 | + ((0.0, 0.0, 1.0), (0.0, -1.0, 0.5)) | ||
| 102 | + ] | ||
| 103 | + | ||
| 104 | + for (rgb, yiq) in values: | ||
| 105 | + with self.subTest(rgb=rgb, yiq=yiq): | ||
| 106 | + self.assertTripleEqual(rgb, colorsys.yiq_to_rgb(*yiq)) | ||
| 107 | + | ||
| 92 | 108 | def test_yiq_values(self): | |
| 93 | 109 | values = [ | |
| 94 | 110 | # rgb, yiq | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -223,6 +223,14 @@ def test_invalid_date_value(self): | |||
| 223 | 223 | self.assertEqual(len(h.defects), 1) | |
| 224 | 224 | self.assertIsInstance(h.defects[0], errors.InvalidDateDefect) | |
| 225 | 225 | ||
| 226 | + def test_out_of_range_date_value(self): | ||
| 227 | + s = 'Mon, 20 Nov 9999999999 12:00:00 +0000' | ||
| 228 | + h = self.make_header('date', s) | ||
| 229 | + self.assertEqual(h, s) | ||
| 230 | + self.assertIsNone(h.datetime) | ||
| 231 | + self.assertEqual(len(h.defects), 1) | ||
| 232 | + self.assertIsInstance(h.defects[0], errors.InvalidDateDefect) | ||
| 233 | + | ||
| 226 | 234 | def test_datetime_read_only(self): | |
| 227 | 235 | h = self.make_header('date', self.datestring) | |
| 228 | 236 | with self.assertRaises(AttributeError): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -77,6 +77,15 @@ def test_parsedate_to_datetime_with_invalid_raises_valueerror(self): | |||
| 77 | 77 | with self.subTest(dtstr=dtstr): | |
| 78 | 78 | self.assertRaises(ValueError, utils.parsedate_to_datetime, dtstr) | |
| 79 | 79 | ||
| 80 | + def test_parsedate_to_datetime_out_of_range_raises_valueerror(self): | ||
| 81 | + out_of_range_dates = [ | ||
| 82 | + 'Mon, 20 Nov 9999999999 12:00:00 +0000', | ||
| 83 | + 'Mon, 20 Nov 2017 12:00:00 +24000000000000', | ||
| 84 | + ] | ||
| 85 | + for dtstr in out_of_range_dates: | ||
| 86 | + with self.subTest(dtstr=dtstr): | ||
| 87 | + self.assertRaises(ValueError, utils.parsedate_to_datetime, dtstr) | ||
| 88 | + | ||
| 80 | 89 | class LocaltimeTests(unittest.TestCase): | |
| 81 | 90 | ||
| 82 | 91 | def test_localtime_is_tz_aware_daylight_true(self): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -149,6 +149,18 @@ def test_getopt(self): | |||
| 149 | 149 | ('-a', ''), ('--alpha', '')]) | |
| 150 | 150 | self.assertEqual(args, ['arg1', 'arg2']) | |
| 151 | 151 | ||
| 152 | + # Allow string for single long argument | ||
| 153 | + opts, args = getopt.getopt(cmdline, 'a::', 'alpha=?') | ||
| 154 | + self.assertEqual(opts, [('-a', '1'), ('--alpha', '2'), ('--alpha', ''), | ||
| 155 | + ('-a', ''), ('--alpha', '')]) | ||
| 156 | + self.assertEqual(args, ['arg1', 'arg2']) | ||
| 157 | + | ||
| 158 | + # Pass everything after -- as args | ||
| 159 | + cmdline = ['-a1', '--alpha=2', '--', '-b', '--beta=5'] | ||
| 160 | + opts, args = getopt.getopt(cmdline, 'a:b', ['alpha=', 'beta']) | ||
| 161 | + self.assertEqual(opts, [('-a', '1'), ('--alpha', '2')]) | ||
| 162 | + self.assertEqual(args, ['-b', '--beta=5']) | ||
| 163 | + | ||
| 152 | 164 | self.assertError(getopt.getopt, cmdline, 'a:b', ['alpha', 'beta']) | |
| 153 | 165 | ||
| 154 | 166 | def test_gnu_getopt(self): | |
@@ -191,13 +203,36 @@ def test_gnu_getopt(self): | |||
| 191 | 203 | self.assertEqual(args, ['arg1', '-b', '1', '--alpha', '--beta=2', | |
| 192 | 204 | '--beta', '3', 'arg2']) | |
| 193 | 205 | ||
| 206 | + # Allow string for single long argument | ||
| 207 | + opts, args = getopt.gnu_getopt(cmdline, 'ab:', 'alpha') | ||
| 208 | + self.assertEqual(opts, [('-a', '')]) | ||
| 209 | + self.assertEqual(args, ['arg1', '-b', '1', '--alpha', '--beta=2', | ||
| 210 | + '--beta', '3', 'arg2']) | ||
| 211 | + | ||
| 212 | + # Pass everything after -- as args | ||
| 213 | + cmdline = ['-a1', '--alpha=2', '--', '-b', '--beta=5'] | ||
| 214 | + opts, args = getopt.gnu_getopt(cmdline, 'a:b', ['alpha=', 'beta']) | ||
| 215 | + self.assertEqual(opts, [('-a', '1'), ('--alpha', '2')]) | ||
| 216 | + self.assertEqual(args, ['-b', '--beta=5']) | ||
| 217 | + | ||
| 218 | + # In order arguments | ||
| 219 | + cmdline = ["gamma", "--alpha=3"] | ||
| 220 | + opts, args = getopt.gnu_getopt(cmdline, '-', ["alpha="]) | ||
| 221 | + self.assertEqual(opts, [(None, ['gamma']), ('--alpha', '3')]) | ||
| 222 | + self.assertEqual(args, []) | ||
| 223 | + | ||
| 224 | + | ||
| 194 | 225 | def test_issue4629(self): | |
| 195 | 226 | longopts, shortopts = getopt.getopt(['--help='], '', ['help=']) | |
| 196 | 227 | self.assertEqual(longopts, [('--help', '')]) | |
| 197 | 228 | longopts, shortopts = getopt.getopt(['--help=x'], '', ['help=']) | |
| 198 | 229 | self.assertEqual(longopts, [('--help', 'x')]) | |
| 199 | 230 | self.assertRaises(getopt.GetoptError, getopt.getopt, ['--help='], '', ['help']) | |
| 200 | 231 | ||
| 232 | + def test_getopt_error_str(self): | ||
| 233 | + error = getopt.GetoptError('option -a not recognized', 'a') | ||
| 234 | + self.assertEqual(str(error), 'option -a not recognized') | ||
| 235 | + | ||
| 201 | 236 | def test_libref_examples(): | |
| 202 | 237 | """ | |
| 203 | 238 | Examples from the Library Reference: Doc/lib/libgetopt.tex | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -116,6 +116,11 @@ def _run_check(self, source, expected_events, | |||
| 116 | 116 | *, collector=None, convert_charrefs=False): | |
| 117 | 117 | if collector is None: | |
| 118 | 118 | collector = self.get_collector(convert_charrefs=convert_charrefs) | |
| 119 | + if isinstance(source, str): | ||
| 120 | + # Also feed the whole string at once, not just character by | ||
| 121 | + # character (below), to exercise different input buffering. | ||
| 122 | + self._run_check([source], expected_events, | ||
| 123 | + convert_charrefs=convert_charrefs) | ||
| 119 | 124 | parser = collector | |
| 120 | 125 | for s in source: | |
| 121 | 126 | parser.feed(s) | |
@@ -593,6 +598,9 @@ def test_comments(self): | |||
| 593 | 598 | '<!-- <!-- nested --> -->' | |
| 594 | 599 | '<!--<!-->' | |
| 595 | 600 | '<!--<!--!>' | |
| 601 | + # abruptly closed empty comment must not swallow later text | ||
| 602 | + '<!-->x-->' | ||
| 603 | + '<!--->y-->' | ||
| 596 | 604 | ) | |
| 597 | 605 | expected = [('comment', " I'm a valid comment "), | |
| 598 | 606 | ('comment', 'me too!'), | |
@@ -613,6 +621,8 @@ def test_comments(self): | |||
| 613 | 621 | ('comment', ' <!-- nested '), ('data', ' -->'), | |
| 614 | 622 | ('comment', '<!'), | |
| 615 | 623 | ('comment', '<!'), | |
| 624 | + ('comment', ''), ('data', 'x-->'), | ||
| 625 | + ('comment', ''), ('data', 'y-->'), | ||
| 616 | 626 | ] | |
| 617 | 627 | self._run_check(html, expected) | |
| 618 | 628 | ||
@@ -1031,6 +1041,26 @@ def check(source): | |||
| 1031 | 1041 | check("<![CDATA[" * 9 * n) | |
| 1032 | 1042 | check("<!doctype" * 35 * n) | |
| 1033 | 1043 | ||
| 1044 | + @support.requires_resource('cpu') | ||
| 1045 | + def test_incremental_no_quadratic_complexity(self): | ||
| 1046 | + # An unterminated construct fed in many small chunks used to take | ||
| 1047 | + # quadratic time, both to rescan and to concatenate the buffer. | ||
| 1048 | + # Now it takes a fraction of a second. | ||
| 1049 | + def check(prefix, chunk, suffix): | ||
| 1050 | + parser = html.parser.HTMLParser() | ||
| 1051 | + parser.feed(prefix) | ||
| 1052 | + for _ in range(200_000): | ||
| 1053 | + parser.feed(chunk) | ||
| 1054 | + parser.feed(suffix) | ||
| 1055 | + parser.close() | ||
| 1056 | + chunk = "a" * 64 | ||
| 1057 | + check("<!--", chunk, "-->") # comment | ||
| 1058 | + check("<?", chunk, ">") # processing instruction | ||
| 1059 | + check("<!doctype ", chunk, ">") # doctype | ||
| 1060 | + check("<![CDATA[", chunk, "]]>") # CDATA section | ||
| 1061 | + check("<a href='", chunk, "'>") # start tag | ||
| 1062 | + check("<script>", chunk, "</script>") # RAWTEXT element | ||
| 1063 | + | ||
| 1034 | 1064 | ||
| 1035 | 1065 | class AttributesTestCase(TestCaseBase): | |
| 1036 | 1066 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments