Limit each image to 1 MB.
UPLOAD_MAX_MB changes the limit, and UPLOAD_MAX_MB=none removes it.
This commit is contained in:
@@ -25,6 +25,10 @@ GOOGLE_ANALYTICS_ID=
|
|||||||
# web root. The default is xxx-uploads in the system temporary directory.
|
# web root. The default is xxx-uploads in the system temporary directory.
|
||||||
UPLOAD_DIR=
|
UPLOAD_DIR=
|
||||||
|
|
||||||
|
# The largest image a story can have, in megabytes (default 1). Set it
|
||||||
|
# to none for no limit. PHP's upload_max_filesize still applies.
|
||||||
|
UPLOAD_MAX_MB=
|
||||||
|
|
||||||
# The most that the upload directory holds, in megabytes (default 100).
|
# The most that the upload directory holds, in megabytes (default 100).
|
||||||
# Past this, previews do not keep new images. Abandoned uploads are
|
# Past this, previews do not keep new images. Abandoned uploads are
|
||||||
# removed after a day.
|
# removed after a day.
|
||||||
|
|||||||
@@ -373,6 +373,24 @@ function upload_dir() {
|
|||||||
return $dir;
|
return $dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The largest image a story can have, in bytes, from UPLOAD_MAX_MB (1 MB
|
||||||
|
* by default). NULL means no limit, which UPLOAD_MAX_MB=none gives.
|
||||||
|
*/
|
||||||
|
function upload_max_bytes() {
|
||||||
|
$value = strtolower(trim((string)config('UPLOAD_MAX_MB', '')));
|
||||||
|
if ($value === 'none')
|
||||||
|
return NULL;
|
||||||
|
if (!is_numeric($value) || (float)$value <= 0)
|
||||||
|
$value = 1;
|
||||||
|
return (int)((float)$value * 1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The same in megabytes for a message, such as "1" or "2.5"
|
||||||
|
function upload_max_mb() {
|
||||||
|
return rtrim(rtrim(number_format(upload_max_bytes() / 1048576, 2, '.', ''), '0'), '.');
|
||||||
|
}
|
||||||
|
|
||||||
function is_upload_name($name) {
|
function is_upload_name($name) {
|
||||||
return is_string($name) && preg_match('/^[0-9a-f]{32}\z/', $name);
|
return is_string($name) && preg_match('/^[0-9a-f]{32}\z/', $name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,10 +206,18 @@ function validate_data() {
|
|||||||
$validate['story'] = "You've tried to remove an image without replacing it with text.";
|
$validate['story'] = "You've tried to remove an image without replacing it with text.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$upload_max = upload_max_bytes();
|
||||||
|
$too_large = htmlspecialchars((string)($_FILES['upload']['name'] ?? '')) .
|
||||||
|
($upload_max === NULL ? ' is larger than the server accepts.' : ' is larger than '.upload_max_mb().' MB.') .
|
||||||
|
' Please choose a smaller image.';
|
||||||
// 0 == no error; 4 == no file; all others are errors.
|
// 0 == no error; 4 == no file; all others are errors.
|
||||||
if ($upload_error > 0 &&
|
if ($upload_error === UPLOAD_ERR_INI_SIZE || $upload_error === UPLOAD_ERR_FORM_SIZE) {
|
||||||
|
$validate['upload'] = $too_large;
|
||||||
|
} else if ($upload_error > 0 &&
|
||||||
$upload_error !== 4) {
|
$upload_error !== 4) {
|
||||||
$validate['upload'] = 'There was an error uploading the file ('.$upload_error.')';
|
$validate['upload'] = 'There was an error uploading the file ('.$upload_error.')';
|
||||||
|
} else if ($upload_tmp && $upload_max !== NULL && (int)@filesize($upload_tmp) > $upload_max) {
|
||||||
|
$validate['upload'] = $too_large;
|
||||||
} else if ($upload_tmp) {
|
} else if ($upload_tmp) {
|
||||||
$upload = getimagesize($upload_tmp);
|
$upload = getimagesize($upload_tmp);
|
||||||
if (!$upload)
|
if (!$upload)
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ class SubmitTest extends PageTestCase {
|
|||||||
$this->assertSame(array(), $this->queries('/^INSERT/'));
|
$this->assertSame(array(), $this->queries('/^INSERT/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testATooLargeImageIsRefused(): void {
|
||||||
|
$page = $this->previewImage(array(), str_pad(TestCase::png(), 1024 * 1024 + 1, "\0"));
|
||||||
|
$this->assertStringContainsString('a.png is larger than 1 MB. Please choose a smaller image.', $page['body']);
|
||||||
|
$this->assertSame(array(), $this->uploads());
|
||||||
|
}
|
||||||
|
|
||||||
public function testAnArrayActionIsIgnored(): void {
|
public function testAnArrayActionIsIgnored(): void {
|
||||||
$page = $this->post('/publish', array('action' => array('post')));
|
$page = $this->post('/publish', array('action' => array('post')));
|
||||||
$this->assertSame(200, $page['status']);
|
$this->assertSame(200, $page['status']);
|
||||||
@@ -128,10 +134,10 @@ class SubmitTest extends PageTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Preview the story with an image; returns the answer
|
// Preview the story with an image; returns the answer
|
||||||
private function previewImage(array $fields = array()): array {
|
private function previewImage(array $fields = array(), ?string $image = NULL): array {
|
||||||
$token = $this->csrfToken($this->get('/publish')['body']);
|
$token = $this->csrfToken($this->get('/publish')['body']);
|
||||||
return $this->postFiles('/publish', $this->story($fields + array('action' => 'preview', 'csrf_token' => $token, 'g-recaptcha-response' => 'pass')),
|
return $this->postFiles('/publish', $this->story($fields + array('action' => 'preview', 'csrf_token' => $token, 'g-recaptcha-response' => 'pass')),
|
||||||
array('upload' => array('a.png', TestCase::png())));
|
array('upload' => array('a.png', $image ?? TestCase::png())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAPreviewKeepsTheImageForTheSession(): void {
|
public function testAPreviewKeepsTheImageForTheSession(): void {
|
||||||
|
|||||||
@@ -59,8 +59,53 @@ class ValidateDataTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testUploadErrorsAreReported(): void {
|
public function testUploadErrorsAreReported(): void {
|
||||||
$_FILES['upload'] = array('name' => 'a.png', 'tmp_name' => '', 'error' => UPLOAD_ERR_INI_SIZE);
|
$_FILES['upload'] = array('name' => 'a.png', 'tmp_name' => '', 'error' => UPLOAD_ERR_PARTIAL);
|
||||||
$this->assertSame(array('upload' => 'There was an error uploading the file (1)'), $this->validate(array()));
|
$this->assertSame(array('upload' => 'There was an error uploading the file (3)'), $this->validate(array()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAFileThatPhpRefusedAsTooLargeIsReported(): void {
|
||||||
|
foreach (array(UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE) as $error) {
|
||||||
|
$_FILES['upload'] = array('name' => 'big.png', 'tmp_name' => '', 'error' => $error);
|
||||||
|
$this->assertSame(array('upload' => 'big.png is larger than 1 MB. Please choose a smaller image.'), $this->validate(array()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An image of $bytes bytes: a PNG with padding after its end
|
||||||
|
private function imageOf(int $bytes): void {
|
||||||
|
$file = $this->tmp . '/upload';
|
||||||
|
file_put_contents($file, str_pad(self::png(), $bytes, "\0"));
|
||||||
|
$_FILES['upload'] = array('name' => '<b>big</b>.png', 'tmp_name' => $file, 'error' => UPLOAD_ERR_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImagesCanBeOneMegabyte(): void {
|
||||||
|
$this->imageOf(1024 * 1024);
|
||||||
|
$this->assertSame(array(), $this->validate(array()));
|
||||||
|
$this->imageOf(1024 * 1024 + 1);
|
||||||
|
$this->assertSame(array('upload' => '<b>big</b>.png is larger than 1 MB. Please choose a smaller image.'), $this->validate(array()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheImageLimitComesFromTheConfiguration(): void {
|
||||||
|
$this->setEnv('UPLOAD_MAX_MB', '2.5');
|
||||||
|
$this->imageOf(2 * 1024 * 1024);
|
||||||
|
$this->assertSame(array(), $this->validate(array()));
|
||||||
|
$this->imageOf((int)(2.5 * 1024 * 1024) + 1);
|
||||||
|
$this->assertSame(array('upload' => '<b>big</b>.png is larger than 2.5 MB. Please choose a smaller image.'), $this->validate(array()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoneMeansNoImageLimit(): void {
|
||||||
|
$this->setEnv('UPLOAD_MAX_MB', 'None');
|
||||||
|
$this->assertNull(upload_max_bytes());
|
||||||
|
$this->imageOf(3 * 1024 * 1024);
|
||||||
|
$this->assertSame(array(), $this->validate(array()));
|
||||||
|
$_FILES['upload'] = array('name' => 'big.png', 'tmp_name' => '', 'error' => UPLOAD_ERR_INI_SIZE);
|
||||||
|
$this->assertSame(array('upload' => 'big.png is larger than the server accepts. Please choose a smaller image.'), $this->validate(array()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testABadImageLimitMeansTheDefault(): void {
|
||||||
|
foreach (array('', 'lots', '0', '-2') as $value) {
|
||||||
|
$this->setEnv('UPLOAD_MAX_MB', $value);
|
||||||
|
$this->assertSame(1024 * 1024, upload_max_bytes(), $value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNoFileIsNotAnError(): void {
|
public function testNoFileIsNotAnError(): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user