parser = $this->createCookieParser(); } private function createCookieParser(?ParserConfig $config = null): CookieParser { // 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); return new CookieParser($config ?? new ParserConfig(), $cache); } public function testParseEmptyCookieHeader(): void { $result = $this->parser->parseCookieHeader(''); $this->assertSame([], $result); } public function testParseSingleCookie(): void { $result = $this->parser->parseCookieHeader('sessionId=abc123'); $this->assertSame(['sessionId' => 'abc123'], $result); } public function testParseMultipleCookies(): void { $result = $this->parser->parseCookieHeader('sessionId=abc123; userId=456; theme=dark'); $this->assertSame([ 'sessionId' => 'abc123', 'userId' => '456', 'theme' => 'dark', ], $result); } public function testParseUrlEncodedValues(): void { $result = $this->parser->parseCookieHeader('name=John%20Doe; email=test%40example.com'); $this->assertSame([ 'name' => 'John Doe', 'email' => 'test@example.com', ], $result); } public function testParseEmptyValues(): void { $result = $this->parser->parseCookieHeader('empty=; valid=value'); $this->assertSame([ 'empty' => '', 'valid' => 'value', ], $result); } public function testIgnoreInvalidPairs(): void { $result = $this->parser->parseCookieHeader('valid=value; invalid_no_equals; another=test'); $this->assertSame([ 'valid' => 'value', 'another' => 'test', ], $result); } public function testHandleExtraSpaces(): void { $result = $this->parser->parseCookieHeader(' key1 = value1 ; key2 = value2 '); $this->assertSame([ 'key1' => 'value1', 'key2' => 'value2', ], $result); } public function testParseSetCookieHeader(): void { $setCookie = 'sessionId=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Path=/; Domain=.example.com; Secure; HttpOnly; SameSite=Lax'; $result = $this->parser->parseSetCookieHeader($setCookie); $this->assertSame('sessionId', $result['name']); $this->assertSame('abc123', $result['value']); $this->assertSame('Wed, 09 Jun 2021 10:18:14 GMT', $result['expires']); $this->assertSame('/', $result['path']); $this->assertSame('.example.com', $result['domain']); $this->assertTrue($result['secure']); $this->assertTrue($result['httponly']); $this->assertSame('Lax', $result['samesite']); } public function testParseSetCookieWithMaxAge(): void { $setCookie = 'token=xyz789; Max-Age=3600; Path=/api'; $result = $this->parser->parseSetCookieHeader($setCookie); $this->assertSame('token', $result['name']); $this->assertSame('xyz789', $result['value']); $this->assertSame(3600, $result['max-age']); $this->assertSame('/api', $result['path']); } public function testParseSetCookieWithUrlEncodedValue(): void { $setCookie = 'data=hello%20world%21; Path=/'; $result = $this->parser->parseSetCookieHeader($setCookie); $this->assertSame('data', $result['name']); $this->assertSame('hello world!', $result['value']); } public function testParseInvalidSetCookieThrowsException(): void { $this->expectException(\InvalidArgumentException::class); $this->parser->parseSetCookieHeader('invalid_cookie_format'); } public function testParseToCookiesObject(): void { $cookies = $this->parser->parseToCookies('foo=bar; baz=qux'); $this->assertSame('bar', $cookies->get('foo')?->value); $this->assertSame('qux', $cookies->get('baz')?->value); $this->assertNull($cookies->get('nonexistent')); } // Security Tests public function testCookieCountLimitExceeded(): void { $config = new ParserConfig(maxCookieCount: 2); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Cookie count exceeded: 3 cookies > 2 maximum'); $parser->parseCookieHeader('cookie1=value1; cookie2=value2; cookie3=value3'); } public function testCookieNameTooLong(): void { $config = new ParserConfig(maxCookieNameLength: 10); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Cookie name too long'); $parser->parseCookieHeader('verylongcookiename=value'); } public function testCookieValueTooLong(): void { $config = new ParserConfig(maxCookieValueLength: 10); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Cookie value too long'); $parser->parseCookieHeader('cookie=verylongcookievaluethatexceedslimit'); } public function testMaliciousScriptInjectionDetected(): void { $config = new ParserConfig(scanForMaliciousContent: true); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Malicious content detected'); $parser->parseCookieHeader('evil='); } public function testMaliciousJavaScriptUrlDetected(): void { $config = new ParserConfig(scanForMaliciousContent: true); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Malicious content detected'); $parser->parseCookieHeader('redirect=javascript:alert("xss")'); } public function testMaliciousEventHandlerDetected(): void { $config = new ParserConfig(scanForMaliciousContent: true); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Malicious content detected'); $parser->parseCookieHeader('data=onclick=alert("xss")'); } public function testExcessiveUrlEncodingDetected(): void { $config = new ParserConfig(scanForMaliciousContent: true); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Excessive URL encoding detected'); $excessive = str_repeat('%20', 15); // More than 10 % characters $parser->parseCookieHeader("data={$excessive}"); } public function testControlCharactersDetected(): void { $config = new ParserConfig(scanForMaliciousContent: true); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Control characters detected'); $parser->parseCookieHeader("data=value\x00nullbyte"); } public function testCrlfInjectionDetected(): void { $config = new ParserConfig(scanForMaliciousContent: true); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Malicious content detected'); $parser->parseCookieHeader("data=value\r\nSet-Cookie: evil=injected"); } // Set-Cookie Security Tests public function testSetCookieNameTooLong(): void { $config = new ParserConfig(maxCookieNameLength: 5); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Cookie name too long'); $parser->parseSetCookieHeader('verylongname=value; Path=/'); } public function testSetCookieValueTooLong(): void { $config = new ParserConfig(maxCookieValueLength: 5); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Cookie value too long'); $parser->parseSetCookieHeader('name=verylongvalue; Path=/'); } public function testSetCookieMaliciousContent(): void { $config = new ParserConfig(scanForMaliciousContent: true); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Malicious content detected'); $parser->parseSetCookieHeader('evil=; Path=/'); } public function testMultipleSetCookieCountLimit(): void { $config = new ParserConfig(maxCookieCount: 2); $parser = $this->createCookieParser($config); $this->expectException(ParserSecurityException::class); $this->expectExceptionMessage('Cookie count exceeded'); $parser->parseSetCookieHeaders([ 'cookie1=value1; Path=/', 'cookie2=value2; Path=/', 'cookie3=value3; Path=/', ]); } // Security Configuration Tests public function testSecurityDisabled(): void { $config = new ParserConfig( scanForMaliciousContent: false, maxCookieCount: 1000, maxCookieNameLength: 1000, maxCookieValueLength: 1000 ); $parser = $this->createCookieParser($config); // Should not throw exception when security is disabled $result = $parser->parseCookieHeader('evil='); $this->assertSame(['evil' => ''], $result); } public function testWithinSecurityLimits(): void { $config = new ParserConfig( maxCookieCount: 5, maxCookieNameLength: 20, maxCookieValueLength: 50, scanForMaliciousContent: true ); $parser = $this->createCookieParser($config); // Should work fine within limits $result = $parser->parseCookieHeader('session=abc123; theme=dark; lang=en'); $this->assertSame([ 'session' => 'abc123', 'theme' => 'dark', 'lang' => 'en', ], $result); } }