Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
251
tests/Framework/Http/Parser/HttpRequestParserSecurityTest.php
Normal file
251
tests/Framework/Http/Parser/HttpRequestParserSecurityTest.php
Normal file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Framework\Http\Parser;
|
||||
|
||||
use App\Framework\Cache\Compression\NullCompression;
|
||||
use App\Framework\Cache\CompressionCacheDecorator;
|
||||
use App\Framework\Cache\Driver\InMemoryCache;
|
||||
use App\Framework\Cache\GeneralCache;
|
||||
use App\Framework\Cache\Serializer\PhpSerializer;
|
||||
use App\Framework\Http\Parser\Exception\ParserSecurityException;
|
||||
use App\Framework\Http\Parser\HttpRequestParser;
|
||||
use App\Framework\Http\Parser\ParserCache;
|
||||
use App\Framework\Http\Parser\ParserConfig;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Security tests for HttpRequestParser
|
||||
* Tests body size limits, URI length limits, and integration security
|
||||
*/
|
||||
final class HttpRequestParserSecurityTest extends TestCase
|
||||
{
|
||||
private HttpRequestParser $parser;
|
||||
|
||||
private ParserConfig $strictConfig;
|
||||
|
||||
private ParserConfig $webConfig;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Strict security config for testing
|
||||
$this->strictConfig = new ParserConfig(
|
||||
maxTotalUploadSize: \App\Framework\Core\ValueObjects\Byte::fromBytes(1024), // 1KB limit
|
||||
maxFileSize: \App\Framework\Core\ValueObjects\Byte::fromBytes(500),
|
||||
maxFormDataSize: \App\Framework\Core\ValueObjects\Byte::fromBytes(500),
|
||||
maxQueryStringLength: 100,
|
||||
validateFileExtensions: true,
|
||||
scanForMaliciousContent: true,
|
||||
throwOnLimitExceeded: true,
|
||||
logSecurityViolations: false // Don't log during tests
|
||||
);
|
||||
|
||||
// Web-friendly config
|
||||
$this->webConfig = new ParserConfig(
|
||||
maxTotalUploadSize: \App\Framework\Core\ValueObjects\Byte::fromMegabytes(10),
|
||||
maxFileSize: \App\Framework\Core\ValueObjects\Byte::fromMegabytes(5),
|
||||
maxFormDataSize: \App\Framework\Core\ValueObjects\Byte::fromMegabytes(5),
|
||||
maxQueryStringLength: 8192,
|
||||
validateFileExtensions: false,
|
||||
scanForMaliciousContent: false,
|
||||
throwOnLimitExceeded: true,
|
||||
logSecurityViolations: false
|
||||
);
|
||||
|
||||
// Create parser cache with proper serialization
|
||||
$baseCache = new GeneralCache(new InMemoryCache(), new \App\Framework\Serializer\Php\PhpSerializer());
|
||||
$compressionCache = new CompressionCacheDecorator(
|
||||
$baseCache,
|
||||
new NullCompression(),
|
||||
new PhpSerializer()
|
||||
);
|
||||
$cache = new ParserCache($compressionCache);
|
||||
|
||||
$this->parser = new HttpRequestParser($cache, $this->strictConfig);
|
||||
}
|
||||
|
||||
public function testRequestBodySizeExceeded(): void
|
||||
{
|
||||
// Create a body larger than the 1KB limit
|
||||
$largeBody = str_repeat('a', 2048);
|
||||
|
||||
$this->expectException(ParserSecurityException::class);
|
||||
$this->expectExceptionMessage('Request body size exceeded: 2048 bytes > 1024 bytes maximum');
|
||||
|
||||
$this->parser->parseRequest('POST', '/test', [], $largeBody);
|
||||
}
|
||||
|
||||
public function testRequestBodySizeWithinLimits(): void
|
||||
{
|
||||
// Create a body smaller than the 1KB limit
|
||||
$smallBody = str_repeat('a', 500);
|
||||
|
||||
$request = $this->parser->parseRequest('POST', '/test', ['HTTP_CONTENT_TYPE' => 'text/plain'], $smallBody);
|
||||
|
||||
$this->assertEquals('/test', $request->path);
|
||||
$this->assertEquals($smallBody, $request->body);
|
||||
}
|
||||
|
||||
public function testUriTooLong(): void
|
||||
{
|
||||
// Create a URI longer than 4KB limit
|
||||
$longUri = '/test?' . str_repeat('param=value&', 500); // ~5KB
|
||||
|
||||
$this->expectException(ParserSecurityException::class);
|
||||
$this->expectExceptionMessage('URI too long:');
|
||||
|
||||
$this->parser->parseRequest('GET', $longUri, [], '');
|
||||
}
|
||||
|
||||
public function testUriWithinLimits(): void
|
||||
{
|
||||
// Normal length URI
|
||||
$normalUri = '/test?param1=value1¶m2=value2';
|
||||
|
||||
$request = $this->parser->parseRequest('GET', $normalUri, [], '');
|
||||
|
||||
$this->assertEquals('/test', $request->path);
|
||||
$this->assertEquals(['param1' => 'value1', 'param2' => 'value2'], $request->queryParams);
|
||||
}
|
||||
|
||||
public function testMultipartFormDataWithSizeLimit(): void
|
||||
{
|
||||
$boundary = 'test-boundary-123';
|
||||
$contentType = "multipart/form-data; boundary={$boundary}";
|
||||
|
||||
// Create multipart data that exceeds 1KB limit
|
||||
$multipartData = "--{$boundary}\r\n" .
|
||||
"Content-Disposition: form-data; name=\"field1\"\r\n\r\n" .
|
||||
str_repeat('large_value_', 200) . "\r\n" . // ~2.4KB of data
|
||||
"--{$boundary}--\r\n";
|
||||
|
||||
$this->expectException(ParserSecurityException::class);
|
||||
$this->expectExceptionMessage('Request body size exceeded');
|
||||
|
||||
$this->parser->parseRequest('POST', '/upload', [
|
||||
'HTTP_CONTENT_TYPE' => $contentType,
|
||||
], $multipartData);
|
||||
}
|
||||
|
||||
public function testFormDataWithSizeLimit(): void
|
||||
{
|
||||
$contentType = 'application/x-www-form-urlencoded';
|
||||
|
||||
// Create form data that exceeds 1KB limit
|
||||
$formData = 'field1=' . str_repeat('value_', 300); // ~1.8KB
|
||||
|
||||
$this->expectException(ParserSecurityException::class);
|
||||
$this->expectExceptionMessage('Request body size exceeded');
|
||||
|
||||
$this->parser->parseRequest('POST', '/form', [
|
||||
'HTTP_CONTENT_TYPE' => $contentType,
|
||||
], $formData);
|
||||
}
|
||||
|
||||
public function testWebConfigAllowsLargerRequests(): void
|
||||
{
|
||||
// Create parser cache for web test
|
||||
$baseCache = new GeneralCache(new InMemoryCache(), new \App\Framework\Serializer\Php\PhpSerializer());
|
||||
$compressionCache = new CompressionCacheDecorator(
|
||||
$baseCache,
|
||||
new NullCompression(),
|
||||
new PhpSerializer()
|
||||
);
|
||||
$cache = new ParserCache($compressionCache);
|
||||
|
||||
$webParser = new HttpRequestParser($cache, $this->webConfig);
|
||||
|
||||
// Create a 2KB body (within web config's 10MB limit)
|
||||
$body = str_repeat('a', 2048);
|
||||
|
||||
$request = $webParser->parseRequest('POST', '/test', [
|
||||
'HTTP_CONTENT_TYPE' => 'text/plain',
|
||||
], $body);
|
||||
|
||||
$this->assertEquals('/test', $request->path);
|
||||
$this->assertEquals($body, $request->body);
|
||||
}
|
||||
|
||||
public function testEmptyBodyIsAllowed(): void
|
||||
{
|
||||
$request = $this->parser->parseRequest('GET', '/test', [], '');
|
||||
|
||||
$this->assertEquals('/test', $request->path);
|
||||
$this->assertEquals('', $request->body);
|
||||
}
|
||||
|
||||
public function testSecurityIntegrationWithAllParsers(): void
|
||||
{
|
||||
// Test that security limits work across all sub-parsers
|
||||
$boundary = 'security-test-boundary';
|
||||
$contentType = "multipart/form-data; boundary={$boundary}";
|
||||
|
||||
// Create valid multipart data within limits
|
||||
$validData = "--{$boundary}\r\n" .
|
||||
"Content-Disposition: form-data; name=\"message\"\r\n\r\n" .
|
||||
"Hello World\r\n" .
|
||||
"--{$boundary}\r\n" .
|
||||
"Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n" .
|
||||
"Content-Type: text/plain\r\n\r\n" .
|
||||
"File content\r\n" .
|
||||
"--{$boundary}--\r\n";
|
||||
|
||||
$request = $this->parser->parseRequest('POST', '/upload?param=value', [
|
||||
'HTTP_CONTENT_TYPE' => $contentType,
|
||||
'HTTP_COOKIE' => 'session=abc123',
|
||||
], $validData);
|
||||
|
||||
$this->assertEquals('/upload', $request->path);
|
||||
$this->assertEquals(['param' => 'value'], $request->queryParams);
|
||||
$this->assertCount(1, $request->files->all());
|
||||
$this->assertEquals('abc123', $request->cookies->get('session')->value);
|
||||
}
|
||||
|
||||
public function testParseFromGlobalsWithSecurityLimits(): void
|
||||
{
|
||||
// Test that parseFromGlobals also applies security limits
|
||||
$largeBody = str_repeat('x', 2048);
|
||||
|
||||
$server = [
|
||||
'REQUEST_METHOD' => 'POST',
|
||||
'REQUEST_URI' => '/test',
|
||||
'HTTP_CONTENT_TYPE' => 'text/plain',
|
||||
];
|
||||
|
||||
$this->expectException(ParserSecurityException::class);
|
||||
$this->expectExceptionMessage('Request body size exceeded');
|
||||
|
||||
$this->parser->parseFromGlobals($server, $largeBody);
|
||||
}
|
||||
|
||||
public function testRawHttpRequestWithSecurityLimits(): void
|
||||
{
|
||||
// Test that parseRawHttpRequest also applies security limits
|
||||
$largeBody = str_repeat('y', 2048);
|
||||
|
||||
$rawRequest = "POST /test HTTP/1.1\r\n" .
|
||||
"Content-Type: text/plain\r\n" .
|
||||
"Content-Length: " . strlen($largeBody) . "\r\n" .
|
||||
"\r\n" .
|
||||
$largeBody;
|
||||
|
||||
$this->expectException(ParserSecurityException::class);
|
||||
$this->expectExceptionMessage('Request body size exceeded');
|
||||
|
||||
$this->parser->parseRawHttpRequest($rawRequest);
|
||||
}
|
||||
|
||||
public function testMethodOverrideWithSecurityLimits(): void
|
||||
{
|
||||
// Test that method override doesn't bypass security
|
||||
$largeData = '_method=PUT&data=' . str_repeat('value_', 300); // ~1.8KB
|
||||
|
||||
$this->expectException(ParserSecurityException::class);
|
||||
$this->expectExceptionMessage('Request body size exceeded');
|
||||
|
||||
$this->parser->parseRequest('POST', '/test', [
|
||||
'HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded',
|
||||
], $largeData);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user