Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update .user.ini when setting upload size limit #17182

Merged
merged 2 commits into from
Aug 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/files/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@
}

$htaccessWritable=is_writable(OC::$SERVERROOT.'/.htaccess');
$userIniWritable=is_writable(OC::$SERVERROOT.'/.user.ini');

$tmpl = new OCP\Template( 'files', 'admin' );
$tmpl->assign( 'uploadChangable', $htaccessWorking and $htaccessWritable );
$tmpl->assign( 'uploadChangable', ($htaccessWorking and $htaccessWritable) or $userIniWritable );
$tmpl->assign( 'uploadMaxFilesize', $maxUploadFilesize);
// max possible makes only sense on a 32 bit system
$tmpl->assign( 'displayMaxPossibleUploadSize', PHP_INT_SIZE===4);
Expand Down
2 changes: 2 additions & 0 deletions apps/files/templates/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<br/>
<input type="hidden" value="<?php p($_['requesttoken']); ?>" name="requesttoken" />
<?php if($_['uploadChangable']): ?>
<?php p($l->t('With PHP-FPM this value may take up to 5 minutes to take effect after saving.')); ?>
<br/>
<input type="submit" name="submitFilesAdminSettings" id="submitFilesAdminSettings"
value="<?php p($l->t( 'Save' )); ?>"/>
<?php else: ?>
Expand Down
91 changes: 57 additions & 34 deletions lib/private/files.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Valerio Ponte <valerio.ponte@gmail.com>
* @author Vincent Petry <pvince81@owncloud.com>
* @author Robin McCorkell <rmccorkell@karoshi.org.uk>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @license AGPL-3.0
Expand Down Expand Up @@ -268,58 +269,80 @@ public static function zipAddDir($dir, ZipStreamer $zip, $internalDir='') {
* set the maximum upload size limit for apache hosts using .htaccess
*
* @param int $size file size in bytes
* @param array $files override '.htaccess' and '.user.ini' locations
* @return bool false on failure, size on success
*/
static function setUploadLimit($size) {
public static function setUploadLimit($size, $files = []) {
//don't allow user to break his config
if ($size > PHP_INT_MAX) {
//max size is always 1 byte lower than computerFileSize returns
if ($size > PHP_INT_MAX + 1)
return false;
$size -= 1;
}
$size = intval($size);
if ($size < self::UPLOAD_MIN_LIMIT_BYTES) {
return false;
}
$size = OC_Helper::phpFileSize($size);

//don't allow user to break his config -- broken or malicious size input
if (intval($size) === 0) {
return false;
}

//suppress errors in case we don't have permissions for
$htaccess = @file_get_contents(OC::$SERVERROOT . '/.htaccess');
if (!$htaccess) {
return false;
}

$phpValueKeys = array(
'upload_max_filesize',
'post_max_size'
);

foreach ($phpValueKeys as $key) {
$pattern = '/php_value ' . $key . ' (\S)*/';
$setting = 'php_value ' . $key . ' ' . $size;
$hasReplaced = 0;
$content = preg_replace($pattern, $setting, $htaccess, 1, $hasReplaced);
if ($content !== null) {
$htaccess = $content;
// default locations if not overridden by $files
$files = array_merge([
'.htaccess' => OC::$SERVERROOT . '/.htaccess',
'.user.ini' => OC::$SERVERROOT . '/.user.ini'
], $files);

$updateFiles = [
$files['.htaccess'] => [
'pattern' => '/php_value %1$s (\S)*/',
'setting' => 'php_value %1$s %2$s'
],
$files['.user.ini'] => [
'pattern' => '/%1$s=(\S)*/',
'setting' => '%1$s=%2$s'
]
];

$success = true;

foreach ($updateFiles as $filename => $patternMap) {
// suppress warnings from fopen()
$handle = @fopen($filename, 'r+');
if (!$handle) {
\OCP\Util::writeLog('files',
'Can\'t write upload limit to ' . $filename . '. Please check the file permissions',
\OCP\Util::WARN);
$success = false;
continue; // try to update as many files as possible
}
if ($hasReplaced === 0) {
$htaccess .= "\n" . $setting;

$content = '';
while (!feof($handle)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$content .= fread($handle, 1000);
}

foreach ($phpValueKeys as $key) {
$pattern = vsprintf($patternMap['pattern'], [$key]);
$setting = vsprintf($patternMap['setting'], [$key, $size]);
$hasReplaced = 0;
$newContent = preg_replace($pattern, $setting, $content, 1, $hasReplaced);
if ($newContent !== null) {
$content = $newContent;
}
if ($hasReplaced === 0) {
$content .= "\n" . $setting;
}
}

// write file back
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, $content);

fclose($handle);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use file_put_contents instead of the 4 lines above: https://secure.php.net/manual/de/function.file-put-contents.php

}

//check for write permissions
if (is_writable(OC::$SERVERROOT . '/.htaccess')) {
file_put_contents(OC::$SERVERROOT . '/.htaccess', $htaccess);
if ($success) {
return OC_Helper::computerFileSize($size);
} else {
\OCP\Util::writeLog('files',
'Can\'t write upload limit to ' . OC::$SERVERROOT . '/.htaccess. Please check the file permissions',
\OCP\Util::WARN);
}
return false;
}
Expand Down
60 changes: 60 additions & 0 deletions tests/data/setUploadLimit/htaccess
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Version: 8.2.0
<IfModule mod_headers.c>
<IfModule mod_fcgid.c>
<IfModule mod_setenvif.c>
SetEnvIfNoCase ^Authorization$ "(.+)" XAUTHORIZATION=$1
RequestHeader set XAuthorization %{XAUTHORIZATION}e env=XAUTHORIZATION
</IfModule>
</IfModule>

<IfModule mod_env.c>
# Add security and privacy related headers
Header set X-Content-Type-Options "nosniff"
Header set X-XSS-Protection "1; mode=block"
Header set X-Robots-Tag "none"
Header set X-Frame-Options "SAMEORIGIN"
SetEnv modHeadersAvailable true
</IfModule>

# Add cache control for CSS and JS files
<FilesMatch "\.(css|js)$">
Header set Cache-Control "max-age=7200, public"
</FilesMatch>
</IfModule>
<IfModule mod_php5.c>
php_value upload_max_filesize 513M
php_value post_max_size 513M
php_value memory_limit 512M
php_value mbstring.func_overload 0
php_value always_populate_raw_post_data -1
php_value default_charset 'UTF-8'
php_value output_buffering off
<IfModule mod_env.c>
SetEnv htaccessWorking true
</IfModule>
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule ^\.well-known/host-meta /public.php?service=host-meta [QSA,L]
RewriteRule ^\.well-known/host-meta\.json /public.php?service=host-meta-json [QSA,L]
RewriteRule ^\.well-known/carddav /remote.php/carddav/ [R=301,L]
RewriteRule ^\.well-known/caldav /remote.php/caldav/ [R=301,L]
RewriteRule ^apps/calendar/caldav\.php remote.php/caldav/ [QSA,L]
RewriteRule ^apps/contacts/carddav\.php remote.php/carddav/ [QSA,L]
RewriteRule ^remote/(.*) remote.php [QSA,L]
RewriteRule ^(build|tests|config|lib|3rdparty|templates)/.* - [R=404,L]
RewriteRule ^(\.|autotest|occ|issue|indie|db_|console).* - [R=404,L]
</IfModule>
<IfModule mod_mime.c>
AddType image/svg+xml svg svgz
AddEncoding gzip svgz
</IfModule>
<IfModule mod_dir.c>
DirectoryIndex index.php index.html
</IfModule>
AddDefaultCharset utf-8
Options -Indexes
<IfModule pagespeed_module>
ModPagespeed Off
</IfModule>
7 changes: 7 additions & 0 deletions tests/data/setUploadLimit/user.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
upload_max_filesize=513M
post_max_size=513M
memory_limit=512M
mbstring.func_overload=0
always_populate_raw_post_data=-1
default_charset='UTF-8'
output_buffering=off
135 changes: 135 additions & 0 deletions tests/lib/files.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php
/**
* @author Robin McCorkell <rmccorkell@karoshi.org.uk>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @license AGPL-3.0
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace Test;

class Files extends \Test\TestCase {

const UPLOAD_LIMIT_DEFAULT_STR = '513M';
const UPLOAD_LIMIT_SETTING_STR = '2M';
const UPLOAD_LIMIT_SETTING_BYTES = 2097152;

/** @var array $tmpDirs */
private $tmpDirs = [];

/**
* @return array
*/
private function getUploadLimitTestFiles() {
$dir = \OC::$server->getTempManager()->getTemporaryFolder();
$this->tmpDirs[] = $dir;
$result = [
'.htaccess' => $dir . '/htaccess',
'.user.ini' => $dir . '/user.ini'
];
copy(\OC::$SERVERROOT . '/tests/data/setUploadLimit/htaccess', $result['.htaccess']);
copy(\OC::$SERVERROOT . '/tests/data/setUploadLimit/user.ini', $result['.user.ini']);
return $result;
}

protected function tearDown() {
foreach ($this->tmpDirs as $dir) {
\OC_Helper::rmdirr($dir);
}
parent::tearDown();
}

public function testSetUploadLimitSizeSanity() {
$this->assertFalse(\OC_Files::setUploadLimit(PHP_INT_MAX + 10));
$this->assertFalse(\OC_Files::setUploadLimit(\OC_Files::UPLOAD_MIN_LIMIT_BYTES - 10));
$this->assertFalse(\OC_Files::setUploadLimit('foobar'));
}

public function setUploadLimitWriteProvider() {
return [
[
// both files writable
true, true,
self::UPLOAD_LIMIT_SETTING_BYTES, self::UPLOAD_LIMIT_SETTING_BYTES,
self::UPLOAD_LIMIT_SETTING_STR, self::UPLOAD_LIMIT_SETTING_STR
],
[
// neither file writable
false, false,
self::UPLOAD_LIMIT_SETTING_BYTES, false,
self::UPLOAD_LIMIT_DEFAULT_STR, self::UPLOAD_LIMIT_DEFAULT_STR
],
[
// only .htaccess writable
true, false,
self::UPLOAD_LIMIT_SETTING_BYTES, false,
self::UPLOAD_LIMIT_SETTING_STR, self::UPLOAD_LIMIT_DEFAULT_STR
],
[
// only .user.ini writable
false, true,
self::UPLOAD_LIMIT_SETTING_BYTES, false,
self::UPLOAD_LIMIT_DEFAULT_STR, self::UPLOAD_LIMIT_SETTING_STR
],
[
// test rounding of values
true, true,
self::UPLOAD_LIMIT_SETTING_BYTES + 20, self::UPLOAD_LIMIT_SETTING_BYTES,
self::UPLOAD_LIMIT_SETTING_STR, self::UPLOAD_LIMIT_SETTING_STR
]
];
}

/**
* @dataProvider setUploadLimitWriteProvider
*/
public function testSetUploadLimitWrite(
$htaccessWritable, $userIniWritable,
$setSize, $expectedSize,
$htaccessStr, $userIniStr
) {
$files = $this->getUploadLimitTestFiles();
chmod($files['.htaccess'], ($htaccessWritable ? 0644 : 0444));
chmod($files['.user.ini'], ($userIniWritable ? 0644 : 0444));

$htaccessSize = filesize($files['.htaccess']);
$userIniSize = filesize($files['.user.ini']);
$htaccessSizeMod = 2*(strlen($htaccessStr) - strlen(self::UPLOAD_LIMIT_DEFAULT_STR));
$userIniSizeMod = 2*(strlen($userIniStr) - strlen(self::UPLOAD_LIMIT_DEFAULT_STR));

$this->assertEquals($expectedSize, \OC_Files::setUploadLimit($setSize, $files));

// check file contents
$htaccess = file_get_contents($files['.htaccess']);
$this->assertEquals(1,
preg_match('/php_value upload_max_filesize '.$htaccessStr.'/', $htaccess)
);
$this->assertEquals(1,
preg_match('/php_value post_max_size '.$htaccessStr.'/', $htaccess)
);
$this->assertEquals($htaccessSize + $htaccessSizeMod, filesize($files['.htaccess']));

$userIni = file_get_contents($files['.user.ini']);
$this->assertEquals(1,
preg_match('/upload_max_filesize='.$userIniStr.'/', $userIni)
);
$this->assertEquals(1,
preg_match('/post_max_size='.$userIniStr.'/', $userIni)
);
$this->assertEquals($userIniSize + $userIniSizeMod, filesize($files['.user.ini']));
}
}