Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Fix memory leaks
- Fix `MimeMessage::extract_uue()` ignoring index greater than zero
- Fix use-after-free when a MIME part outlives its parent, or when a child part is freed before its parent. `mailparse_msg_free()` on a part that still belongs to a parent now returns true without freeing it; the part is freed with its root. (Firas Fatnassi, Oz Kaiser)
- Fix `MimeMessage::remove()` keeping the removed part alive until the end of the request.

# Version 3.2.0 - 2026-04-09

Expand Down
4 changes: 4 additions & 0 deletions php_mailparse_mime.c
Original file line number Diff line number Diff line change
Expand Up @@ -1026,8 +1026,12 @@ PHP_MAILPARSE_API void php_mimepart_remove_from_parent(php_mimepart *part)
mailparse_fetch_mimepart_resource(childpart, childpart_z);
if (childpart == part) {
zend_ulong h;
zval child_z;

ZVAL_COPY_VALUE(&child_z, childpart_z);
zend_hash_get_current_key_ex(&parent->children, NULL, &h, &pos);
zend_hash_index_del(&parent->children, h);
zval_ptr_dtor(&child_z);
break;
}
}
Expand Down
41 changes: 41 additions & 0 deletions tests/mimemessage_remove_releases_part.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
MimeMessage::remove() releases the removed part
--SKIPIF--
<?php
if (!extension_loaded("mailparse")) die("skip mailparse extension not available");
if (getenv("USE_ZEND_ALLOC") === "0") die("skip memory_get_usage() needs the Zend allocator");
?>
--FILE--
<?php
$data = "MIME-Version: 1.0\r\n"
. "Content-Type: multipart/mixed; boundary=x\r\n\r\n"
. "--x\r\nContent-Type: text/plain\r\n\r\none\r\n"
. "--x\r\nContent-Type: text/html\r\n\r\ntwo\r\n--x--\r\n";

$root = new MimeMessage('var', $data);
var_dump($root->get_child_count());
$child = $root->get_child(1);
$child->remove();
var_dump($root->get_child_count());
var_dump($child->get_parent());
var_dump($root->get_child(1)->data['content-type']);
unset($child, $root);

$warmup = new MimeMessage('var', $data);
$warmup->get_child(1)->remove();
unset($warmup);

$before = memory_get_usage();
for ($i = 0; $i < 100; $i++) {
$msg = new MimeMessage('var', $data);
$msg->get_child(1)->remove();
unset($msg);
}
var_dump(memory_get_usage() - $before);
?>
--EXPECT--
int(3)
int(2)
NULL
string(9) "text/html"
int(0)
Loading