Files
xxx/tests/php/Unit/UploadTest.php
T

208 lines
7.9 KiB
PHP

<?php
namespace Xxx\Tests\Unit;
use PHPUnit\Framework\Attributes\DataProvider;
use Xxx\Tests\Support\TestCase;
// The upload directory and the pending upload in get_data.lib.php and publish/post.lib.php
class UploadTest extends TestCase {
private function pending(string $content = ''): string {
$name = bin2hex(random_bytes(16));
$file = upload_dir() . '/' . $name;
file_put_contents($file, $content === '' ? self::png() : $content);
$_SESSION['pending_upload'] = $name;
return $file;
}
public function testUploadDirIsMadeFromTheConfiguration(): void {
$this->assertSame($this->tmp . '/uploads', upload_dir());
$this->assertDirectoryExists($this->tmp . '/uploads');
$this->assertSame(0700, fileperms($this->tmp . '/uploads') & 0777);
}
public function testUploadDirLosesATrailingSlash(): void {
$this->setEnv('UPLOAD_DIR', $this->tmp . '/other/');
$this->assertSame($this->tmp . '/other', upload_dir());
}
public function testUploadDirDefaultsToTheTempDirectory(): void {
$this->setEnv('UPLOAD_DIR', '');
$this->assertSame(sys_get_temp_dir() . '/xxx-uploads', upload_dir());
}
public function testUploadNamesAre32HexDigits(): void {
$this->assertTrue((bool)is_upload_name(str_repeat('a0', 16)));
$this->assertFalse((bool)is_upload_name(str_repeat('a0', 15)));
$this->assertFalse((bool)is_upload_name(str_repeat('A0', 16)));
$this->assertFalse((bool)is_upload_name('../' . str_repeat('a', 29)));
$this->assertFalse((bool)is_upload_name(str_repeat('a', 32) . "\n"));
$this->assertFalse((bool)is_upload_name(array()));
$this->assertFalse((bool)is_upload_name(NULL));
}
public function testStoreUploadRefusesAFileThatWasNotUploaded(): void {
$file = $this->tmp . '/local';
file_put_contents($file, 'x');
$this->assertFalse(store_upload($file));
$this->assertFileExists($file);
$this->assertArrayNotHasKey('pending_upload', $_SESSION);
}
public function testPendingUploadFindsTheSessionFile(): void {
$file = $this->pending();
$this->assertSame($file, pending_upload());
}
public function testPendingUploadNeedsAValidNameAndAFile(): void {
$this->assertFalse(pending_upload());
$_SESSION['pending_upload'] = '../../etc/passwd';
$this->assertFalse(pending_upload());
$_SESSION['pending_upload'] = str_repeat('b', 32);
$this->assertFalse(pending_upload());
}
public function testPendingUploadUrlHasTheStartOfTheName(): void {
$_SESSION['pending_upload'] = '0123456789abcdef0123456789abcdef';
$this->assertSame('/display?pending=01234567', pending_upload_url());
unset($_SESSION['pending_upload']);
$this->assertSame('/display?pending=', pending_upload_url());
}
public function testMimeTypeComesFromTheImage(): void {
$this->assertSame('image/png', get_mimetype($this->pending()));
$text = $this->tmp . '/text';
file_put_contents($text, 'This is not an image, but it is long enough to read.');
$this->assertNull(get_mimetype($text));
}
public function testSaveUploadedFileUsesThePendingUpload(): void {
$file = $this->pending();
$this->assertFalse(save_uploaded_file());
$_POST['uploaded'] = 'pending';
$this->assertSame($file, save_uploaded_file());
}
public function testSaveUploadedFileIgnoresAFakeUpload(): void {
$file = $this->tmp . '/fake';
file_put_contents($file, self::png());
$_FILES['upload'] = array('tmp_name' => $file, 'name' => 'a.png', 'error' => 0);
$this->assertFalse(save_uploaded_file());
}
public static function arrayTemporaryNames(): array {
return array(
'empty array' => array(array()),
'file list' => array(array('/tmp/not-an-upload')),
);
}
#[DataProvider('arrayTemporaryNames')]
public function testAnArrayTemporaryNameIsRefused(array $name): void {
$pending = $this->pending();
$_FILES['upload'] = array('tmp_name' => $name, 'name' => array('a.png'), 'error' => array(UPLOAD_ERR_OK));
$this->assertFalse(save_uploaded_file());
$_POST['uploaded'] = 'pending';
$this->assertFalse(save_uploaded_file(), 'an invalid new upload does not select the pending image');
$this->assertFileExists($pending);
$this->assertSame(basename($pending), $_SESSION['pending_upload']);
}
public static function missingTemporaryNames(): array {
return array(
'absent entry' => array(array()),
'absent name' => array(array('upload' => array('error' => UPLOAD_ERR_NO_FILE))),
'empty name' => array(array('upload' => array('tmp_name' => ''))),
);
}
#[DataProvider('missingTemporaryNames')]
public function testAMissingTemporaryNameLeavesThePendingUploadAlone(array $files): void {
$pending = $this->pending();
$_FILES = $files;
$this->assertFalse(save_uploaded_file());
$this->assertFileExists($pending);
$this->assertSame(basename($pending), $_SESSION['pending_upload']);
$_POST['uploaded'] = 'pending';
$this->assertSame($pending, save_uploaded_file());
}
public function testAMissingTemporaryFileIsNotSaved(): void {
$_FILES['upload'] = array('name' => 'a.png', 'tmp_name' => $this->tmp . '/missing', 'error' => UPLOAD_ERR_OK);
$this->assertFalse(save_uploaded_file());
$this->assertArrayNotHasKey('pending_upload', $_SESSION);
}
// Files in the upload directory: an old upload, a new one and a file that is not an upload
private function others(): array {
$files = array('old' => upload_dir() . '/' . str_repeat('c', 32), 'new' => upload_dir() . '/' . str_repeat('d', 32),
'other' => upload_dir() . '/keep-me');
foreach ($files as $file)
file_put_contents($file, 'xx');
touch($files['old'], time() - 86401);
touch($files['other'], time() - 86401);
return $files;
}
public function testPruneImagesDeletesTheSessionUploadAndOldUploads(): void {
$saved = $this->pending();
$files = $this->others();
prune_images();
$this->assertFileDoesNotExist($saved);
$this->assertFileDoesNotExist($files['old']);
$this->assertFileExists($files['new']);
$this->assertFileExists($files['other']);
$this->assertArrayNotHasKey('pending_upload', $_SESSION);
}
public function testPruneUploadsKeepsTheSessionUploadUntilItIsOld(): void {
$pending = $this->pending();
prune_uploads();
$this->assertFileExists($pending);
touch($pending, time() - 86401);
prune_uploads();
$this->assertFileDoesNotExist($pending);
}
public function testRemovePendingUploadTouchesOnlyTheSessionFile(): void {
$files = $this->others();
remove_pending_upload();
$pending = $this->pending();
remove_pending_upload();
$this->assertFileDoesNotExist($pending);
$this->assertArrayNotHasKey('pending_upload', $_SESSION);
foreach ($files as $file)
$this->assertFileExists($file);
}
public function testUploadDirSizeCountsOnlyUploads(): void {
$this->others();
$this->pending('12345');
$this->assertSame(2 + 2 + 5, upload_dir_size());
}
public function testTheUploadDirLimitComesFromTheConfiguration(): void {
$this->setEnv('UPLOAD_DIR_LIMIT_MB', '2.5');
$this->assertSame((int)(2.5 * 1024 * 1024), upload_dir_limit_bytes());
}
public function testAnEmptyOrBadUploadDirLimitMeansTheDefault(): void {
foreach (array('', ' ', 'lots', '0', '-2', 'none') as $value) {
$this->setEnv('UPLOAD_DIR_LIMIT_MB', $value);
$this->assertSame(100 * 1024 * 1024, upload_dir_limit_bytes(), "'$value'");
}
}
public function testStoreUploadPrunesAndDropsTheLastUploadFirst(): void {
$last = $this->pending();
$files = $this->others();
$file = $this->tmp . '/local';
file_put_contents($file, 'x');
// Not a real upload, so nothing is kept, but the old files still go
$this->assertFalse(store_upload($file));
$this->assertFileDoesNotExist($last);
$this->assertFileDoesNotExist($files['old']);
$this->assertFileExists($files['new']);
$this->assertArrayNotHasKey('pending_upload', $_SESSION);
}
}