security = VSecurity::getInstance(); // Clear any existing session data if (isset($_SESSION)) { $_SESSION = []; } // Clear superglobals for clean testing $_GET = []; $_POST = []; } protected function tearDown(): void { // Clean up after each test if (isset($_SESSION)) { $_SESSION = []; } $_GET = []; $_POST = []; } /** * Test input validation with various data types */ public function testInputValidationWithEdgeCases() { // Test integer validation $this->assertEquals(123, VSecurity::validateInput('123', 'int')); $this->assertEquals(0, VSecurity::validateInput('0', 'int')); $this->assertEquals(-123, VSecurity::validateInput('-123', 'int')); $this->assertNull(VSecurity::validateInput('abc', 'int')); $this->assertNull(VSecurity::validateInput('123.45', 'int')); $this->assertNull(VSecurity::validateInput('999999999999999999999', 'int')); // Test integer with min/max constraints $this->assertEquals(50, VSecurity::validateInput('50', 'int', null, ['min' => 10, 'max' => 100])); $this->assertNull(VSecurity::validateInput('5', 'int', null, ['min' => 10, 'max' => 100])); $this->assertNull(VSecurity::validateInput('150', 'int', null, ['min' => 10, 'max' => 100])); // Test float validation $this->assertEquals(123.45, VSecurity::validateInput('123.45', 'float')); $this->assertEquals(0.0, VSecurity::validateInput('0.0', 'float')); $this->assertNull(VSecurity::validateInput('abc', 'float')); // Test email validation $this->assertEquals('test@example.com', VSecurity::validateInput('test@example.com', 'email')); $this->assertEquals('user+tag@domain.co.uk', VSecurity::validateInput('user+tag@domain.co.uk', 'email')); $this->assertNull(VSecurity::validateInput('invalid-email', 'email')); $this->assertNull(VSecurity::validateInput('test@', 'email')); $this->assertNull(VSecurity::validateInput('@example.com', 'email')); // Test URL validation $this->assertEquals('https://example.com', VSecurity::validateInput('https://example.com', 'url')); $this->assertEquals('http://localhost:8080/path', VSecurity::validateInput('http://localhost:8080/path', 'url')); $this->assertNull(VSecurity::validateInput('not-a-url', 'url')); $this->assertNull(VSecurity::validateInput('ftp://invalid', 'url')); // Test alpha validation $this->assertEquals('abcDEF', VSecurity::validateInput('abcDEF', 'alpha')); $this->assertEquals('test', VSecurity::validateInput('test123!@#', 'alpha')); $this->assertNull(VSecurity::validateInput('123', 'alpha')); // Test alphanum validation $this->assertEquals('abc123', VSecurity::validateInput('abc123', 'alphanum')); $this->assertEquals('test123', VSecurity::validateInput('test123!@#', 'alphanum')); $this->assertNull(VSecurity::validateInput('!@#', 'alphanum')); // Test slug validation $this->assertEquals('test-slug_123', VSecurity::validateInput('test-slug_123', 'slug')); $this->assertEquals('test-slug', VSecurity::validateInput('test-slug!@#', 'slug')); // Test filename validation $this->assertEquals('test.txt', VSecurity::validateInput('test.txt', 'filename')); $this->assertEquals('file_name.pdf', VSecurity::validateInput('file_name.pdf', 'filename')); $this->assertEquals('test.txt', VSecurity::validateInput('test/path.txt', 'filename')); // Test boolean validation $this->assertTrue(VSecurity::validateInput('true', 'boolean')); $this->assertTrue(VSecurity::validateInput('1', 'boolean')); $this->assertTrue(VSecurity::validateInput('yes', 'boolean')); $this->assertFalse(VSecurity::validateInput('false', 'boolean')); $this->assertFalse(VSecurity::validateInput('0', 'boolean')); $this->assertFalse(VSecurity::validateInput('no', 'boolean')); // Test string validation with length constraints $this->assertEquals('test', VSecurity::validateInput('test', 'string', null, ['min_length' => 2, 'max_length' => 10])); $this->assertNull(VSecurity::validateInput('a', 'string', null, ['min_length' => 2, 'max_length' => 10])); $this->assertNull(VSecurity::validateInput('verylongstring', 'string', null, ['min_length' => 2, 'max_length' => 10])); } /** * Test GET parameter handling */ public function testGetParameterHandling() { $_GET['test_int'] = '123'; $_GET['test_string'] = 'hello world'; $_GET['test_email'] = 'test@example.com'; $_GET['test_invalid'] = 'invalid_email'; $this->assertEquals(123, VSecurity::getParam('test_int', 'int')); $this->assertEquals('hello world', VSecurity::getParam('test_string', 'string')); $this->assertEquals('test@example.com', VSecurity::getParam('test_email', 'email')); $this->assertEquals('default', VSecurity::getParam('test_invalid', 'email', 'default')); $this->assertNull(VSecurity::getParam('nonexistent', 'string')); $this->assertEquals('default', VSecurity::getParam('nonexistent', 'string', 'default')); } /** * Test POST parameter handling */ public function testPostParameterHandling() { $_POST['username'] = 'testuser'; $_POST['age'] = '25'; $_POST['email'] = 'user@example.com'; $_POST['malicious'] = ''; $this->assertEquals('testuser', VSecurity::postParam('username', 'string')); $this->assertEquals(25, VSecurity::postParam('age', 'int')); $this->assertEquals('user@example.com', VSecurity::postParam('email', 'email')); $this->assertEquals('<script>alert("xss")</script>', VSecurity::postParam('malicious', 'string')); $this->assertNull(VSecurity::postParam('nonexistent', 'string')); } /** * Test CSRF token generation and validation */ public function testCSRFProtection() { // Test token generation $token1 = VSecurity::generateCSRFToken('test_action'); $this->assertIsString($token1); $this->assertEquals(64, strlen($token1)); // 32 bytes = 64 hex chars // Test token validation $this->assertTrue(VSecurity::validateCSRFToken($token1, 'test_action')); // Test invalid token $this->assertFalse(VSecurity::validateCSRFToken('invalid_token', 'test_action')); // Test token is one-time use $this->assertFalse(VSecurity::validateCSRFToken($token1, 'test_action')); // Test different actions have different tokens $token2 = VSecurity::generateCSRFToken('different_action'); $this->assertNotEquals($token1, $token2); // Test default action $defaultToken = VSecurity::generateCSRFToken(); $this->assertTrue(VSecurity::validateCSRFToken($defaultToken, 'default')); } /** * Test CSRF field generation */ public function testCSRFFieldGeneration() { $field = VSecurity::getCSRFField('test_form'); $this->assertStringContainsString('assertStringContainsString('name="csrf_token"', $field); $this->assertStringContainsString('value="', $field); // Extract token from field preg_match('/value="([^"]+)"/', $field, $matches); $token = $matches[1] ?? ''; $this->assertNotEmpty($token); $this->assertEquals(64, strlen($token)); } /** * Test CSRF validation from POST data */ public function testCSRFValidationFromPost() { $token = VSecurity::generateCSRFToken('form_submit'); $_POST['csrf_token'] = $token; $this->assertTrue(VSecurity::validateCSRFFromPost('form_submit')); // Test with invalid token $_POST['csrf_token'] = 'invalid_token'; $this->assertFalse(VSecurity::validateCSRFFromPost('form_submit')); // Test with missing token unset($_POST['csrf_token']); $this->assertFalse(VSecurity::validateCSRFFromPost('form_submit')); } /** * Test output escaping */ public function testOutputEscaping() { $maliciousInput = ''; $escaped = VSecurity::escapeOutput($maliciousInput); $this->assertEquals('<script>alert("xss")</script>', $escaped); // Test with quotes and ampersands $input = 'Hello "world" & '; $escaped = VSecurity::escapeOutput($input); $this->assertEquals('Hello "world" & <friends>', $escaped); } /** * Test JavaScript escaping */ public function testJavaScriptEscaping() { $input = 'Hello "world" & ', 'javascript:alert("XSS")', '', '', '">', '\';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//-->">\'>' ]; foreach ($xssPayloads as $payload) { $sanitized = VSecurity::validateInput($payload, 'string'); // Should not contain dangerous elements $this->assertStringNotContainsString('