-
Notifications
You must be signed in to change notification settings - Fork 2k
feat: make DebugToolbar smarter about detecting binary/streamed responses #9862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 4.7
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /** | ||
| * This file is part of CodeIgniter 4 framework. | ||
| * | ||
| * (c) CodeIgniter Foundation <[email protected]> | ||
| * | ||
| * For the full copyright and license information, please view | ||
| * the LICENSE file that was distributed with this source code. | ||
| */ | ||
|
|
||
| namespace CodeIgniter\Debug; | ||
|
|
||
| /** | ||
| * Class MockNativeHeaders | ||
| * | ||
| * This class serves as a container to hold the state of HTTP headers | ||
| * during unit testing. It allows the framework to simulate sending headers | ||
| * without actually outputting them to the CLI or browser. | ||
| */ | ||
| class MockNativeHeaders | ||
| { | ||
| /** | ||
| * Simulates the state of whether headers have been sent. | ||
| */ | ||
| public static bool $headersSent = false; | ||
|
|
||
| /** | ||
| * Stores the list of headers that have been sent. | ||
| */ | ||
| public static array $headers = []; | ||
|
|
||
| /** | ||
| * Resets the class state to defaults. | ||
| * Useful for cleaning up between individual tests. | ||
| */ | ||
| public static function reset(): void | ||
| { | ||
| self::$headersSent = false; | ||
| self::$headers = []; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Mock implementation of the native PHP headers_sent() function. | ||
| * | ||
| * Instead of checking the actual PHP output buffer, this function | ||
| * checks the static property in MockNativeHeaders. | ||
| * | ||
| * @return bool True if headers are considered sent, false otherwise. | ||
| */ | ||
| function headers_sent(): bool | ||
| { | ||
| return MockNativeHeaders::$headersSent; | ||
| } | ||
|
|
||
| /** | ||
| * Mock implementation of the native PHP headers_list() function. | ||
| * | ||
| * Retrieves the array of headers stored in the MockNativeHeaders class | ||
| * rather than the actual headers sent by the server. | ||
| * | ||
| * @return array The list of simulated headers. | ||
| */ | ||
| function headers_list(): array | ||
| { | ||
| return MockNativeHeaders::$headers; | ||
| } | ||
|
Comment on lines
+46
to
+70
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should go to the namespace CodeIgniter\Debug {
use CodeIgniter\Test\Utilities\NativeHeadersStack;
// our functions go here
}This way, if we decide we need to mock |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,8 @@ | |
| use PHPUnit\Framework\Attributes\BackupGlobals; | ||
| use PHPUnit\Framework\Attributes\Group; | ||
|
|
||
| require_once SUPPORTPATH . 'Debug/MockNativeHeaders.php'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I hate having Adding this to the |
||
|
|
||
| /** | ||
| * @internal | ||
| */ | ||
|
|
@@ -37,6 +39,9 @@ final class ToolbarTest extends CIUnitTestCase | |
| protected function setUp(): void | ||
| { | ||
| parent::setUp(); | ||
|
|
||
| MockNativeHeaders::reset(); | ||
|
|
||
| Services::reset(); | ||
|
|
||
| is_cli(false); | ||
|
|
@@ -99,4 +104,76 @@ public function testPrepareInjectsNormallyWithoutIgnoredHeader(): void | |
| // Assertions | ||
| $this->assertStringContainsString('id="debugbar_loader"', (string) $this->response->getBody()); | ||
| } | ||
|
|
||
| // ------------------------------------------------------------------------- | ||
| // Native Header Conflicts | ||
| // ------------------------------------------------------------------------- | ||
|
|
||
| public function testPrepareAbortsIfHeadersAlreadySent(): void | ||
| { | ||
| // Headers explicitly sent (e.g., echo before execution) | ||
| MockNativeHeaders::$headersSent = true; | ||
|
|
||
| $this->request = service('incomingrequest', null, false); | ||
| $this->response = service('response', null, false); | ||
| $this->response->setBody('<html><body>Content</body></html>'); | ||
|
|
||
| $toolbar = new Toolbar($this->config); | ||
| $toolbar->prepare($this->request, $this->response); | ||
|
|
||
| // Must NOT inject because we can't modify the body safely | ||
| $this->assertStringNotContainsString('id="debugbar_loader"', (string) $this->response->getBody()); | ||
| } | ||
|
|
||
| public function testPrepareAbortsIfNativeContentTypeIsNotHtml(): void | ||
| { | ||
| // A library (like Dompdf) set a PDF header directly | ||
| MockNativeHeaders::$headers = ['Content-Type: application/pdf']; | ||
|
|
||
| $this->request = service('incomingrequest', null, false); | ||
| $this->response = service('response', null, false); | ||
| // Even if the body looks like HTML (before rendering), the header says PDF | ||
| $this->response->setBody('<html><body>Raw PDF Data</body></html>'); | ||
|
|
||
| $toolbar = new Toolbar($this->config); | ||
| $toolbar->prepare($this->request, $this->response); | ||
|
|
||
| // Must NOT inject into non-HTML content | ||
| $this->assertStringNotContainsString('id="debugbar_loader"', (string) $this->response->getBody()); | ||
| } | ||
|
|
||
| public function testPrepareAbortsIfNativeContentDispositionIsAttachment(): void | ||
| { | ||
| // A file download (even if it is HTML) | ||
| MockNativeHeaders::$headers = [ | ||
| 'Content-Type: text/html', | ||
| 'Content-Disposition: attachment; filename="report.html"', | ||
| ]; | ||
|
|
||
| $this->request = service('incomingrequest', null, false); | ||
| $this->response = service('response', null, false); | ||
| $this->response->setBody('<html><body>Downloadable Report</body></html>'); | ||
|
|
||
| $toolbar = new Toolbar($this->config); | ||
| $toolbar->prepare($this->request, $this->response); | ||
|
|
||
| // Must NOT inject into downloads | ||
| $this->assertStringNotContainsString('id="debugbar_loader"', (string) $this->response->getBody()); | ||
| } | ||
|
|
||
| public function testPrepareWorksWithNativeHtmlHeader(): void | ||
| { | ||
| // Standard scenario where PHP header is text/html | ||
| MockNativeHeaders::$headers = ['Content-Type: text/html; charset=UTF-8']; | ||
|
|
||
| $this->request = service('incomingrequest', null, false); | ||
| $this->response = service('response', null, false); | ||
| $this->response->setBody('<html><body>Valid Page</body></html>'); | ||
|
|
||
| $toolbar = new Toolbar($this->config); | ||
| $toolbar->prepare($this->request, $this->response); | ||
|
|
||
| // Should inject normally | ||
| $this->assertStringContainsString('id="debugbar_loader"', (string) $this->response->getBody()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move it to the
system/test/Utilities/NativeHeadersStack.php. This class may be useful in case the developer builds something that requires testing native headers as well. For that reason, I would also consider addinghas()andpush()methods.