diff options
-rw-r--r-- | .htaccess | 48 | ||||
-rw-r--r-- | apps/files/admin.php | 3 | ||||
-rw-r--r-- | apps/files/index.php | 3 | ||||
-rw-r--r-- | apps/files/js/detailsview.js | 176 | ||||
-rw-r--r-- | apps/files/js/detailtabview.js | 6 | ||||
-rw-r--r-- | apps/files/js/tagsplugin.js | 3 | ||||
-rw-r--r-- | apps/files/templates/admin.php | 2 | ||||
-rw-r--r-- | apps/files/templates/test.png | bin | 0 -> 2302 bytes | |||
-rw-r--r-- | apps/files/tests/js/detailsviewSpec.js | 80 | ||||
-rw-r--r-- | apps/files_sharing/js/share.js | 8 | ||||
-rw-r--r-- | apps/files_sharing/js/sharetabview.js | 9 | ||||
-rw-r--r-- | apps/files_sharing/lib/capabilities.php | 1 | ||||
-rw-r--r-- | apps/files_sharing/tests/capabilities.php | 20 | ||||
-rw-r--r-- | config/config.sample.php | 33 | ||||
-rw-r--r-- | lib/private/files.php | 91 | ||||
-rw-r--r-- | lib/private/ocsclient.php | 2 | ||||
-rw-r--r-- | lib/private/server.php | 17 | ||||
-rw-r--r-- | lib/public/iservercontainer.php | 9 | ||||
-rw-r--r-- | tests/data/setUploadLimit/htaccess | 60 | ||||
-rw-r--r-- | tests/data/setUploadLimit/user.ini | 7 | ||||
-rw-r--r-- | tests/lib/files.php | 135 | ||||
-rw-r--r-- | tests/settings/controller/userscontrollertest.php | 18 |
22 files changed, 565 insertions, 166 deletions
diff --git a/.htaccess b/.htaccess index 65957a29838..35c478860d5 100644 --- a/.htaccess +++ b/.htaccess @@ -22,36 +22,36 @@ </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> + 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] + 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 + AddType image/svg+xml svg svgz + AddEncoding gzip svgz </IfModule> <IfModule mod_dir.c> -DirectoryIndex index.php index.html + DirectoryIndex index.php index.html </IfModule> AddDefaultCharset utf-8 Options -Indexes diff --git a/apps/files/admin.php b/apps/files/admin.php index 70f537d0db9..349c27ff742 100644 --- a/apps/files/admin.php +++ b/apps/files/admin.php @@ -42,9 +42,10 @@ if($_POST && OC_Util::isCallRegistered()) { } $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); diff --git a/apps/files/index.php b/apps/files/index.php index beae585cea4..a73caa50fbe 100644 --- a/apps/files/index.php +++ b/apps/files/index.php @@ -143,6 +143,9 @@ OCP\Util::addscript('files', 'fileactionsmenu'); OCP\Util::addscript('files', 'files'); OCP\Util::addscript('files', 'navigation'); OCP\Util::addscript('files', 'keyboardshortcuts'); + +\OC::$server->getEventDispatcher()->dispatch('OCA\Files::loadAdditionalScripts'); + $tmpl = new OCP\Template('files', 'index', 'user'); $tmpl->assign('usedSpacePercent', (int)$storageInfo['relative']); $tmpl->assign('owner', $storageInfo['owner']); diff --git a/apps/files/js/detailsview.js b/apps/files/js/detailsview.js index 4df359e4523..a4ebe90cd64 100644 --- a/apps/files/js/detailsview.js +++ b/apps/files/js/detailsview.js @@ -9,23 +9,26 @@ */ (function() { - var TEMPLATE = '<div>' + - ' <div class="detailFileInfoContainer">' + - ' </div>' + - ' <div>' + - ' <ul class="tabHeaders">' + - ' </ul>' + - ' <div class="tabsContainer">' + - ' </div>' + - ' </div>' + - ' <a class="close icon-close" href="#" alt="{{closeLabel}}"></a>' + + ' <div class="detailFileInfoContainer">' + + ' </div>' + + ' <div>' + + ' {{#if tabHeaders}}' + + ' <ul class="tabHeaders">' + + ' {{#each tabHeaders}}' + + ' <li class="tabHeader" data-tabid="{{tabId}}" data-tabindex="{{tabIndex}}">' + + ' <a href="#">{{label}}</a>' + + ' </li>' + + ' {{/each}}' + + ' </ul>' + + ' {{/if}}' + + ' <div class="tabsContainer">' + + ' </div>' + + ' </div>' + + ' <a class="close icon-close" href="#" alt="{{closeLabel}}"></a>' + '</div>'; - var TEMPLATE_TAB_HEADER = - '<li class="tabHeader {{#if selected}}selected{{/if}}" data-tabid="{{tabId}}" data-tabindex="{{tabIndex}}"><a href="#">{{label}}</a></li>'; - /** * @class OCA.Files.DetailsView * @classdesc @@ -39,7 +42,6 @@ className: 'detailsView', _template: null, - _templateTabHeader: null, /** * List of detail tab views @@ -62,6 +64,11 @@ */ _currentTabId: null, + /** + * Dirty flag, whether the view needs to be rerendered + */ + _dirty: false, + events: { 'click a.close': '_onClose', 'click .tabHeaders .tabHeader': '_onClickTab' @@ -74,8 +81,10 @@ this._tabViews = []; this._detailFileInfoViews = []; + this._dirty = true; + // uncomment to add some dummy tabs for testing - // this._addTestTabs(); + //this._addTestTabs(); }, _onClose: function(event) { @@ -85,28 +94,21 @@ _onClickTab: function(e) { var $target = $(e.target); + e.preventDefault(); if (!$target.hasClass('tabHeader')) { $target = $target.closest('.tabHeader'); } - var tabIndex = $target.attr('data-tabindex'); - var targetTab; - if (_.isUndefined(tabIndex)) { + var tabId = $target.attr('data-tabid'); + if (_.isUndefined(tabId)) { return; } - this.$el.find('.tabsContainer .tab').addClass('hidden'); - targetTab = this._tabViews[tabIndex]; - targetTab.$el.removeClass('hidden'); - - this.$el.find('.tabHeaders li').removeClass('selected'); - $target.addClass('selected'); - - e.preventDefault(); + this.selectTab(tabId); }, _addTestTabs: function() { for (var j = 0; j < 2; j++) { - var testView = new OCA.Files.DetailTabView('testtab' + j); + var testView = new OCA.Files.DetailTabView({id: 'testtab' + j}); testView.index = j; testView.getLabel = function() { return 'Test tab ' + this.index; }; testView.render = function() { @@ -119,26 +121,34 @@ } }, + template: function(vars) { + if (!this._template) { + this._template = Handlebars.compile(TEMPLATE); + } + return this._template(vars); + }, + /** * Renders this details view */ render: function() { - var self = this; - - if (!this._template) { - this._template = Handlebars.compile(TEMPLATE); - } + var templateVars = { + closeLabel: t('files', 'Close') + }; - if (!this._templateTabHeader) { - this._templateTabHeader = Handlebars.compile(TEMPLATE_TAB_HEADER); + if (this._tabViews.length > 1) { + // only render headers if there is more than one available + templateVars.tabHeaders = _.map(this._tabViews, function(tabView, i) { + return { + tabId: tabView.id, + tabIndex: i, + label: tabView.getLabel() + }; + }); } - this.$el.html(this._template({ - closeLabel: t('files', 'Close') - })); + this.$el.html(this.template(templateVars)); - var $tabsContainer = this.$el.find('.tabsContainer'); - var $tabHeadsContainer = this.$el.find('.tabHeaders'); var $detailsContainer = this.$el.find('.detailFileInfoContainer'); // render details @@ -146,29 +156,58 @@ $detailsContainer.append(detailView.get$()); }); - if (this._tabViews.length > 0) { - if (!this._currentTab) { - this._currentTab = this._tabViews[0].id; - } - - // render tabs - _.each(this._tabViews, function(tabView, i) { - // hidden by default - var $el = tabView.get$(); - var isCurrent = (tabView.id === self._currentTab); - if (!isCurrent) { - $el.addClass('hidden'); - } - $tabsContainer.append($el); + if (!this._currentTabId && this._tabViews.length > 0) { + this._currentTabId = this._tabViews[0].id; + } - $tabHeadsContainer.append(self._templateTabHeader({ - tabId: tabView.id, - tabIndex: i, - label: tabView.getLabel(), - selected: isCurrent - })); - }); + this.selectTab(this._currentTabId); + + this._dirty = false; + }, + + /** + * Selects the given tab by id + * + * @param {string} tabId tab id + */ + selectTab: function(tabId) { + if (!tabId) { + return; + } + + var tabView = _.find(this._tabViews, function(tab) { + return tab.id === tabId; + }); + + if (!tabView) { + console.warn('Details view tab with id "' + tabId + '" not found'); + return; } + + this._currentTabId = tabId; + + var $tabsContainer = this.$el.find('.tabsContainer'); + var $tabEl = $tabsContainer.find('#' + tabId); + + // hide other tabs + $tabsContainer.find('.tab').addClass('hidden'); + + // tab already rendered ? + if (!$tabEl.length) { + // render tab + $tabsContainer.append(tabView.$el); + $tabEl = tabView.$el; + } + + // this should trigger tab rendering + tabView.setFileInfo(this.model); + + $tabEl.removeClass('hidden'); + + // update tab headers + var $tabHeaders = this.$el.find('.tabHeaders li'); + $tabHeaders.removeClass('selected'); + $tabHeaders.filterAttr('data-tabid', tabView.id).addClass('selected'); }, /** @@ -179,12 +218,19 @@ setFileInfo: function(fileInfo) { this.model = fileInfo; - this.render(); + if (this._dirty) { + this.render(); + } - // notify all panels - _.each(this._tabViews, function(tabView) { + if (this._currentTabId) { + // only update current tab, others will be updated on-demand + var tabId = this._currentTabId; + var tabView = _.find(this._tabViews, function(tab) { + return tab.id === tabId; + }); tabView.setFileInfo(fileInfo); - }); + } + _.each(this._detailFileInfoViews, function(detailView) { detailView.setFileInfo(fileInfo); }); @@ -206,6 +252,7 @@ */ addTabView: function(tabView) { this._tabViews.push(tabView); + this._dirty = true; }, /** @@ -215,6 +262,7 @@ */ addDetailView: function(detailView) { this._detailFileInfoViews.push(detailView); + this._dirty = true; } }); diff --git a/apps/files/js/detailtabview.js b/apps/files/js/detailtabview.js index b2e02971fb4..67f8b535abd 100644 --- a/apps/files/js/detailtabview.js +++ b/apps/files/js/detailtabview.js @@ -71,8 +71,10 @@ * @param {OCA.Files.FileInfoModel} fileInfo file info to set */ setFileInfo: function(fileInfo) { - this.model = fileInfo; - this.render(); + if (this.model !== fileInfo) { + this.model = fileInfo; + this.render(); + } }, /** diff --git a/apps/files/js/tagsplugin.js b/apps/files/js/tagsplugin.js index d8552c71e45..ed1105a1706 100644 --- a/apps/files/js/tagsplugin.js +++ b/apps/files/js/tagsplugin.js @@ -108,6 +108,8 @@ } toggleStar($actionEl, !isFavorite); + context.fileInfoModel.set('tags', tags); + self.applyFileTags( dir + '/' + fileName, tags, @@ -124,7 +126,6 @@ toggleStar($actionEl, (newTags.indexOf(OC.TAG_FAVORITE) >= 0)); $file.attr('data-tags', newTags.join('|')); $file.attr('data-favorite', !isFavorite); - context.fileInfoModel.set('tags', newTags); fileInfo.tags = newTags; }); } diff --git a/apps/files/templates/admin.php b/apps/files/templates/admin.php index adf756a12be..822fc779bd3 100644 --- a/apps/files/templates/admin.php +++ b/apps/files/templates/admin.php @@ -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: ?> diff --git a/apps/files/templates/test.png b/apps/files/templates/test.png Binary files differnew file mode 100644 index 00000000000..2b90216f797 --- /dev/null +++ b/apps/files/templates/test.png diff --git a/apps/files/tests/js/detailsviewSpec.js b/apps/files/tests/js/detailsviewSpec.js index 4261aa53c94..852f8b04293 100644 --- a/apps/files/tests/js/detailsviewSpec.js +++ b/apps/files/tests/js/detailsviewSpec.js @@ -67,32 +67,75 @@ describe('OCA.Files.DetailsView tests', function() { var testView, testView2; beforeEach(function() { - testView = new OCA.Files.DetailTabView('test1'); - testView2 = new OCA.Files.DetailTabView('test2'); + testView = new OCA.Files.DetailTabView({id: 'test1'}); + testView2 = new OCA.Files.DetailTabView({id: 'test2'}); detailsView.addTabView(testView); detailsView.addTabView(testView2); detailsView.render(); }); - it('renders registered tabs', function() { - expect(detailsView.$el.find('.tab').length).toEqual(2); + it('initially renders only the selected tab', function() { + expect(detailsView.$el.find('.tab').length).toEqual(1); + expect(detailsView.$el.find('.tab').attr('id')).toEqual('test1'); }); - it('updates registered tabs when fileinfo is updated', function() { - var tabRenderStub = sinon.stub(OCA.Files.DetailTabView.prototype, 'render'); - var fileInfo = {id: 5, name: 'test.txt'}; - tabRenderStub.reset(); - detailsView.setFileInfo(fileInfo); + it('updates tab model and rerenders on-demand as soon as it gets selected', function() { + var tab1RenderStub = sinon.stub(testView, 'render'); + var tab2RenderStub = sinon.stub(testView2, 'render'); + var fileInfo1 = new OCA.Files.FileInfoModel({id: 5, name: 'test.txt'}); + var fileInfo2 = new OCA.Files.FileInfoModel({id: 8, name: 'test2.txt'}); - expect(testView.getFileInfo()).toEqual(fileInfo); - expect(testView2.getFileInfo()).toEqual(fileInfo); + detailsView.setFileInfo(fileInfo1); + + // first tab renders, not the second one + expect(tab1RenderStub.calledOnce).toEqual(true); + expect(tab2RenderStub.notCalled).toEqual(true); + + // info got set only to the first visible tab + expect(testView.getFileInfo()).toEqual(fileInfo1); + expect(testView2.getFileInfo()).toBeUndefined(); + + // select second tab for first render + detailsView.$el.find('.tabHeader').eq(1).click(); + + // second tab got rendered + expect(tab2RenderStub.calledOnce).toEqual(true); + expect(testView2.getFileInfo()).toEqual(fileInfo1); + + // select the first tab again + detailsView.$el.find('.tabHeader').eq(0).click(); + + // no re-render + expect(tab1RenderStub.calledOnce).toEqual(true); + expect(tab2RenderStub.calledOnce).toEqual(true); - expect(tabRenderStub.callCount).toEqual(2); - tabRenderStub.restore(); + tab1RenderStub.reset(); + tab2RenderStub.reset(); + + // switch to another file + detailsView.setFileInfo(fileInfo2); + + // only the visible tab was updated and rerendered + expect(tab1RenderStub.calledOnce).toEqual(true); + expect(testView.getFileInfo()).toEqual(fileInfo2); + + // second/invisible tab still has old info, not rerendered + expect(tab2RenderStub.notCalled).toEqual(true); + expect(testView2.getFileInfo()).toEqual(fileInfo1); + + // reselect the second one + detailsView.$el.find('.tabHeader').eq(1).click(); + + // second tab becomes visible, updated and rendered + expect(testView2.getFileInfo()).toEqual(fileInfo2); + expect(tab2RenderStub.calledOnce).toEqual(true); + + tab1RenderStub.restore(); + tab2RenderStub.restore(); }); it('selects the first tab by default', function() { expect(detailsView.$el.find('.tabHeader').eq(0).hasClass('selected')).toEqual(true); expect(detailsView.$el.find('.tabHeader').eq(1).hasClass('selected')).toEqual(false); expect(detailsView.$el.find('.tab').eq(0).hasClass('hidden')).toEqual(false); - expect(detailsView.$el.find('.tab').eq(1).hasClass('hidden')).toEqual(true); + expect(detailsView.$el.find('.tab').eq(1).length).toEqual(0); }); it('switches the current tab when clicking on tab header', function() { detailsView.$el.find('.tabHeader').eq(1).click(); @@ -101,5 +144,14 @@ describe('OCA.Files.DetailsView tests', function() { expect(detailsView.$el.find('.tab').eq(0).hasClass('hidden')).toEqual(true); expect(detailsView.$el.find('.tab').eq(1).hasClass('hidden')).toEqual(false); }); + it('does not render tab headers when only one tab exists', function() { + detailsView.remove(); + detailsView = new OCA.Files.DetailsView(); + testView = new OCA.Files.DetailTabView({id: 'test1'}); + detailsView.addTabView(testView); + detailsView.render(); + + expect(detailsView.$el.find('.tabHeader').length).toEqual(0); + }); }); }); diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 04700b84011..c124d390d04 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -60,6 +60,14 @@ return tr; }; + var oldElementToFile = fileList.elementToFile; + fileList.elementToFile = function($el) { + var fileInfo = oldElementToFile.apply(this, arguments); + fileInfo.sharePermissions = $el.attr('data-share-permissions') || undefined; + fileInfo.shareOwner = $el.attr('data-share-owner') || undefined; + return fileInfo; + }; + // use delegate to catch the case with multiple file lists fileList.$el.on('fileActionsReady', function(ev){ var fileList = ev.fileList; diff --git a/apps/files_sharing/js/sharetabview.js b/apps/files_sharing/js/sharetabview.js index 5f4a21a4a57..ee572b747ea 100644 --- a/apps/files_sharing/js/sharetabview.js +++ b/apps/files_sharing/js/sharetabview.js @@ -10,7 +10,7 @@ (function() { var TEMPLATE = - '<div>Owner: {{owner}}'; + '<div><ul>{{#if owner}}<li>Owner: {{owner}}</li>{{/if}}</ul></div>'; /** * @memberof OCA.Sharing @@ -37,8 +37,13 @@ } if (this.model) { + console.log(this.model); + var owner = this.model.get('shareOwner'); + if (owner === OC.currentUser) { + owner = null; + } this.$el.append(this._template({ - owner: this.model.get('shareOwner') || OC.currentUser + owner: owner })); } else { diff --git a/apps/files_sharing/lib/capabilities.php b/apps/files_sharing/lib/capabilities.php index 0338dfe96e2..ea71c47a05c 100644 --- a/apps/files_sharing/lib/capabilities.php +++ b/apps/files_sharing/lib/capabilities.php @@ -59,6 +59,7 @@ class Capabilities implements ICapability { } $public['send_mail'] = $this->config->getAppValue('core', 'shareapi_allow_public_notification', 'no') === 'yes'; + $public['upload'] = $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes'; } $res["public"] = $public; diff --git a/apps/files_sharing/tests/capabilities.php b/apps/files_sharing/tests/capabilities.php index 7656849120b..5b9789ce324 100644 --- a/apps/files_sharing/tests/capabilities.php +++ b/apps/files_sharing/tests/capabilities.php @@ -183,4 +183,24 @@ class FilesSharingCapabilitiesTest extends \Test\TestCase { $result = $this->getResults($map); $this->assertFalse($result['resharing']); } + + public function testLinkPublicUpload() { + $map = [ + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ['core', 'shareapi_allow_public_upload', 'yes', 'yes'], + ]; + $result = $this->getResults($map); + $this->assertTrue($result['public']['upload']); + } + + public function testLinkNoPublicUpload() { + $map = [ + ['core', 'shareapi_allow_links', 'yes', 'yes'], + ['core', 'shareapi_allow_public_upload', 'yes', 'no'], + ]; + $result = $this->getResults($map); + $this->assertFalse($result['public']['upload']); + } + + } diff --git a/config/config.sample.php b/config/config.sample.php index 047e7ccdd12..2b4873d5310 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -284,7 +284,7 @@ $CONFIG = array( 'mail_smtpmode' => 'sendmail', /** - * This depends on ``mail_smtpmode``. Specified the IP address of your mail + * This depends on ``mail_smtpmode``. Specify the IP address of your mail * server host. This may contain multiple hosts separated by a semi-colon. If * you need to specify the port number append it to the IP address separated by * a colon, like this: ``127.0.0.1:24``. @@ -297,7 +297,7 @@ $CONFIG = array( 'mail_smtpport' => 25, /** - * This depends on ``mail_smtpmode``. This set an SMTP server timeout, in + * This depends on ``mail_smtpmode``. This sets the SMTP server timeout, in * seconds. You may need to increase this if you are running an anti-malware or * spam scanner. */ @@ -373,7 +373,7 @@ $CONFIG = array( 'overwritecondaddr' => '', /** - * Use this configuration parameter to specify the base url for any urls which + * Use this configuration parameter to specify the base URL for any URLs which * are generated within ownCloud using any kind of command line tools (cron or * occ). The value should contain the full base URL: * ``https://www.example.com/owncloud`` @@ -412,18 +412,19 @@ $CONFIG = array( * ownCloud 8.1 and before. * * Available values: - * ``auto`` default setting. keeps files and folders in the trash bin + * + * * ``auto`` default setting. keeps files and folders in the trash bin * for 30 days and automatically deletes anytime after that * if space is needed (note: files may not be deleted if space * is not needed). - * ``D, auto`` keeps files and folders in the trash bin for D+ days, + * * ``D, auto`` keeps files and folders in the trash bin for D+ days, * delete anytime if space needed (note: files may not be deleted * if space is not needed) * * ``auto, D`` delete all files in the trash bin that are older than D days * automatically, delete other files anytime if space needed * * ``D1, D2`` keep files and folders the in trash bin for at least D1 days * and delete when exceeds D2 days - * ``disabled`` trash bin auto clean disabled, files and folders will be + * * ``disabled`` trash bin auto clean disabled, files and folders will be * kept forever */ 'trashbin_retention_obligation' => 'auto', @@ -550,8 +551,8 @@ $CONFIG = array( * Location of the lock file for cron executions can be specified here. * Default is within the tmp directory. The file is named in the following way: * owncloud-server-$INSTANCEID-cron.lock - * where $INSTANCEID is the string specified in the instanceid field. - * Because the cron lock file is accessed in regular intervals, it may prevent + * where $INSTANCEID is the string specified in the ``instanceid`` field. + * Because the cron lock file is accessed at regular intervals, it may prevent * enabled disk drives from spinning down. A different location for this file * can solve such issues. */ @@ -654,7 +655,7 @@ $CONFIG = array( /** * By default, ownCloud can generate previews for the following filetypes: * - * - Images files + * - Image files * - Covers of MP3 files * - Text documents * @@ -765,7 +766,7 @@ $CONFIG = array( /** * defines the interval in minutes for the background job that checks user - * existance and marks them as ready to be cleaned up. The number is always + * existence and marks them as ready to be cleaned up. The number is always * minutes. Setting it to 0 disables the feature. * See command line (occ) methods ldap:show-remnants and user:delete */ @@ -782,10 +783,10 @@ $CONFIG = array( /** * Enable maintenance mode to disable ownCloud * - * If you want to prevent users to login to ownCloud before you start doing some - * maintenance work, you need to set the value of the maintenance parameter to - * true. Please keep in mind that users who are already logged-in are kicked out - * of ownCloud instantly. + * If you want to prevent users from logging in to ownCloud before you start + * doing some maintenance work, you need to set the value of the maintenance + * parameter to true. Please keep in mind that users who are already logged-in + * are kicked out of ownCloud instantly. */ 'maintenance' => false, @@ -1003,7 +1004,7 @@ $CONFIG = array( /** * The parent of the directory where css and js assets will be stored if - * piplelining is enabled; this defaults to the ownCloud directory. The assets + * pipelining is enabled; this defaults to the ownCloud directory. The assets * will be stored in a subdirectory of this directory named 'assets'. The * server *must* be configured to serve that directory as $WEBROOT/assets. * You will only likely need to change this if the main ownCloud directory @@ -1059,7 +1060,7 @@ $CONFIG = array( * Enables transactional file locking. * This is disabled by default as it is still beta. * - * Prevents concurrent processes to access the same files + * Prevents concurrent processes from accessing the same files * at the same time. Can help prevent side effects that would * be caused by concurrent operations. Mainly relevant for * very large installations with many users working with diff --git a/lib/private/files.php b/lib/private/files.php index b61d09d8a0c..6268bf8a129 100644 --- a/lib/private/files.php +++ b/lib/private/files.php @@ -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 @@ -268,58 +269,80 @@ class OC_Files { * 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)) { + $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); } - //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; } diff --git a/lib/private/ocsclient.php b/lib/private/ocsclient.php index d6593e5089a..78df3b79bb6 100644 --- a/lib/private/ocsclient.php +++ b/lib/private/ocsclient.php @@ -272,7 +272,7 @@ class OCSClient { $tmp = $data->data->content; if (is_null($tmp)) { - \OCP\Util::writeLog('core', 'No update found at the ownCloud appstore for app ' . $id, \OCP\Util::INFO); + \OCP\Util::writeLog('core', 'No update found at the ownCloud appstore for app ' . $id, \OCP\Util::DEBUG); return null; } diff --git a/lib/private/server.php b/lib/private/server.php index 618431ff2d4..89001567219 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -51,7 +51,6 @@ use OC\Http\Client\ClientService; use OC\Lock\MemcacheLockingProvider; use OC\Lock\NoopLockingProvider; use OC\Mail\Mailer; -use OC\Memcache\NullCache; use OC\Security\CertificateManager; use OC\Security\Crypto; use OC\Security\Hasher; @@ -59,6 +58,8 @@ use OC\Security\SecureRandom; use OC\Security\TrustedDomainHelper; use OC\Tagging\TagMapper; use OCP\IServerContainer; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * Class Server @@ -457,7 +458,9 @@ class Server extends SimpleContainer implements IServerContainer { }); return $manager; }); - + $this->registerService('EventDispatcher', function() { + return new EventDispatcher(); + }); } /** @@ -963,4 +966,14 @@ class Server extends SimpleContainer implements IServerContainer { public function getCapabilitiesManager() { return $this->query('CapabilitiesManager'); } + + /** + * Get the EventDispatcher + * + * @return EventDispatcherInterface + * @since 8.2.0 + */ + public function getEventDispatcher() { + return $this->query('EventDispatcher'); + } } diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php index ab1729da255..a6d83156de3 100644 --- a/lib/public/iservercontainer.php +++ b/lib/public/iservercontainer.php @@ -39,6 +39,7 @@ // use OCP namespace for all classes that are considered public. // This means that they should be used by apps instead of the internal ownCloud classes namespace OCP; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -439,4 +440,12 @@ interface IServerContainer { */ public function getMimeTypeDetector(); + + /** + * Get the EventDispatcher + * + * @return EventDispatcherInterface + * @since 8.2.0 + */ + public function getEventDispatcher(); } diff --git a/tests/data/setUploadLimit/htaccess b/tests/data/setUploadLimit/htaccess new file mode 100644 index 00000000000..65957a29838 --- /dev/null +++ b/tests/data/setUploadLimit/htaccess @@ -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> diff --git a/tests/data/setUploadLimit/user.ini b/tests/data/setUploadLimit/user.ini new file mode 100644 index 00000000000..c5996e8d47e --- /dev/null +++ b/tests/data/setUploadLimit/user.ini @@ -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 diff --git a/tests/lib/files.php b/tests/lib/files.php new file mode 100644 index 00000000000..6808b3e9f64 --- /dev/null +++ b/tests/lib/files.php @@ -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'])); + } +} diff --git a/tests/settings/controller/userscontrollertest.php b/tests/settings/controller/userscontrollertest.php index 5f98cf21c04..06065a8454e 100644 --- a/tests/settings/controller/userscontrollertest.php +++ b/tests/settings/controller/userscontrollertest.php @@ -1390,9 +1390,11 @@ class UsersControllerTest extends \Test\TestCase { public function setEmailAddressData() { return [ - ['', true, false, true], - ['foobar@localhost', true, true, false], - ['foo@bar@localhost', false, false, false], + /* mailAddress, isValid, expectsUpdate, expectsDelete, canChangeDisplayName, responseCode */ + [ '', true, false, true, true, Http::STATUS_OK ], + [ 'foo@local', true, true, false, true, Http::STATUS_OK], + [ 'foo@bar@local', false, false, false, true, Http::STATUS_UNPROCESSABLE_ENTITY], + [ 'foo@local', true, false, false, false, Http::STATUS_FORBIDDEN], ]; } @@ -1404,7 +1406,7 @@ class UsersControllerTest extends \Test\TestCase { * @param bool $expectsUpdate * @param bool $expectsDelete */ - public function testSetEmailAddress($mailAddress, $isValid, $expectsUpdate, $expectsDelete) { + public function testSetEmailAddress($mailAddress, $isValid, $expectsUpdate, $expectsDelete, $canChangeDisplayName, $responseCode) { $this->container['IsAdmin'] = true; $user = $this->getMockBuilder('\OC\User\User') @@ -1413,6 +1415,10 @@ class UsersControllerTest extends \Test\TestCase { ->expects($this->any()) ->method('getUID') ->will($this->returnValue('foo')); + $user + ->expects($this->any()) + ->method('canChangeDisplayName') + ->will($this->returnValue($canChangeDisplayName)); $this->container['UserSession'] ->expects($this->atLeastOnce()) ->method('getUser') @@ -1455,7 +1461,9 @@ class UsersControllerTest extends \Test\TestCase { ); - $this->container['UsersController']->setMailAddress($user->getUID(), $mailAddress); + $response = $this->container['UsersController']->setMailAddress($user->getUID(), $mailAddress); + + $this->assertSame($responseCode, $response->getStatus()); } } |