Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Lib/test/test_pyexpat.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,34 @@ def test_disabling_buffer(self):
parser.Parse(xml3, True)
self.assertEqual(self.n, 12)

def test_disable_buffer_in_character_handler(self):
parser = expat.ParserCreate()
parser.buffer_size = 2
parser.buffer_text = True
seen = []

def consume(text):
seen.append(text)
if len(seen) == 1:
parser.buffer_text = False

parser.CharacterDataHandler = consume
parser.Parse(b'<r>a&#65;bc</r>', True)
self.assertEqual(''.join(seen), 'aAbc')

def test_clear_element_handler_during_flush(self):
parser = expat.ParserCreate()
parser.buffer_text = True
elements = []

def consume(text):
parser.StartElementHandler = None

parser.CharacterDataHandler = consume
parser.StartElementHandler = lambda name, attrs: elements.append(name)
parser.Parse(b'<r>abc<s/></r>', True)
self.assertEqual(elements, ['r'])

def counting_handler(self, text):
self.n += 1

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix crashes in :mod:`xml.parsers.expat` when a character data callback
disables buffering or clears another handler during a flush, or when interning
an attribute name changes :attr:`~xml.parsers.expat.xmlparser.ordered_attributes`.
75 changes: 46 additions & 29 deletions Modules/pyexpat.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,9 @@ call_with_frame(const char *funcname, int lineno, PyObject* func, PyObject* args
{
PyObject *res;

Py_INCREF(func);
res = PyObject_Call(func, args, NULL);
Py_DECREF(func);
if (res == NULL) {
_PyTraceback_Add(funcname, __FILE__, lineno);
XML_StopParser(self->itself, XML_FALSE);
Expand Down Expand Up @@ -357,19 +359,25 @@ call_character_handler(xmlparseobject *self, const XML_Char *buffer, int len)
if (!have_handler(self, CharacterData))
return -1;

args = PyTuple_New(1);
if (args == NULL)
return -1;
temp = (conv_string_len_to_unicode(buffer, len));
/* Copy the buffer before tuple allocation can trigger a GC callback. */
temp = conv_string_len_to_unicode(buffer, len);
if (temp == NULL) {
Py_DECREF(args);
flag_error(self);
XML_SetCharacterDataHandler(self->itself,
noop_character_data_handler);
return -1;
}
args = PyTuple_New(1);
if (args == NULL) {
Py_DECREF(temp);
return -1;
}
PyTuple_SET_ITEM(args, 0, temp);
/* temp is now a borrowed reference; consider it unused. */
if (!have_handler(self, CharacterData)) {
Py_DECREF(args);
return 0;
}
self->in_callback = 1;
temp = call_with_frame("CharacterData", __LINE__,
self->handlers[CharacterData], args, self);
Expand All @@ -389,12 +397,13 @@ call_character_handler(xmlparseobject *self, const XML_Char *buffer, int len)
static int
flush_character_buffer(xmlparseobject *self)
{
int rc;
if (self->buffer == NULL || self->buffer_used == 0)
return 0;
rc = call_character_handler(self, self->buffer, self->buffer_used);
int len = self->buffer_used;
/* The callback may change buffering or install a new handler, which
also flushes. Do not expose the same pending data a second time. */
self->buffer_used = 0;
return rc;
return call_character_handler(self, self->buffer, len);
}

static void
Expand All @@ -405,27 +414,22 @@ my_CharacterDataHandler(void *userData, const XML_Char *data, int len)
if (PyErr_Occurred())
return;

if (self->buffer == NULL)
if (self->buffer != NULL &&
len > (self->buffer_size - self->buffer_used)) {
if (flush_character_buffer(self) < 0)
return;
/* The callback may replace the handler or disable buffering. */
if (!have_handler(self, CharacterData))
return;
}
if (self->buffer == NULL ||
len > (self->buffer_size - self->buffer_used)) {
call_character_handler(self, data, len);
}
else {
if (len > (self->buffer_size - self->buffer_used)) {
if (flush_character_buffer(self) < 0)
return;
/* handler might have changed; drop the rest on the floor
* if there isn't a handler anymore
*/
if (!have_handler(self, CharacterData))
return;
}
if (len > self->buffer_size) {
call_character_handler(self, data, len);
self->buffer_used = 0;
}
else {
memcpy(self->buffer + self->buffer_used,
data, len * sizeof(XML_Char));
self->buffer_used += len;
}
memcpy(self->buffer + self->buffer_used,
data, len * sizeof(XML_Char));
self->buffer_used += len;
}
}

Expand Down Expand Up @@ -456,7 +460,9 @@ my_StartElementHandler(void *userData,
max += 2;
}
/* Build the container. */
if (self->ordered_attributes)
/* Intern-dictionary comparisons can change the mode mid-loop. */
int ordered_attributes = self->ordered_attributes;
if (ordered_attributes)
container = PyList_New(max);
else
container = PyDict_New();
Expand All @@ -479,7 +485,7 @@ my_StartElementHandler(void *userData,
Py_DECREF(n);
return;
}
if (self->ordered_attributes) {
if (ordered_attributes) {
PyList_SET_ITEM(container, i, n);
PyList_SET_ITEM(container, i+1, v);
}
Expand All @@ -505,6 +511,10 @@ my_StartElementHandler(void *userData,
return;
}
/* Container is now a borrowed reference; ignore it. */
if (!have_handler(self, StartElement)) {
Py_DECREF(args);
return;
}
self->in_callback = 1;
rv = call_with_frame("StartElement", __LINE__,
self->handlers[StartElement], args, self);
Expand Down Expand Up @@ -561,6 +571,10 @@ my_ ## NAME ## Handler PARAMS { \
flag_error(self); \
return RETURN_VARIABLE; \
} \
if (!have_handler(self, NAME)) { \
Py_DECREF(args); \
return RETURN_VARIABLE; \
} \
self->in_callback = 1; \
PyObject *rv = call_with_frame( \
#NAME, __LINE__, \
Expand Down Expand Up @@ -709,6 +723,9 @@ my_ElementDeclHandler(void *userData,
flag_error(self);
goto finally;
}
if (!have_handler(self, ElementDecl)) {
goto finally;
}
self->in_callback = 1;
rv = call_with_frame("ElementDecl", __LINE__,
self->handlers[ElementDecl], args, self);
Expand Down
Loading