View Issue Details

IDProjectCategoryView StatusLast Update
0037393mantisbtplug-inspublic2026-09-17 06:22
Reporter123 Assigned Tocommunity  
PrioritynormalSeveritymajorReproducibilityalways
Status assignedResolutionopen 
Product Version2.27.1 
Summary0037393: plugin_file.php sends a gzip body with 304 Not Modified, which strict HTTP servers (Caddy) reject
Description

plugin_file_include() (core/plugin_api.php) answers a matching If-Modified-Since with 304 Not Modified and no output. However, compress_handler_is_enabled() (core/compress_api.php) has already switched on zlib.output_compression via ini_set(), and PHP's zlib output handler emits a gzip stream (20 bytes: header, empty deflate block, trailer) even for an empty buffer. The raw response from php-fpm is:

Status: 304 Not Modified
Last-Modified: Wed, 16 Sep 2026 06:51:05 GMT
Content-Encoding: gzip
Vary: Accept-Encoding

<20 bytes of gzip>
RFC 9110 ยง15.4.5 forbids a body on 304. Apache and nginx silently drop it, so this went unnoticed. Caddy (Go net/http) refuses to write it and aborts the response:

{"level":"warn","logger":"http.handlers.reverse_proxy","msg":"aborting with incomplete response",
"uri":"/plugin_file.php?file=Calendar/calendar_filter.js",
"error":"writing: http: request method or response status code does not allow body"}
In the browser every plugin CSS/JS asset then fails with net::ERR_HTTP2_PROTOCOL_ERROR and the page renders unstyled. Since plugin_file.php sends Cache-Control: private, max-age=10800, the browser starts revalidating with If-Modified-Since about three hours after the first visit, so a MantisBT behind Caddy loses all plugin assets a few hours after each hard reload. Every plugin that serves files through plugin_file.php is affected.

Steps To Reproduce:

Additional Information:

Steps To Reproduce

MantisBT 2.28.4 (the code in master is the same), PHP 8.4 php-fpm, $g_compress_html = ON (default), zlib.output_compression = Off in php.ini (default), served by Caddy over HTTP/2.
Take any plugin file, e.g. plugin_file.php?file=Calendar/calendar_filter.js, and note its Last-Modified.
Request it with a matching If-Modified-Since and gzip accepted:

curl --http2 -H 'Accept-Encoding: gzip' \
-H 'If-Modified-Since: Wed, 16 Sep 2026 06:51:05 GMT' \
https://mantis.example/plugin_file.php?file=Calendar/calendar_filter.js
Expected: 304 with an empty body. Actual: curl: (92) HTTP/2 stream 1 was not closed cleanly; with --http1.1 the connection is closed without a response. Without Accept-Encoding the same request returns a correct 304.

Talking to php-fpm directly shows the offending body:

SCRIPT_FILENAME=/path/to/mantisbt/plugin_file.php SCRIPT_NAME=/plugin_file.php \
REQUEST_METHOD=GET QUERY_STRING='file=Calendar/calendar_filter.js' \
HTTP_ACCEPT_ENCODING=gzip HTTP_IF_MODIFIED_SINCE='Wed, 16 Sep 2026 06:51:05 GMT' \
cgi-fcgi -bind -connect /run/php/php8.4-fpm.sock | od -c

Additional Information

Fix: compress_disable() only clears $g_compression_started, which has no effect on the zlib.output_compression path. Make it also turn zlib.output_compression off while the headers are still unsent, and call it in plugin_file_include() before sending the 304. Turning zlib.output_compression off at runtime keeps already-buffered output and flushes it uncompressed, so the other caller (error_api.php) is unaffected.

Verified on PHP 8.4 / php-fpm / Caddy: the 304 is sent with an empty body, a stale If-Modified-Since still returns 200 with the file, regular pages are still gzip-compressed.

Pull request: https://github.com/mantisbt/mantisbt/pull/2284

TagsNo tags attached.

Relationships

related to 0035199 closedcommunity Improvement of the file_get_mime_type() function 

Activities

dregad

dregad

2026-09-17 06:22

developer   ~0071453

Looks like a regression introduced by 0035199