generated from jric11/baseProject
Initial commit
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Test;
|
||||
|
||||
class A0FileNoForceCurlTest extends TestUtil
|
||||
{
|
||||
/**
|
||||
* Covers the early getUrlData() guard when allow_url_fopen is enabled and
|
||||
* FORCE_CURL is not defined yet.
|
||||
*
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testGetUrlDataReturnsFalseWhenAllowUrlFopenEnabledWithoutForceCurl(): void
|
||||
{
|
||||
if (\defined('FORCE_CURL')) {
|
||||
$this->markTestSkipped('FORCE_CURL already defined by another test class');
|
||||
}
|
||||
|
||||
if (\ini_get('allow_url_fopen') === false || \ini_get('allow_url_fopen') === '0') {
|
||||
$this->markTestSkipped('allow_url_fopen is disabled in this environment');
|
||||
}
|
||||
|
||||
$file = new \Com\Tecnick\File\File(['example.com']);
|
||||
$this->assertFalse($file->getUrlData('http://example.com/path.txt'));
|
||||
}
|
||||
}
|
||||
+435
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ByteTest.php
|
||||
*
|
||||
* @since 2015-07-28
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*
|
||||
* This file is part of tc-lib-file software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
/**
|
||||
* Byte Color class test
|
||||
*
|
||||
* @since 2015-07-28
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*/
|
||||
class ByteTest extends TestUtil
|
||||
{
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
protected function getTestObject(): \Com\Tecnick\File\Byte
|
||||
{
|
||||
$str =
|
||||
\chr(0)
|
||||
. \chr(0)
|
||||
. \chr(0)
|
||||
. \chr(0)
|
||||
. \chr(1)
|
||||
. \chr(3)
|
||||
. \chr(7)
|
||||
. \chr(15)
|
||||
. \chr(31)
|
||||
. \chr(63)
|
||||
. \chr(127)
|
||||
. \chr(255)
|
||||
. \chr(254)
|
||||
. \chr(252)
|
||||
. \chr(248)
|
||||
. \chr(240)
|
||||
. \chr(224)
|
||||
. \chr(192)
|
||||
. \chr(128)
|
||||
. \chr(0)
|
||||
. \chr(255)
|
||||
. \chr(255)
|
||||
. \chr(255)
|
||||
. \chr(255);
|
||||
return new \Com\Tecnick\File\Byte($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
#[DataProvider('getByteDataProvider')]
|
||||
public function testGetByte(int $offset, int $expected): void
|
||||
{
|
||||
$byte = $this->getTestObject();
|
||||
$res = $byte->getByte($offset);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{int, int}>
|
||||
*/
|
||||
public static function getByteDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[0, 0],
|
||||
[1, 0],
|
||||
[2, 0],
|
||||
[3, 0],
|
||||
[4, 1],
|
||||
[5, 3],
|
||||
[6, 7],
|
||||
[7, 15],
|
||||
[8, 31],
|
||||
[9, 63],
|
||||
[10, 127],
|
||||
[11, 255],
|
||||
[12, 254],
|
||||
[13, 252],
|
||||
[14, 248],
|
||||
[15, 240],
|
||||
[16, 224],
|
||||
[17, 192],
|
||||
[18, 128],
|
||||
[19, 0],
|
||||
[20, 255],
|
||||
[21, 255],
|
||||
[22, 255],
|
||||
[23, 255],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
#[DataProvider('getUShortDataProvider')]
|
||||
public function testGetUShort(int $offset, int $expected): void
|
||||
{
|
||||
$byte = $this->getTestObject();
|
||||
$res = $byte->getUShort($offset);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
#[DataProvider('getUShortDataProvider')]
|
||||
public function testGetUFWord(int $offset, int $expected): void
|
||||
{
|
||||
$byte = $this->getTestObject();
|
||||
$res = $byte->getUFWord($offset);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{int, int}>
|
||||
*/
|
||||
public static function getUShortDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[0, 0],
|
||||
[1, 0],
|
||||
[2, 0],
|
||||
[3, 1],
|
||||
[4, 259],
|
||||
[5, 775],
|
||||
[6, 1_807],
|
||||
[7, 3_871],
|
||||
[8, 7_999],
|
||||
[9, 16_255],
|
||||
[10, 32_767],
|
||||
[11, 65_534],
|
||||
[12, 65_276],
|
||||
[13, 64_760],
|
||||
[14, 63_728],
|
||||
[15, 61_664],
|
||||
[16, 57_536],
|
||||
[17, 49_280],
|
||||
[18, 32_768],
|
||||
[19, 255],
|
||||
[20, 65_535],
|
||||
[21, 65_535],
|
||||
[22, 65_535],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
#[DataProvider('getShortDataProvider')]
|
||||
public function testGetShort(int $offset, int $expected): void
|
||||
{
|
||||
$byte = $this->getTestObject();
|
||||
$res = $byte->getShort($offset);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
#[DataProvider('getShortDataProvider')]
|
||||
public function testGetFWord(int $offset, int $expected): void
|
||||
{
|
||||
$byte = $this->getTestObject();
|
||||
$res = $byte->getFWord($offset);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{int, int}>
|
||||
*/
|
||||
public static function getShortDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[0, 0],
|
||||
[1, 0],
|
||||
[2, 0],
|
||||
[3, 1],
|
||||
[4, 259],
|
||||
[5, 775],
|
||||
[6, 1_807],
|
||||
[7, 3_871],
|
||||
[8, 7_999],
|
||||
[9, 16_255],
|
||||
[10, 32_767],
|
||||
[11, -2],
|
||||
[12, -260],
|
||||
[13, -776],
|
||||
[14, -1_808],
|
||||
[15, -3_872],
|
||||
[16, -8_000],
|
||||
[17, -16_256],
|
||||
[18, -32_768],
|
||||
[19, 255],
|
||||
[20, -1],
|
||||
[21, -1],
|
||||
[22, -1],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
#[DataProvider('getULongDataProvider')]
|
||||
public function testGetULong(int $offset, int $expected): void
|
||||
{
|
||||
$byte = $this->getTestObject();
|
||||
$res = $byte->getULong($offset);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{int, int}>
|
||||
*/
|
||||
public static function getULongDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
[2, 259],
|
||||
[3, 66_311],
|
||||
[4, 16_975_631],
|
||||
[5, 50_794_271],
|
||||
[6, 118_431_551],
|
||||
[7, 253_706_111],
|
||||
[8, 524_255_231],
|
||||
[9, 1_065_353_214],
|
||||
[10, 2_147_483_388],
|
||||
[11, 4_294_900_984],
|
||||
[12, 4_277_991_664],
|
||||
[13, 4_244_173_024],
|
||||
[14, 4_176_535_744],
|
||||
[15, 4_041_261_184],
|
||||
[16, 3_770_712_064],
|
||||
[17, 3_229_614_335],
|
||||
[18, 2_147_549_183],
|
||||
[19, 16_777_215],
|
||||
[20, 4_294_967_295],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
#[DataProvider('getLongDataProvider')]
|
||||
public function testGetLong(int $offset, int $expected): void
|
||||
{
|
||||
$byte = $this->getTestObject();
|
||||
$res = $byte->getLong($offset);
|
||||
$this->assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{int, int}>
|
||||
*/
|
||||
public static function getLongDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
[2, 259],
|
||||
[3, 66_311],
|
||||
[4, 16_975_631],
|
||||
[5, 50_794_271],
|
||||
[6, 118_431_551],
|
||||
[7, 253_706_111],
|
||||
[8, 524_255_231],
|
||||
[9, 1_065_353_214],
|
||||
[10, 2_147_483_388],
|
||||
[11, -66_312],
|
||||
[12, -16_975_632],
|
||||
[13, -50_794_272],
|
||||
[14, -118_431_552],
|
||||
[15, -253_706_112],
|
||||
[16, -524_255_232],
|
||||
[17, -1_065_352_961],
|
||||
[18, -2_147_418_113],
|
||||
[19, 16_777_215],
|
||||
[20, -1],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
#[DataProvider('getFixedDataProvider')]
|
||||
public function testGetFixed(int $offset, int|float $expected): void
|
||||
{
|
||||
$byte = $this->getTestObject();
|
||||
$res = $byte->getFixed($offset);
|
||||
// compare floats with a small tolerance to avoid precision issues
|
||||
$this->assertEqualsWithDelta($expected, $res, 1e-12, "float mismatch at offset {$offset}");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{int, float}>
|
||||
*/
|
||||
public static function getFixedDataProvider(): array
|
||||
{
|
||||
return [
|
||||
// high and low parts from the test string; see getShort/getUShort providers
|
||||
// offset 0: all zero bytes
|
||||
[0, 0.0],
|
||||
// offset 1: high=0, low=1 -> 1/65536
|
||||
[1, 1.52587890625E-5],
|
||||
// offset 2: high=0, low=259 -> 259/65536
|
||||
[2, 0.0039520263671875],
|
||||
// offset 3: high=1, low=775 -> 1 + 775/65536
|
||||
[3, 1.0118255615234375],
|
||||
// a negative result with fractional part
|
||||
[11, -1.0118408203125],
|
||||
// large positive value near next integer
|
||||
[19, 255.99998474121094],
|
||||
// negative value very close to zero (tiny fraction)
|
||||
[20, -1.52587890625E-5],
|
||||
];
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Issue 7: out-of-bounds reads — always throw exceptions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testGetByteOutOfBoundsThrows(): void
|
||||
{
|
||||
$byte = new \Com\Tecnick\File\Byte('AB'); // 2 bytes
|
||||
$this->expectException(\RangeException::class);
|
||||
$this->expectExceptionMessageMatches('/Out-of-bounds read/');
|
||||
$byte->getByte(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testGetUShortOutOfBoundsThrows(): void
|
||||
{
|
||||
$byte = new \Com\Tecnick\File\Byte('A'); // 1 byte only
|
||||
$this->expectException(\RangeException::class);
|
||||
$byte->getUShort(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testGetULongOutOfBoundsThrows(): void
|
||||
{
|
||||
$byte = new \Com\Tecnick\File\Byte('ABC'); // 3 bytes
|
||||
$this->expectException(\RangeException::class);
|
||||
$byte->getULong(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testGetShortOutOfBoundsThrows(): void
|
||||
{
|
||||
$byte = new \Com\Tecnick\File\Byte('A');
|
||||
$this->expectException(\RangeException::class);
|
||||
$byte->getShort(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testGetFixedOutOfBoundsThrows(): void
|
||||
{
|
||||
$byte = new \Com\Tecnick\File\Byte('AB'); // needs 4 bytes
|
||||
$this->expectException(\RangeException::class);
|
||||
$byte->getFixed(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testGetUFWordOutOfBoundsThrows(): void
|
||||
{
|
||||
$byte = new \Com\Tecnick\File\Byte('A'); // 1 byte, needs 2
|
||||
$this->expectException(\RangeException::class);
|
||||
$this->expectExceptionMessageMatches('/Out-of-bounds read/');
|
||||
$byte->getUFWord(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testGetFWordOutOfBoundsThrows(): void
|
||||
{
|
||||
$byte = new \Com\Tecnick\File\Byte('A'); // 1 byte, needs 2
|
||||
$this->expectException(\RangeException::class);
|
||||
$this->expectExceptionMessageMatches('/Out-of-bounds read/');
|
||||
$byte->getFWord(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testGetLongOutOfBoundsThrows(): void
|
||||
{
|
||||
$byte = new \Com\Tecnick\File\Byte('ABC'); // 3 bytes, needs 4
|
||||
$this->expectException(\RangeException::class);
|
||||
$this->expectExceptionMessageMatches('/Out-of-bounds read/');
|
||||
$byte->getLong(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RangeException
|
||||
*/
|
||||
public function testInBoundsReadNoWarning(): void
|
||||
{
|
||||
// A read exactly at the boundary must not warn.
|
||||
$byte = new \Com\Tecnick\File\Byte("\x00\x01");
|
||||
$this->assertSame(0, $byte->getByte(0));
|
||||
$this->assertSame(1, $byte->getByte(1));
|
||||
}
|
||||
}
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CacheTest.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-filecache
|
||||
*
|
||||
* This file is part of tc-lib-pdf-filecache software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use PHPUnit\Framework\Attributes\PreserveGlobalState;
|
||||
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
|
||||
|
||||
/**
|
||||
* Unit Test
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-filecache
|
||||
*/
|
||||
class CacheTest extends TestUtil
|
||||
{
|
||||
protected function getTestObject(): \Com\Tecnick\File\Cache
|
||||
{
|
||||
return new \Com\Tecnick\File\Cache('1_2-a+B/c');
|
||||
}
|
||||
|
||||
public function testAutoPrefix(): void
|
||||
{
|
||||
$cache = new \Com\Tecnick\File\Cache();
|
||||
$this->assertNotEmpty($cache->getFilePrefix());
|
||||
}
|
||||
|
||||
public function testGetCachePath(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
$cachePath = $cache->getCachePath();
|
||||
// The normalized cache path always ends with the platform separator.
|
||||
$this->assertSame(\DIRECTORY_SEPARATOR, \substr($cachePath, -1));
|
||||
|
||||
$cache->setCachePath();
|
||||
$this->assertEquals($cachePath, $cache->getCachePath());
|
||||
|
||||
// Use the real temp dir and compare realpath-to-realpath so the
|
||||
// assertion holds on every platform (e.g. macOS /var -> /private/var
|
||||
// symlink, Windows backslash separators).
|
||||
$path = \sys_get_temp_dir();
|
||||
$real = \realpath($path);
|
||||
if ($real === false) {
|
||||
self::fail('the system temp dir must resolve');
|
||||
}
|
||||
|
||||
$cache->setCachePath($path);
|
||||
$this->assertSame($real . \DIRECTORY_SEPARATOR, $cache->getCachePath());
|
||||
}
|
||||
|
||||
public function testGetFilePrefix(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
$filePrefix = $cache->getFilePrefix();
|
||||
$this->assertEquals('_1_2-a-B_c_', $filePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testGetNewFileName(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
$val = $cache->getNewFileName('tst', '0123');
|
||||
$this->bcAssertMatchesRegularExpression('/_1_2-a-B_c_tst_0123_/', $val);
|
||||
\unlink($val);
|
||||
}
|
||||
|
||||
/**
|
||||
* The full prefix, type and key must survive in the generated filename so
|
||||
* the prefix-based glob in delete()/deleteOlderThan() matches on every
|
||||
* platform. tempnam() alone truncates the prefix to 3 chars on Windows.
|
||||
*
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testGetNewFileNamePreservesFullPrefix(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
$fileA = $cache->getNewFileName('typ', 'k1');
|
||||
$fileB = $cache->getNewFileName('typ', 'k2');
|
||||
|
||||
try {
|
||||
$this->assertNotSame($fileA, $fileB, 'each call must return a distinct file');
|
||||
$this->assertTrue(\file_exists($fileA));
|
||||
$this->assertTrue(\file_exists($fileB));
|
||||
$this->assertStringStartsWith($cache->getFilePrefix() . 'typ_k1_', \basename($fileA));
|
||||
$this->assertStringStartsWith($cache->getFilePrefix() . 'typ_k2_', \basename($fileB));
|
||||
} finally {
|
||||
if (\is_file($fileA)) {
|
||||
\unlink($fileA);
|
||||
}
|
||||
|
||||
if (\is_file($fileB)) {
|
||||
\unlink($fileB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Characters that are invalid in Windows filenames (and glob metacharacters)
|
||||
* are stripped from the generated name, keeping it valid and consistent with
|
||||
* the patterns delete() builds.
|
||||
*
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testGetNewFileNameSanitizesUnsafeTypeAndKey(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
$file = $cache->getNewFileName('a/b*', 'c:d?');
|
||||
|
||||
try {
|
||||
$this->assertStringStartsWith($cache->getFilePrefix() . 'ab_cd_', \basename($file));
|
||||
$this->assertTrue(\file_exists($file));
|
||||
} finally {
|
||||
if (\is_file($file)) {
|
||||
\unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testNormalizePathInvalid(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
|
||||
// invoke protected normalizePath via reflection so we can test
|
||||
// the branch where realpath() returns false
|
||||
$ref = new \ReflectionMethod($cache, 'normalizePath');
|
||||
|
||||
$invalid = \sys_get_temp_dir() . '/nonexistent_' . \uniqid('', true);
|
||||
$this->assertFalse(\file_exists($invalid), 'Sanity check: path should not exist');
|
||||
|
||||
$this->assertSame('', $ref->invoke($cache, $invalid));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testDelete(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
$idk = 0;
|
||||
/** @var array<int, string> $file */
|
||||
$file = [];
|
||||
for ($idx = 1; $idx <= 2; ++$idx) {
|
||||
for ($idy = 1; $idy <= 2; ++$idy) {
|
||||
$file[$idk] = $cache->getNewFileName((string) $idx, (string) $idy);
|
||||
\file_put_contents($file[$idk], '');
|
||||
$this->assertTrue(\file_exists($file[$idk]));
|
||||
++$idk;
|
||||
}
|
||||
}
|
||||
|
||||
$f0 = $file[0] ?? '';
|
||||
$f1 = $file[1] ?? '';
|
||||
$f2 = $file[2] ?? '';
|
||||
$f3 = $file[3] ?? '';
|
||||
|
||||
// delete a specific type/key pair
|
||||
$cache->delete('2', '1');
|
||||
$this->assertFalse(\file_exists($f2));
|
||||
|
||||
// delete all entries for type "1"
|
||||
$cache->delete('1');
|
||||
$this->assertFalse(\file_exists($f0));
|
||||
$this->assertFalse(\file_exists($f1));
|
||||
$this->assertTrue(\file_exists($f3));
|
||||
|
||||
// delete everything
|
||||
$cache->delete();
|
||||
$this->assertFalse(\file_exists($f3));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testKeyOnlyDeletesAll(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
$file = $cache->getNewFileName('foo', 'bar');
|
||||
\file_put_contents($file, '');
|
||||
$this->assertTrue(\file_exists($file));
|
||||
|
||||
// key-only call should treat as delete all
|
||||
$cache->delete(null, 'bar');
|
||||
$this->assertFalse(\file_exists($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testDeleteNonExistingPatterns(): void
|
||||
{
|
||||
$cache = $this->getTestObject();
|
||||
$file = $cache->getNewFileName('foo', 'bar');
|
||||
\file_put_contents($file, '');
|
||||
$this->assertTrue(\file_exists($file));
|
||||
|
||||
// deleting a type that does not exist should leave the file in place
|
||||
$cache->delete('no-such-type');
|
||||
$this->assertTrue(\file_exists($file));
|
||||
|
||||
// deleting a non-existent key under existing type
|
||||
$cache->delete('foo', 'no-such-key');
|
||||
$this->assertTrue(\file_exists($file));
|
||||
|
||||
\unlink($file);
|
||||
}
|
||||
|
||||
public function testEachInstanceHasOwnPrefix(): void
|
||||
{
|
||||
// Each instance should have its own prefix
|
||||
$cache1 = new \Com\Tecnick\File\Cache('pfx1');
|
||||
$cache2 = new \Com\Tecnick\File\Cache('pfx2');
|
||||
|
||||
$prefix1 = $cache1->getFilePrefix();
|
||||
$prefix2 = $cache2->getFilePrefix();
|
||||
|
||||
// Prefixes should be different since each instance is independent
|
||||
$this->assertNotSame($prefix1, $prefix2);
|
||||
$this->assertStringContainsString('pfx1', $prefix1);
|
||||
$this->assertStringContainsString('pfx2', $prefix2);
|
||||
}
|
||||
|
||||
public function testEachInstanceHasOwnCachePath(): void
|
||||
{
|
||||
$cache1 = new \Com\Tecnick\File\Cache();
|
||||
$path1 = $cache1->getCachePath();
|
||||
|
||||
$tempdir = \sys_get_temp_dir() . '/cache_test_' . \uniqid();
|
||||
\mkdir($tempdir);
|
||||
|
||||
$cache2 = new \Com\Tecnick\File\Cache();
|
||||
$cache2->setCachePath($tempdir);
|
||||
$path2 = $cache2->getCachePath();
|
||||
|
||||
// Paths should be different for each instance
|
||||
$this->assertNotSame($path1, $path2);
|
||||
$this->assertStringContainsString('cache_test_', $path2);
|
||||
|
||||
\rmdir($tempdir);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Issue 4: glob-injection sanitisation in delete()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testDeleteGlobCharsInTypeSanitised(): void
|
||||
{
|
||||
$cache = new \Com\Tecnick\File\Cache('safepfx');
|
||||
|
||||
// Create a real file we do NOT want deleted.
|
||||
$real = $cache->getNewFileName('safe', '1');
|
||||
\file_put_contents($real, '');
|
||||
$this->assertTrue(\file_exists($real));
|
||||
|
||||
// Call delete() with glob metacharacters in $type — must not expand.
|
||||
$cache->delete('*', null);
|
||||
|
||||
// The real file must still exist because '*' was stripped to '' and
|
||||
// the resulting pattern matched nothing (or only unrelated files).
|
||||
// If glob injection were possible every file would be gone.
|
||||
$this->assertTrue(\file_exists($real), 'Glob injection via $type must not delete unrelated files');
|
||||
\unlink($real);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testDeleteGlobCharsInKeySanitised(): void
|
||||
{
|
||||
$cache = new \Com\Tecnick\File\Cache('safepfx2');
|
||||
|
||||
$real = $cache->getNewFileName('mytype', 'goodkey');
|
||||
\file_put_contents($real, '');
|
||||
$this->assertTrue(\file_exists($real));
|
||||
|
||||
// Inject glob metacharacter in $key — must be stripped.
|
||||
$cache->delete('mytype', '?');
|
||||
|
||||
$this->assertTrue(\file_exists($real), 'Glob injection via $key must not delete unrelated files');
|
||||
\unlink($real);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Issue 6: per-instance cache properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Testing covered by testEachInstanceHasOwnPrefix() and testEachInstanceHasOwnCachePath()
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Issue 9: createNewFileName()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Testing the exception path of getNewFileName() requires tempnam() to return false,
|
||||
// which is not deterministic in this environment because tempnam() can fall back
|
||||
// to the system temporary directory.
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Issue 10: deleteOlderThan()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function testDeleteOlderThanNoFiles(): void
|
||||
{
|
||||
// Call deleteOlderThan() when no files exist for this cache prefix.
|
||||
// glob() returns [] so the early-return on line 171 is exercised.
|
||||
$cache = new \Com\Tecnick\File\Cache('emptyprefix_' . \uniqid('', true));
|
||||
$cache->deleteOlderThan(3600);
|
||||
// No exception thrown is the expected outcome.
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
public function testDeleteOlderThan(): void
|
||||
{
|
||||
$cache = new \Com\Tecnick\File\Cache('ttl');
|
||||
|
||||
$old = $cache->getNewFileName('aged', '1');
|
||||
$fresh = $cache->getNewFileName('aged', '2');
|
||||
\file_put_contents($old, '');
|
||||
\file_put_contents($fresh, '');
|
||||
|
||||
// Back-date the "old" file to 2 hours ago.
|
||||
\touch($old, \time() - 7200);
|
||||
|
||||
// Delete files older than 1 hour.
|
||||
$cache->deleteOlderThan(3600);
|
||||
|
||||
$this->assertFalse(\file_exists($old), 'Expired file must be deleted');
|
||||
$this->assertTrue(\file_exists($fresh), 'Fresh file must be kept');
|
||||
|
||||
\unlink($fresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the host application defines K_PATH_CACHE without a trailing separator,
|
||||
* the fallback path must still be normalized so generated files land inside the
|
||||
* cache directory instead of escaping into its parent (e.g. ".../cache" + name
|
||||
* yielding ".../cache_<name>"). Runs in a separate process because K_PATH_CACHE
|
||||
* is a constant that cannot be (re)defined once the cache class has set it.
|
||||
*
|
||||
* @throws \Com\Tecnick\File\Exception
|
||||
*/
|
||||
#[RunInSeparateProcess]
|
||||
#[PreserveGlobalState(false)]
|
||||
public function testGetNewFileNameRespectsCachePathWithoutTrailingSeparator(): void
|
||||
{
|
||||
$dir = \sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'tcfilecache_' . \uniqid('', true);
|
||||
$this->assertTrue(\mkdir($dir, 0o700));
|
||||
|
||||
// Define K_PATH_CACHE without a trailing separator, before the cache class
|
||||
// has a chance to define it from sys_get_temp_dir().
|
||||
\define('K_PATH_CACHE', $dir);
|
||||
|
||||
try {
|
||||
$cache = new \Com\Tecnick\File\Cache('trailsep');
|
||||
|
||||
// The reported cache path must end with the platform separator.
|
||||
$cachePath = $cache->getCachePath();
|
||||
$this->assertSame(\DIRECTORY_SEPARATOR, \substr($cachePath, -1));
|
||||
|
||||
$file = $cache->getNewFileName('tmp', '0');
|
||||
try {
|
||||
// The generated file must live inside the cache directory.
|
||||
$this->assertTrue(\str_starts_with($file, $cachePath));
|
||||
$this->assertSame(\realpath($dir), \realpath(\dirname($file)));
|
||||
$this->assertTrue(\is_file($file));
|
||||
} finally {
|
||||
if (\is_file($file)) {
|
||||
\unlink($file);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
\rmdir($dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DirTest.php
|
||||
*
|
||||
* @since 2015-07-28
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*
|
||||
* This file is part of tc-lib-file software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\PreserveGlobalState;
|
||||
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
|
||||
|
||||
/**
|
||||
* File Color class test
|
||||
*
|
||||
* @since 2015-07-28
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*/
|
||||
class DirTest extends TestUtil
|
||||
{
|
||||
protected function getTestObject(): \Com\Tecnick\File\Dir
|
||||
{
|
||||
return new \Com\Tecnick\File\Dir();
|
||||
}
|
||||
|
||||
#[DataProvider('getAltFilePathsDataProvider')]
|
||||
public function testGetAltFilePaths(string $name, string $expected): void
|
||||
{
|
||||
$testObj = $this->getTestObject();
|
||||
$dir = $testObj->findParentDir($name);
|
||||
$this->bcAssertMatchesRegularExpression('#' . $expected . '#', $dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{string, string}>
|
||||
*/
|
||||
public static function getAltFilePathsDataProvider(): array
|
||||
{
|
||||
return [['', '/src/'], ['missing', '/'], ['src', '/src/']];
|
||||
}
|
||||
|
||||
/**
|
||||
* The upward directory search must not raise open_basedir warnings when it walks
|
||||
* above the allowed paths (see issue #238). Runs in a separate process because
|
||||
* open_basedir cannot be relaxed once set.
|
||||
*/
|
||||
#[RunInSeparateProcess]
|
||||
#[PreserveGlobalState(false)]
|
||||
public function testFindParentDirUnderOpenBasedir(): void
|
||||
{
|
||||
$libRoot = \dirname(__DIR__);
|
||||
|
||||
// Restrict file access to the library tree (the temp dir is required by the
|
||||
// test runner). The ancestors of $libRoot fall outside this restriction, so
|
||||
// probing them would raise open_basedir warnings without the guard.
|
||||
// @mago-expect lint:no-ini-set -- open_basedir can only be set at runtime in a test.
|
||||
\ini_set('open_basedir', $libRoot . PATH_SEPARATOR . \sys_get_temp_dir());
|
||||
|
||||
$warnings = [];
|
||||
\set_error_handler(static function (int $_errno, string $errstr) use (&$warnings): bool {
|
||||
$warnings[] = $errstr;
|
||||
return true;
|
||||
});
|
||||
|
||||
try {
|
||||
$dir = $this->getTestObject()->findParentDir('missing', $libRoot . '/src');
|
||||
} finally {
|
||||
\restore_error_handler();
|
||||
}
|
||||
|
||||
$this->assertSame([], $warnings, 'open_basedir restriction must not raise warnings');
|
||||
$this->bcAssertMatchesRegularExpression('#/$#', $dir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* EmptyReadStreamWrapper.php
|
||||
*
|
||||
* @since 2026-04-30
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*
|
||||
* This file is part of tc-lib-file software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* A stream wrapper that returns initial data on the first read then returns
|
||||
* empty strings on all subsequent reads while never signalling EOF via
|
||||
* stream_eof(). This exercises the inner `break` in File::rfRead() that
|
||||
* fires when fread() yields an empty string before the while-loop condition
|
||||
* can detect EOF.
|
||||
*/
|
||||
class EmptyReadStreamWrapper
|
||||
{
|
||||
public mixed $context;
|
||||
|
||||
private string $initialData = 'ab';
|
||||
|
||||
private bool $initialRead = false;
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
|
||||
{
|
||||
unset($path, $mode, $options, $opened_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_read(int $count): string
|
||||
{
|
||||
unset($count);
|
||||
if (!$this->initialRead) {
|
||||
$this->initialRead = true;
|
||||
return $this->initialData;
|
||||
}
|
||||
|
||||
// Return empty string while still claiming not at EOF so that the
|
||||
// while-loop in rfRead() re-enters and hits the inner break.
|
||||
return '';
|
||||
}
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
// Never signal EOF — stream_read() will return '' instead.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_stat(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+1649
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* RecursiveReadFile.php
|
||||
*
|
||||
* @since 2015-07-28
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*
|
||||
* This file is part of tc-lib-file software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
class RecursiveReadFile extends \Com\Tecnick\File\File
|
||||
{
|
||||
protected function hasUnreadBytes(mixed $resource): bool
|
||||
{
|
||||
return \is_resource($resource) && !\feof($resource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* RecursiveReadStreamWrapper.php
|
||||
*
|
||||
* @since 2015-07-28
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*
|
||||
* This file is part of tc-lib-file software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
class RecursiveReadStreamWrapper
|
||||
{
|
||||
public mixed $context;
|
||||
|
||||
private string $data = 'abcde';
|
||||
|
||||
private int $position = 0;
|
||||
|
||||
private int $reads = 0;
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
|
||||
{
|
||||
unset($path, $mode, $options, $opened_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_read(int $count): string
|
||||
{
|
||||
++$this->reads;
|
||||
$length = $this->reads === 1 ? \min(2, $count) : $count;
|
||||
$chunk = \substr($this->data, $this->position, $length);
|
||||
$this->position += \strlen($chunk);
|
||||
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return $this->position >= \strlen($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_stat(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SingleByteStreamWrapper.php
|
||||
*
|
||||
* @since 2026-04-30
|
||||
* @category Library
|
||||
* @package File
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*
|
||||
* This file is part of tc-lib-file software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* A stream wrapper that delivers exactly one byte per stream_read() call.
|
||||
* Used to exercise the iterative while-loop in File::rfRead().
|
||||
*/
|
||||
class SingleByteStreamWrapper
|
||||
{
|
||||
public mixed $context;
|
||||
|
||||
private string $data = 'abcdefgh';
|
||||
|
||||
private int $position = 0;
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
|
||||
{
|
||||
unset($path, $mode, $options, $opened_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_read(int $count): string
|
||||
{
|
||||
unset($count);
|
||||
// Always return one byte at a time regardless of requested count.
|
||||
if ($this->position >= \strlen($this->data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$byte = $this->data[$this->position];
|
||||
++$this->position;
|
||||
return $byte;
|
||||
}
|
||||
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return $this->position >= \strlen($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
// phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
|
||||
public function stream_stat(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TestUtil.php
|
||||
*
|
||||
* @since 2020-12-19
|
||||
* @category Library
|
||||
* @package file
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*
|
||||
* This file is part of tc-lib-file software library.
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test Util
|
||||
*
|
||||
* @since 2020-12-19
|
||||
* @category Library
|
||||
* @package file
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2015-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-file
|
||||
*/
|
||||
class TestUtil extends TestCase
|
||||
{
|
||||
public function bcAssertEqualsWithDelta(
|
||||
mixed $expected,
|
||||
mixed $actual,
|
||||
float $delta = 0.01,
|
||||
string $message = '',
|
||||
): void {
|
||||
parent::assertEqualsWithDelta($expected, $actual, $delta, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<\Throwable> $exception
|
||||
*/
|
||||
public function bcExpectException(string $exception): void
|
||||
{
|
||||
parent::expectException($exception);
|
||||
}
|
||||
|
||||
public function bcAssertIsResource(mixed $res): void
|
||||
{
|
||||
parent::assertIsResource($res);
|
||||
}
|
||||
|
||||
public function bcAssertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void
|
||||
{
|
||||
parent::assertMatchesRegularExpression($pattern, $string, $message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return;
|
||||
|
||||
/**
|
||||
* empty.php - Test HTTP endpoint that returns an empty response body.
|
||||
*
|
||||
* Used by FileTest to verify the `$ret === true` branch in getUrlData()
|
||||
* when CURLOPT_RETURNTRANSFER is not set.
|
||||
*/
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* large.php - Test HTTP endpoint that returns a large body.
|
||||
*
|
||||
* Used by FileTest to verify that the maxRemoteSize limit is enforced
|
||||
* via the cURL progress callback.
|
||||
*/
|
||||
|
||||
echo \str_repeat('X', 1000);
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
$target = isset($_GET['to']) && \is_string($_GET['to']) ? $_GET['to'] : '/empty.php';
|
||||
\header('Location: ' . $target, true, 302);
|
||||
Reference in New Issue
Block a user