Skip to content
Open
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/private/AppFramework/Http/Output.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,34 @@ public function setReadfile($path) {
*/
#[\Override]
public function setHeader($header) {
// Apache mod_proxy_fcgi parses FCGI response headers with a buffer
// hardcoded at HUGE_STRING_LEN (8192 bytes in httpd.h); 7800 leaves
// a safety margin for the status line and surrounding header bytes.
$maxLen = 7800;
Comment on lines +49 to +52
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like this magic value, it should very likely just be 8192 or 8191.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7800 is indeed empirical. Just using 8192 isn't quite right though: HUGE_STRING_LEN is the buffer Apache reserves for the full header line (name + ": " + value + CRLF), so the value alone has to fit in roughly 8192 - 27 = 8165 bytes to stay safely under the parser's threshold.

We can express the intent in the code with:
private const HEADER_LINE_MAX = 8192; // Apache HUGE_STRING_LEN
private const HEADER_NAME_OVERHEAD = 32; // name + ": " + CRLF + margin

Or we could keep a single constant CSP_VALUE_MAX = 8000 (or any round number below 8165) with a comment pointing at HUGE_STRING_LEN

if (strlen($header) > $maxLen) {
foreach (['Content-Security-Policy:', 'Feature-Policy:'] as $prefix) {
if (str_starts_with($header, $prefix)) {
$value = ltrim(substr($header, strlen($prefix)));
$directives = array_filter(array_map(trim(...), explode(';', $value)));
$segment = '';
$first = true;
foreach ($directives as $directive) {
$candidate = $segment === '' ? $directive : $segment . ';' . $directive;
if (strlen($prefix . ' ' . $candidate . ';') > $maxLen && $segment !== '') {
header($prefix . ' ' . $segment . ';', $first);
$first = false;
$segment = $directive;
} else {
$segment = $candidate;
}
}
if ($segment !== '') {
header($prefix . ' ' . $segment . ';', $first);
}
Comment on lines +58 to +72
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is way too messy and I'm pretty sure it's also not doing its job correctly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The splitter has been running in production for quite some time on a NC32 instance with ~23 trusted servers (CSP ~6.3 kB, splits into two policies), with no observed regression on federated shares, iframes, or form submissions. The output of the production curl is available if useful.

return;
}
}
}
header($header);
}

Expand Down