164 lines
6.1 KiB
PHP
164 lines
6.1 KiB
PHP
<?php
|
|
namespace Xxx\Tests\Unit;
|
|
|
|
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());
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|