bpo-29110: Fix file object leak in `aifc.open` when given invalid AIFF file. (GH-162)

diff --git a/Lib/aifc.py b/Lib/aifc.py
index 692d0bf..380adc8 100644
--- a/Lib/aifc.py
+++ b/Lib/aifc.py
@@ -344,9 +344,15 @@
 
     def __init__(self, f):
         if isinstance(f, str):
-            f = builtins.open(f, 'rb')
-        # else, assume it is an open file object already
-        self.initfp(f)
+            file_object = builtins.open(f, 'rb')
+            try:
+                self.initfp(file_object)
+            except:
+                file_object.close()
+                raise
+        else:
+            # assume it is an open file object already
+            self.initfp(f)
 
     def __enter__(self):
         return self
@@ -543,16 +549,19 @@
 
     def __init__(self, f):
         if isinstance(f, str):
-            filename = f
-            f = builtins.open(f, 'wb')
+            file_object = builtins.open(f, 'wb')
+            try:
+                self.initfp(file_object)
+            except:
+                file_object.close()
+                raise
+
+            # treat .aiff file extensions as non-compressed audio
+            if f.endswith('.aiff'):
+                self._aifc = 0
         else:
-            # else, assume it is an open file object already
-            filename = '???'
-        self.initfp(f)
-        if filename[-5:] == '.aiff':
-            self._aifc = 0
-        else:
-            self._aifc = 1
+            # assume it is an open file object already
+            self.initfp(f)
 
     def initfp(self, file):
         self._file = file
diff --git a/Lib/test/test_aifc.py b/Lib/test/test_aifc.py
index 1bd1f89..989df93 100644
--- a/Lib/test/test_aifc.py
+++ b/Lib/test/test_aifc.py
@@ -1,4 +1,4 @@
-from test.support import findfile, TESTFN, unlink
+from test.support import check_no_resource_warning, findfile, TESTFN, unlink
 import unittest
 from test import audiotests
 from audioop import byteswap
@@ -149,6 +149,14 @@
         #This file contains chunk types aifc doesn't recognize.
         self.f = aifc.open(findfile('Sine-1000Hz-300ms.aif'))
 
+    def test_close_opened_files_on_error(self):
+        non_aifc_file = findfile('pluck-pcm8.wav', subdir='audiodata')
+        with check_no_resource_warning(self):
+            with self.assertRaises(aifc.Error):
+                # Try opening a non-AIFC file, with the expectation that
+                # `aifc.open` will fail (without raising a ResourceWarning)
+                f = self.f = aifc.open(non_aifc_file, 'rb')
+
     def test_params_added(self):
         f = self.f = aifc.open(TESTFN, 'wb')
         f.aiff()
diff --git a/Misc/NEWS b/Misc/NEWS
index 4d1cf29..57e5ab9 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -242,6 +242,9 @@
 - bpo-29532: Altering a kwarg dictionary passed to functools.partial()
   no longer affects a partial object after creation.
 
+- bpo-29110: Fix file object leak in aifc.open() when file is given as a
+  filesystem path and is not in valid AIFF format. Patch by Anthony Zhang.
+
 - bpo-22807: Add uuid.SafeUUID and uuid.UUID.is_safe to relay information from
   the platform about whether generated UUIDs are generated with a
   multiprocessing safe method.