[2.7] closes bpo-32997: Fix REDOS in fpformat (GH-5984)

The regex to decode a number in fpformat is susceptible to catastrophic backtracking. This is a potential DOS vector if a server is using fpformat on untrusted number strings.

Replace it with an equivalent non-vulnerable regex. The match behavior of the new regex is slightly different. It captures the whole integer part of the number in one group, Leading zeros are stripped off later.
diff --git a/Lib/fpformat.py b/Lib/fpformat.py
index 71cbb25..0537a27 100644
--- a/Lib/fpformat.py
+++ b/Lib/fpformat.py
@@ -19,7 +19,7 @@
 __all__ = ["fix","sci","NotANumber"]
 
 # Compiled regular expression to "decode" a number
-decoder = re.compile(r'^([-+]?)0*(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
+decoder = re.compile(r'^([-+]?)(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
 # \0 the whole thing
 # \1 leading sign or empty
 # \2 digits left of decimal point
@@ -41,6 +41,7 @@
     res = decoder.match(s)
     if res is None: raise NotANumber, s
     sign, intpart, fraction, exppart = res.group(1,2,3,4)
+    intpart = intpart.lstrip('0');
     if sign == '+': sign = ''
     if fraction: fraction = fraction[1:]
     if exppart: expo = int(exppart[1:])
diff --git a/Lib/test/test_fpformat.py b/Lib/test/test_fpformat.py
index e6de3b0..428623e 100644
--- a/Lib/test/test_fpformat.py
+++ b/Lib/test/test_fpformat.py
@@ -67,6 +67,16 @@
         else:
             self.fail("No exception on non-numeric sci")
 
+    def test_REDOS(self):
+        # This attack string will hang on the old decoder pattern.
+        attack = '+0' + ('0' * 1000000) + '++'
+        digs = 5 # irrelevant
+
+        # fix returns input if it does not decode
+        self.assertEqual(fpformat.fix(attack, digs), attack)
+        # sci raises NotANumber
+        with self.assertRaises(NotANumber):
+            fpformat.sci(attack, digs)
 
 def test_main():
     run_unittest(FpformatTest)