assertSame('file_name_txt', Util::sanitizeFilename('file name.txt')); $this->assertSame('a_b_c_bc_123', Util::sanitizeFilename("a/b\\c:*?<>|\"\t\näbc 123")); $this->assertSame('_', Util::sanitizeFilename('äöü')); $this->assertSame('_', Util::sanitizeFilename('')); } public function testSafePathBasic(): void { $this->assertNull(Util::safePath('')); $this->assertNull(Util::safePath('/etc/passwd')); $this->assertNull(Util::safePath("foo\x01bar")); $this->assertNull(Util::safePath('a/../b')); $this->assertSame('./foo/bar', Util::safePath('foo/bar')); $this->assertSame('./foo/bar', Util::safePath('./foo/bar')); } public function testSafePathWithPrefix(): void { $this->assertSame('./data/file.txt', Util::safePath('data/file.txt', 'data')); $this->assertSame('./data/file.txt', Util::safePath('./data/file.txt', './data')); $this->assertNull(Util::safePath('other/file.txt', 'data')); } public function testReadableFileSize(): void { // Bytes $this->assertSame("0\xE2\x80\x89Byte", Util::readableFileSize(0)); $this->assertSame("999\xE2\x80\x89Byte", Util::readableFileSize(999)); // KiB $this->assertSame("1.00\xE2\x80\x89KiB", Util::readableFileSize(1024)); $this->assertSame("1.95\xE2\x80\x89KiB", Util::readableFileSize(2000)); // MiB with shift (pretend given is KiB) $this->assertSame("1.00\xE2\x80\x89MiB", Util::readableFileSize(1024, -1, 1)); } public function testVerifyToken(): void { // Not logged in and no session token -> allowed User::$loggedIn = false; Session::$store = []; // get('token') -> false $this->assertTrue(Util::verifyToken()); // Logged in with matching token -> allowed User::$loggedIn = true; Session::$store['token'] = 'abc'; $_REQUEST['token'] = 'abc'; $this->assertTrue(Util::verifyToken()); // Logged in with missing/invalid token -> error and false Message::reset(); $_REQUEST['token'] = 'xyz'; $this->assertFalse(Util::verifyToken()); $this->assertTrue(in_array('main.token', Message::$errors, true)); } public function testUploadErrorStringIncludesUnknown(): void { $this->assertSame('The uploaded file exceeds the upload_max_filesize directive in php.ini', Util::uploadErrorString(UPLOAD_ERR_INI_SIZE)); $this->assertSame('No file was uploaded', Util::uploadErrorString(UPLOAD_ERR_NO_FILE)); $this->assertSame('Unknown upload error', Util::uploadErrorString(9999)); } public function testIsPublicIpv4(): void { $this->assertTrue(Util::isPublicIpv4('8.8.8.8')); $this->assertFalse(Util::isPublicIpv4('10.0.0.1')); $this->assertFalse(Util::isPublicIpv4('192.168.1.1')); $this->assertFalse(Util::isPublicIpv4('172.16.0.1')); $this->assertTrue(Util::isPublicIpv4('172.15.0.1')); $this->assertTrue(Util::isPublicIpv4('172.32.0.1')); $this->assertFalse(Util::isPublicIpv4('127.0.0.1')); $this->assertFalse(Util::isPublicIpv4('0.0.0.0')); $this->assertFalse(Util::isPublicIpv4('256.0.0.1')); $this->assertFalse(Util::isPublicIpv4('1.2.3')); } public function testMarkupEscapesAndFormats(): void { $in = '*bold* _under_ /italics/ raw' . "\n" . 'next'; $out = Util::markup($in); $this->assertStringContainsString('bold', $out); $this->assertStringContainsString('under', $out); $this->assertStringContainsString('italics', $out); // raw HTML must be escaped $this->assertStringContainsString('<b>raw</b>', $out); // newline converted to
$this->assertStringContainsString('assertStringContainsString('today', $outToday); $yesterday = $now - 86400 + 60; // ensure within yesterday day window $outY = Util::prettyTime($yesterday); $this->assertStringContainsString('yesterday', $outY); $this->assertSame('Yes', Util::boolToString(true)); $this->assertSame('No', Util::boolToString(false)); } public function testFormatDuration(): void { $this->assertSame('1y 00:00:00', Util::formatDuration(31536000)); $this->assertSame('1y 01mon 00:00:00', Util::formatDuration(31536000 + 2592000)); $this->assertSame('1y 01mon 01d 00:00:00', Util::formatDuration(31536000 + 2592000 + 86400)); $this->assertSame('00:01:05', Util::formatDuration(65)); $this->assertSame('00:01', Util::formatDuration(65, false)); } public function testCleanUtf8AndAnsiToUtf8(): void { $bad = "Hello\xC3World\xFF"; // invalid sequences $clean = Util::cleanUtf8($bad); $this->assertStringContainsString('Hello', $clean); $this->assertStringNotContainsString("\xFF", $clean); $ansi = "Hi\x01\x02\x03Mehr"; // control chars removed $out = Util::ansiToUtf8($ansi); $this->assertStringNotContainsString("\x01", $out); $this->assertStringContainsString('HiMehr', $out); } public function testClamp(): void { $v = 5; Util::clamp($v, 1, 10); $this->assertSame(5, $v); $v = -1; Util::clamp($v, 1, 10); $this->assertSame(1, $v); $v = 99; Util::clamp($v, 1, 10); $this->assertSame(10, $v); $v = 5.5; Util::clamp($v, 1, 10, false); $this->assertIsFloat($v); $this->assertSame(5.5, $v); } public function testShouldRedirectDomain(): void { Property::$values['webinterface.redirect-domain'] = 1; Property::$values['webinterface.https-domains'] = 'admin.example.org *.example.org other.tld'; // Current host not matching -> expect first domain $_SERVER['HTTP_HOST'] = 'some.other.domain.com'; $this->assertSame('admin.example.org', Util::shouldRedirectDomain()); // Matches exact $_SERVER['HTTP_HOST'] = 'other.tld'; $this->assertNull(Util::shouldRedirectDomain()); // Matches wildcard $_SERVER['HTTP_HOST'] = 'sub.example.org'; $this->assertNull(Util::shouldRedirectDomain()); // Disabled Property::$values['webinterface.redirect-domain'] = 0; $_SERVER['HTTP_HOST'] = 'some.other.domain.com'; $this->assertNull(Util::shouldRedirectDomain()); } public function testRandomBytesAndUuid(): void { $rb = Util::randomBytes(32, true); $this->assertNotNull($rb); $this->assertSame(32, strlen($rb)); // UUID v4 format: xxxxxxxx-xxxx-4xxx-[89ab]xxx-xxxxxxxxxxxx for ($i = 0; $i < 10; $i++) { $uuid = Util::randomUuid(); $this->assertMatchesRegularExpression('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $uuid); } } public function testOsUptimeIfAvailable(): void { if (file_exists('/proc/uptime')) { $this->assertGreaterThan(0, Util::osUptime()); } else { $this->markTestSkipped('No /proc/uptime on this platform'); } } }