Skip to content

[3.0] End the search for an avatar at the last resort - #9587

Open
albertlast wants to merge 1 commit into
SimpleMachines:release-3.0from
albertlast:3.0/avatar-fallback-loop
Open

[3.0] End the search for an avatar at the last resort#9587
albertlast wants to merge 1 commit into
SimpleMachines:release-3.0from
albertlast:3.0/avatar-fallback-loop

Conversation

@albertlast

@albertlast albertlast commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Note

This change was produced by an LLM. The fix, the test, the commit message and
this description were all written by Claude (Anthropic), driven by @albertlast. It
has not yet had human code review.

Description

Avatar::__construct() looks for the member's image by trying each possibility in
turn, inside while (!$url->isValid()). The last possibility, reached when nothing
else produced anything, is a 1×1 transparent GIF as a data URI — there so that there
is always an answer:

// Last ditch fallback is a transparent 1x1 GIF.
default:
	$url = new Url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
	break;

Url::isValid() is filter_var($url, FILTER_VALIDATE_URL), which rejects a data
URI. So that answer never satisfies the condition the loop is testing, the switch
falls to default again on the next turn, and the loop never ends. The constructor
does not return, and the PHP worker running it spins until something kills it.

Reaching the last resort takes nothing exotic. The step before it is:

case 5:
	if (
		!empty(Config::$modSettings['avatar_url'])
		&& is_file(Config::$boarddir . '/avatars/default.png')
	) {

so a forum where avatar_url was never written — or one whose avatars/default.png
is not there — hangs on any member who has an avatar at all. Every page that shows
that member takes a worker with it.

The change is break 2 in place of break: at that point there is nothing further
to try, which is what the case is for.

Reproducing it

Without a forum, on release-3.0 as it stands:

require 'tests/bootstrap.php';

SMF\Config::$boardurl = 'https://example.com';
SMF\Config::$modSettings['gravatarEnabled'] = false;

new SMF\Avatar(url: 'Oxygen/beagle.png', id_member: 1);   // never returns

tests/Unit/AvatarFallbackTest.php is that, as a test. Against the unfixed
constructor it does not fail so much as never finish — phpunit --filter AvatarFallbackTest had to be killed at 40 seconds, having printed nothing past its
header. With the fix, both tests pass and the whole suite is back under a second.

The second test is the control: given an avatar_url, the same avatar is found the
ordinary way and the loop ends where it always did.

Notes for review

Issues References (Fixes|Related|Closes)

Related to #9586, #9588.

The Avatar constructor tries each way of finding an image in turn, inside a
loop that runs until it holds a URL that Url::isValid() accepts. The last of
those ways is a 1x1 transparent GIF as a data URI, which is there so that there
is always an answer. A data URI is not a URL that filter_var() will validate,
so that answer never satisfied the condition, and the loop went round again,
and again, until the request was killed.

Reaching the last resort takes nothing exotic. The step before it only produces
a URL when avatar_url is set, so a forum where that setting was never written
hangs a PHP worker on every member who has an avatar.

Leaving the loop rather than the switch is the whole change: at that point
there is nothing further to try, which is what the case is for.

Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant