bpo-30264: ExpatParser now closes the source (#1476) · python/cpython@d81f9e2

2 files changed

lines changed

Original file line numberDiff line numberDiff line change

@@ -2,7 +2,8 @@

22

# $Id$

33
44

from xml.sax import make_parser, ContentHandler, \

5-

SAXException, SAXReaderNotAvailable, SAXParseException

5+

SAXException, SAXReaderNotAvailable, SAXParseException, \

6+

saxutils

67

try:

78

make_parser()

89

except SAXReaderNotAvailable:

@@ -173,6 +174,21 @@ def test_parse_InputSource(self):

173174

input.setEncoding('iso-8859-1')

174175

self.check_parse(input)

175176
177+

def test_parse_close_source(self):

178+

builtin_open = open

179+

non_local = {'fileobj': None}

180+
181+

def mock_open(*args):

182+

fileobj = builtin_open(*args)

183+

non_local['fileobj'] = fileobj

184+

return fileobj

185+
186+

with support.swap_attr(saxutils, 'open', mock_open):

187+

make_xml_file(self.data, 'iso-8859-1', None)

188+

with self.assertRaises(SAXException):

189+

self.check_parse(TESTFN)

190+

self.assertTrue(non_local['fileobj'].closed)

191+
176192

def check_parseString(self, s):

177193

from xml.sax import parseString

178194

result = StringIO()

Original file line numberDiff line numberDiff line change

@@ -105,9 +105,16 @@ def parse(self, source):

105105

source = saxutils.prepare_input_source(source)

106106
107107

self._source = source

108-

self.reset()

109-

self._cont_handler.setDocumentLocator(ExpatLocator(self))

110-

xmlreader.IncrementalParser.parse(self, source)

108+

try:

109+

self.reset()

110+

self._cont_handler.setDocumentLocator(ExpatLocator(self))

111+

xmlreader.IncrementalParser.parse(self, source)

112+

except:

113+

# bpo-30264: Close the source on error to not leak resources:

114+

# xml.sax.parse() doesn't give access to the underlying parser

115+

# to the caller

116+

self._close_source()

117+

raise

111118
112119

def prepareParser(self, source):

113120

if source.getSystemId() is not None:

@@ -216,6 +223,17 @@ def feed(self, data, isFinal = 0):

216223

# FIXME: when to invoke error()?

217224

self._err_handler.fatalError(exc)

218225
226+

def _close_source(self):

227+

source = self._source

228+

try:

229+

file = source.getCharacterStream()

230+

if file is not None:

231+

file.close()

232+

finally:

233+

file = source.getByteStream()

234+

if file is not None:

235+

file.close()

236+
219237

def close(self):

220238

if (self._entity_stack or self._parser is None or

221239

isinstance(self._parser, _ClosedParser)):

@@ -235,6 +253,7 @@ def close(self):

235253

parser.ErrorColumnNumber = self._parser.ErrorColumnNumber

236254

parser.ErrorLineNumber = self._parser.ErrorLineNumber

237255

self._parser = parser

256+

self._close_source()

238257
239258

def _reset_cont_handler(self):

240259

self._parser.ProcessingInstructionHandler = \