From a674b3659c1c1d50cfbd01dc8a6f86b7bb83d41c Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Tue, 10 May 2016 15:38:29 +0100 Subject: [PATCH] MDL-54110 repositories: Allow repositories to ignore post_max_size Repositories can now override usespost() if they don't upload using POST requests. This restores the ability of users with moodle/course:ignorefilesizelimits to upload files of unlimited size lost in MDL-44626. --- lib/moodlelib.php | 70 +++++++++++++++++++++------------- lib/tests/moodlelib_test.php | 47 +++++++++++++++++++++++ repository/filepicker.php | 6 ++- repository/filesystem/lib.php | 11 ++++++ repository/lib.php | 12 ++++++ repository/repository_ajax.php | 3 +- 6 files changed, 120 insertions(+), 29 deletions(-) diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 7ca9188e2fb..a7cfe26516e 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -6079,53 +6079,67 @@ function valid_uploaded_file($newfile) { /** * Returns the maximum size for uploading files. * - * There are seven possible upload limits: - * 1. in Apache using LimitRequestBody (no way of checking or changing this) - * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP) - * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP) - * 4. in php.ini for 'post_max_size' (can not be changed inside PHP) - * 5. by the Moodle admin in $CFG->maxbytes - * 6. by the teacher in the current course $course->maxbytes - * 7. by the teacher for the current module, eg $assignment->maxbytes + * There are eight possible upload limits: + * 1. No limit, if the upload isn't using a post request and the user has permission to ignore limits. + * 2. in Apache using LimitRequestBody (no way of checking or changing this) + * 3. in php.ini for 'upload_max_filesize' (can not be changed inside PHP) + * 4. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP) + * 5. in php.ini for 'post_max_size' (can not be changed inside PHP) + * 6. by the Moodle admin in $CFG->maxbytes + * 7. by the teacher in the current course $course->maxbytes + * 8. by the teacher for the current module, eg $assignment->maxbytes * * These last two are passed to this function as arguments (in bytes). * Anything defined as 0 is ignored. * The smallest of all the non-zero numbers is returned. * + * The php.ini settings are only used if $usespost is true. This allows repositories that do not use post requests, such as + * repository_filesystem, to copy in files that are larger than post_max_size if the user has permission. + * * @todo Finish documenting this function * * @param int $sitebytes Set maximum size * @param int $coursebytes Current course $course->maxbytes (in bytes) * @param int $modulebytes Current module ->maxbytes (in bytes) + * @param bool $usespost Does the upload we're getting the max size for use a post request? * @return int The maximum size for uploading files. */ -function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) { +function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0, $usespost = true) { - if (! $filesize = ini_get('upload_max_filesize')) { - $filesize = '5M'; - } - $minimumsize = get_real_size($filesize); + $sizes = array(); - if ($postsize = ini_get('post_max_size')) { - $postsize = get_real_size($postsize); - if ($postsize < $minimumsize) { - $minimumsize = $postsize; + if ($usespost) { + if (! $filesize = ini_get('upload_max_filesize')) { + $filesize = '5M'; + } + $sizes[] = get_real_size($filesize); + + if ($postsize = ini_get('post_max_size')) { + $sizes[] = get_real_size($postsize); + } + + if ($sitebytes > 0) { + $sizes[] = $sitebytes; + } + } else { + if ($sitebytes != 0) { // It's for possible that $sitebytes == USER_CAN_IGNORE_FILE_SIZE_LIMITS (-1). + $sizes[] = $sitebytes; } } - if (($sitebytes > 0) and ($sitebytes < $minimumsize)) { - $minimumsize = $sitebytes; + if ($coursebytes > 0) { + $sizes[] = $coursebytes; } - if (($coursebytes > 0) and ($coursebytes < $minimumsize)) { - $minimumsize = $coursebytes; + if ($modulebytes > 0) { + $sizes[] = $modulebytes; } - if (($modulebytes > 0) and ($modulebytes < $minimumsize)) { - $minimumsize = $modulebytes; + if (empty($sizes)) { + throw new coding_exception('You must specify at least one filesize limit.'); } - return $minimumsize; + return min($sizes); } /** @@ -6138,9 +6152,11 @@ function get_max_upload_file_size($sitebytes=0, $coursebytes=0, $modulebytes=0) * @param int $coursebytes Current course $course->maxbytes (in bytes) * @param int $modulebytes Current module ->maxbytes (in bytes) * @param stdClass $user The user + * @param bool $usespost Does the upload we're getting the max size for use a post request? * @return int The maximum size for uploading files. */ -function get_user_max_upload_file_size($context, $sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $user = null) { +function get_user_max_upload_file_size($context, $sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $user = null, + $usespost = true) { global $USER; if (empty($user)) { @@ -6148,10 +6164,10 @@ function get_user_max_upload_file_size($context, $sitebytes = 0, $coursebytes = } if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) { - return get_max_upload_file_size(USER_CAN_IGNORE_FILE_SIZE_LIMITS); + return get_max_upload_file_size(USER_CAN_IGNORE_FILE_SIZE_LIMITS, 0, 0, $usespost); } - return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes); + return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes, $usespost); } /** diff --git a/lib/tests/moodlelib_test.php b/lib/tests/moodlelib_test.php index 229e918186b..0a1d16f51ca 100644 --- a/lib/tests/moodlelib_test.php +++ b/lib/tests/moodlelib_test.php @@ -2216,6 +2216,53 @@ class core_moodlelib_testcase extends advanced_testcase { $this->assertArrayHasKey(get_max_upload_file_size(), $result); } + public function test_get_max_upload_file_size() { + // Get the smallest upload limit from ini settings. + $inisize = min(array(get_real_size(ini_get('post_max_size')), get_real_size(ini_get('upload_max_filesize')))); + + // The inisize is the smallest. + $sitebytes = $inisize + 10; + $coursebytes = $inisize + 20; + $modulebytes = $inisize + 30; + $this->assertEquals($inisize, get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)); + + // Site limit is the smallest. + $sitebytes = $inisize - 30; + $coursebytes = $inisize - 20; + $modulebytes = $inisize - 10; + $this->assertEquals($sitebytes, get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)); + + // Course limit is the smallest. + $sitebytes = $inisize - 20; + $coursebytes = $inisize - 30; + $modulebytes = $inisize - 10; + $this->assertEquals($coursebytes, get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)); + + // Module limit is the smallest. + $sitebytes = $inisize - 20; + $coursebytes = $inisize - 10; + $modulebytes = $inisize - 30; + $this->assertEquals($modulebytes, get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)); + + // The inisize is the smallest, the upload does not use post. + $sitebytes = $inisize + 10; + $coursebytes = $inisize + 20; + $modulebytes = $inisize + 30; + $this->assertEquals($sitebytes, get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes, false)); + + // The user can ignore file size limits, the upload does use post. + $this->assertEquals($inisize, get_max_upload_file_size(USER_CAN_IGNORE_FILE_SIZE_LIMITS, 0, 0)); + + // The user can ignore file size limits, the upload not does use post. + $this->assertEquals(USER_CAN_IGNORE_FILE_SIZE_LIMITS, + get_max_upload_file_size(USER_CAN_IGNORE_FILE_SIZE_LIMITS, 0, 0, false)); + + // If not using post we have to provide at least one other limit. + $this->setExpectedException('coding_exception', 'You must specify at least one filesize limit.'); + get_max_upload_file_size(0, 0, 0, false); + + } + /** * Test function password_is_legacy_hash(). */ diff --git a/repository/filepicker.php b/repository/filepicker.php index e488e3d4d41..dccec4270e5 100644 --- a/repository/filepicker.php +++ b/repository/filepicker.php @@ -77,6 +77,8 @@ if (!$course = $DB->get_record('course', array('id'=>$courseid))) { } $PAGE->set_course($course); +$usespost = true; + if ($repo_id) { // Get repository instance information $repooptions = array( @@ -87,12 +89,14 @@ if ($repo_id) { // Check permissions $repo->check_capability(); + + $usespost = $repo->uses_post_requests(); } $context = context::instance_by_id($contextid); // Make sure maxbytes passed is within site filesize limits. -$maxbytes = get_user_max_upload_file_size($context, $CFG->maxbytes, $course->maxbytes, $maxbytes); +$maxbytes = get_user_max_upload_file_size($context, $CFG->maxbytes, $course->maxbytes, $maxbytes, null, $usespost); $params = array('ctx_id' => $contextid, 'itemid' => $itemid, 'env' => $env, 'course'=>$courseid, 'maxbytes'=>$maxbytes, 'areamaxbytes'=>$areamaxbytes, 'maxfiles'=>$maxfiles, 'subdirs'=>$subdirs, 'sesskey'=>sesskey()); $params['action'] = 'browse'; diff --git a/repository/filesystem/lib.php b/repository/filesystem/lib.php index 1585d883ac5..7ce8b5f7b52 100644 --- a/repository/filesystem/lib.php +++ b/repository/filesystem/lib.php @@ -594,6 +594,17 @@ class repository_filesystem extends repository { public function supports_relative_file() { return $this->get_option('relativefiles'); } + + /** + * Helper funtion to indicate if this repository uses post requests for uploading files. + * + * Files are copied from the filesystem so don't rely on POST requests. + * + * @return bool + */ + public function uses_post_requests() { + return false; + } } /** diff --git a/repository/lib.php b/repository/lib.php index 3892f09e7f0..93dcd2e6da7 100644 --- a/repository/lib.php +++ b/repository/lib.php @@ -2826,6 +2826,18 @@ abstract class repository implements cacheable_object { public function supports_relative_file() { return false; } + + /** + * Helper funtion to indicate if this repository uses post requests for uploading files. + * + * If the respository doesn't rely on uploading via POST requests, this can be overridden to return true, + * allowing users with the right permissions to upload files of any size from this repository. + * + * @return bool + */ + public function uses_post_requests() { + return true; + } } /** diff --git a/repository/repository_ajax.php b/repository/repository_ajax.php index 0873a4a1987..5ac44058d67 100644 --- a/repository/repository_ajax.php +++ b/repository/repository_ajax.php @@ -86,7 +86,8 @@ if (!empty($course)) { $coursemaxbytes = $course->maxbytes; } // Make sure maxbytes passed is within site filesize limits. -$maxbytes = get_user_max_upload_file_size($context, $CFG->maxbytes, $coursemaxbytes, $maxbytes); +$maxbytes = get_user_max_upload_file_size($context, $CFG->maxbytes, $coursemaxbytes, $maxbytes, + null, $repo->uses_post_requests()); // Wait as long as it takes for this script to finish core_php_time_limit::raise();