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