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); } }