├── .bowerrc ├── .drone.star ├── .github └── dependabot.yml ├── .gitignore ├── .phan └── config.php ├── .php-cs-fixer.dist.php ├── .scrutinizer.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── COPYING-AGPL ├── Makefile ├── README.md ├── app ├── bootstrap.php └── config │ └── container.php ├── application.php ├── bower.json ├── box.json ├── composer.json ├── composer.lock ├── index.php ├── phpstan.neon ├── phpunit.xml ├── pub ├── css │ └── main.css ├── img │ ├── actions │ │ ├── checkmark-color.png │ │ ├── checkmark-color.svg │ │ ├── confirm.png │ │ ├── confirm.svg │ │ ├── error-color.png │ │ └── error-color.svg │ ├── loading-dark.gif │ ├── loading-small.gif │ ├── loading.gif │ ├── logo-icon.png │ ├── logo-icon.svg │ ├── logo.png │ └── logo.svg └── js │ ├── login.js │ └── main.js ├── sonar-project.properties ├── src ├── Command │ ├── BackupDataCommand.php │ ├── BackupDbCommand.php │ ├── CheckSystemCommand.php │ ├── CheckpointCommand.php │ ├── CleanCacheCommand.php │ ├── Command.php │ ├── DetectCommand.php │ ├── ExecuteCoreUpgradeScriptsCommand.php │ ├── InfoCommand.php │ ├── MaintenanceModeCommand.php │ ├── PostUpgradeCleanupCommand.php │ ├── PostUpgradeRepairCommand.php │ ├── PreUpgradeRepairCommand.php │ ├── RestartWebServerCommand.php │ ├── StartCommand.php │ └── UpdateConfigCommand.php ├── Console │ └── Application.php ├── Controller │ ├── DownloadController.php │ └── IndexController.php ├── Formatter │ └── HtmlOutputFormatter.php ├── Http │ └── Request.php ├── Resources │ └── views │ │ ├── base.php │ │ └── partials │ │ ├── error.php │ │ ├── inner.php │ │ └── login.php └── Utils │ ├── AppManager.php │ ├── Checkpoint.php │ ├── Collection.php │ ├── ConfigReader.php │ ├── DocLink.php │ ├── Feed.php │ ├── Fetcher.php │ ├── FilesystemHelper.php │ ├── Locator.php │ ├── OccRunner.php │ ├── Registry.php │ └── ZipExtractor.php ├── tests └── unit │ ├── Command │ └── ExecuteCoreUpgradeScriptsCommandTest.php │ ├── Controller │ └── DownloadControllerTest.php │ ├── Http │ └── RequestTest.php │ ├── Utils │ ├── AppManagerTest.php │ ├── CheckpointTest.php │ ├── ConfigReaderTest.php │ ├── DocLinkTest.php │ ├── FeedTest.php │ ├── FetcherTest.php │ ├── OccRunnerTest.php │ └── RegistryTest.php │ └── bootstrap.php └── vendor-bin ├── owncloud-codestyle └── composer.json ├── phan └── composer.json └── phpstan └── composer.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "pub/js/vendor" 3 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "22:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: guzzlehttp/guzzle 11 | versions: 12 | - ">= 6.a, < 7" 13 | - dependency-name: symfony/console 14 | versions: 15 | - 4.4.19 16 | - 4.4.20 17 | - dependency-name: symfony/process 18 | versions: 19 | - 4.4.19 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/ 2 | /pub/js/vendor/ 3 | /vendor/symfony/console/Tests/ 4 | /vendor/symfony/process/Tests 5 | /vendor/pimple/pimple/ext/ 6 | /vendor/pimple/pimple/src/Pimple/Tests/ 7 | /data 8 | /build 9 | /vendor 10 | /composer.lock 11 | .phpunit.result.cache 12 | *.exe 13 | 14 | /tests/output 15 | 16 | # Composer 17 | vendor/ 18 | vendor-bin/**/vendor 19 | vendor-bin/**/composer.lock 20 | .php_cs.cache 21 | .php-cs-fixer.cache 22 | 23 | 24 | .idea/ 25 | 26 | # Generated from .drone.star 27 | .drone.yml 28 | -------------------------------------------------------------------------------- /.phan/config.php: -------------------------------------------------------------------------------- 1 | null, 16 | 17 | // A list of directories that should be parsed for class and 18 | // method information. After excluding the directories 19 | // defined in exclude_analysis_directory_list, the remaining 20 | // files will be statically analyzed for errors. 21 | // 22 | // Thus, both first-party and third-party code being used by 23 | // your application should be included in this list. 24 | 'directory_list' => [ 25 | 'app', 26 | 'src', 27 | 'vendor', 28 | ], 29 | 30 | // A list of files to include in analysis 31 | 'file_list' => [ 32 | 'index.php', 33 | ], 34 | 35 | // A directory list that defines files that will be excluded 36 | // from static analysis, but whose class and method 37 | // information should be included. 38 | // 39 | // Generally, you'll want to include the directories for 40 | // third-party code (such as "vendor/") in this list. 41 | // 42 | // n.b.: If you'd like to parse but not analyze 3rd 43 | // party code, directories containing that code 44 | // should be added to both the `directory_list` 45 | // and `exclude_analysis_directory_list` arrays. 46 | 'exclude_analysis_directory_list' => [ 47 | // The things in "views" are mixed HTML+PHP that do not analyze nicely 48 | 'src/Resources/views', 49 | // Do not analyze the vendor code. We can't fix that. 50 | 'vendor', 51 | ], 52 | 53 | // A regular expression to match files to be excluded 54 | // from parsing and analysis and will not be read at all. 55 | // 56 | // This is useful for excluding groups of test or example 57 | // directories/files, unanalyzable files, or files that 58 | // can't be removed for whatever reason. 59 | // (e.g. '@Test\.php$@', or '@vendor/.*/(tests|Tests)/@') 60 | 'exclude_file_regex' => '@.*/[^/]*(tests|Tests|templates)/@', 61 | 62 | // If true, missing properties will be created when 63 | // they are first seen. If false, we'll report an 64 | // error message. 65 | "allow_missing_properties" => true, 66 | 67 | // If enabled, allow null to be cast as any array-like type. 68 | // This is an incremental step in migrating away from null_casts_as_any_type. 69 | // If null_casts_as_any_type is true, this has no effect. 70 | "null_casts_as_any_type" => true, 71 | 72 | // Backwards Compatibility Checking. This is slow 73 | // and expensive, but you should consider running 74 | // it before upgrading your version of PHP to a 75 | // new version that has backward compatibility 76 | // breaks. 77 | 'backward_compatibility_checks' => false, 78 | 79 | // The initial scan of the function's code block has no 80 | // type information for `$arg`. It isn't until we see 81 | // the call and rescan test()'s code block that we can 82 | // detect that it is actually returning the passed in 83 | // `string` instead of an `int` as declared. 84 | 'quick_mode' => false, 85 | 86 | // The minimum severity level to report on. This can be 87 | // set to Issue::SEVERITY_LOW, Issue::SEVERITY_NORMAL or 88 | // Issue::SEVERITY_CRITICAL. Setting it to only 89 | // critical issues is a good place to start on a big 90 | // sloppy mature code base. 91 | 'minimum_severity' => 5, 92 | 93 | // A set of fully qualified class-names for which 94 | // a call to parent::__construct() is required 95 | 'parent_constructor_required' => [ 96 | ], 97 | 98 | ]; 99 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | setUsingCache(true) 7 | ->getFinder() 8 | ->in(__DIR__); 9 | 10 | return $config; 11 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - javascript 3 | - php 4 | 5 | filter: 6 | excluded_paths: [vendor/*, pub/js/vendor/*, src/Tests/*] 7 | 8 | tools: 9 | external_code_coverage: 10 | timeout: 7200 # Timeout in seconds. 120 minutes 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). 6 | 7 | 8 | ## [Unreleased] 9 | 10 | 11 | ## [1.1.1] - 2024-06-20 12 | 13 | ### Changed 14 | 15 | - [#730](https://github.com/owncloud/updater/pull/730) - exit with success command line status 0 for not implemented methods 16 | - Bump libraries 17 | 18 | ## [1.1.0] 19 | 20 | ### Changed 21 | 22 | - [#711](https://github.com/owncloud/updater/pull/711) - Always return an int from Symfony Command execute method 23 | - Bump libraries 24 | 25 | ## [1.0.1] 26 | 27 | ### Changed 28 | 29 | - Bump psr/log from 1.1.1 to 1.1.2 - [#528](https://github.com/owncloud/updater/issues/528) 30 | - Bump symfony/process from 3.4.32 to 3.4.33 - [#526](https://github.com/owncloud/updater/issues/526) 31 | - Bump symfony/console from 3.4.32 to 3.4.33 - [#527](https://github.com/owncloud/updater/issues/527) 32 | - Bump guzzlehttp/guzzle from 5.3.3 to 5.3.4 - [#524](https://github.com/owncloud/updater/issues/524) 33 | - Bump psr/log from 1.1.0 to 1.1.1 - [#523](https://github.com/owncloud/updater/issues/523) 34 | - Collect known locations from old and new signature.json as well - [#522](https://github.com/owncloud/updater/issues/522) 35 | - Update dependencies - [#521](https://github.com/owncloud/updater/issues/521) 36 | 37 | ## [1.0.0] 38 | 39 | ### Changed 40 | 41 | - Decoupled release from core 42 | 43 | ### Fixed 44 | 45 | - Clarification that the updater.secret to be entered must be unhashed 46 | 47 | ### Removed 48 | 49 | - Removed support for PHP 5.6 50 | 51 | 52 | [Unreleased]: https://github.com/owncloud/updater/compare/v1.1.1...master 53 | [1.1.1]: https://github.com/owncloud/updater/compare/v1.1.0...v1.1.1 54 | [1.1.0]: https://github.com/owncloud/updater/compare/v1.0.1...v1.1.0 55 | [1.0.1]: https://github.com/owncloud/updater/compare/v1.0.0...v1.0.1 56 | [1.0.0]: https://github.com/owncloud/updater/compare/v10.1.1...v1.0.0 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting issues 2 | 3 | If you have questions about how to install or use ownCloud, please direct these to the [mailing list][mailinglist] or our [forum][forum]. We are also available on [IRC][irc]. 4 | 5 | ### Short version 6 | 7 | * The [**issue template can be found here**][template]. Please always use the issue template when reporting issues. 8 | 9 | ### Guidelines 10 | * Please search the existing issues first, it's likely that your issue was already reported or even fixed. 11 | - Go to one of the repositories, click "issues" and type any word in the top search/command bar. 12 | - You can also filter by appending e. g. "state:open" to the search string. 13 | - More info on [search syntax within github](https://help.github.com/articles/searching-issues) 14 | * This repository ([updater](https://github.com/owncloud/updater/issues)) is *only* for issues within the ownCloud updater code. 15 | * __SECURITY__: Report any potential security bug to security@owncloud.com following our [security policy](https://owncloud.org/security/) instead of filing an issue in our bug tracker 16 | * Report the issue using our [template][template], it includes all the information we need to track down the issue. 17 | 18 | Help us to maximize the effort we can spend fixing issues and adding new features, by not reporting duplicate issues. 19 | 20 | [template]: https://raw.github.com/owncloud/core/master/issue_template.md 21 | [mailinglist]: https://mailman.owncloud.org/mailman/listinfo/owncloud 22 | [forum]: https://forum.owncloud.org/ 23 | [irc]: https://webchat.freenode.net/?channels=owncloud&uio=d4 24 | 25 | ### Contribute Code and translations 26 | Please check [core's contribution guidelines](https://github.com/owncloud/core/blob/master/CONTRIBUTING.md) for further information about contributing code and translations. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | NPM := $(shell command -v npm 2> /dev/null) 4 | 5 | updater_doc_files=COPYING-AGPL README.md CHANGELOG.md 6 | updater_src_files=application.php index.php 7 | updater_src_dirs=app pub src vendor 8 | updater_all_src=$(updater_src_files) $(updater_src_dirs) $(updater_doc_files) 9 | build_dir=build 10 | dist_dir=$(build_dir)/dist 11 | COMPOSER_BIN=$(build_dir)/composer.phar 12 | BOWER=$(build_dir)/node_modules/bower/bin/bower 13 | PHPUNIT=php -d zend.enable_gc=0 ./vendor/bin/phpunit 14 | PHPUNITDBG=phpdbg -qrr -d memory_limit=4096M -d zend.enable_gc=0 ./vendor/bin/phpunit 15 | PHP_CS_FIXER=php -d zend.enable_gc=0 vendor-bin/owncloud-codestyle/vendor/bin/php-cs-fixer 16 | PHAN=php -d zend.enable_gc=0 vendor-bin/phan/vendor/bin/phan 17 | PHPSTAN=php -d zend.enable_gc=0 vendor-bin/phpstan/vendor/bin/phpstan 18 | 19 | # internal aliases 20 | composer_deps=vendor/ 21 | composer_dev_deps=lib/composer/phpunit 22 | js_deps=pub/js/vendor/ 23 | 24 | # 25 | # Catch-all rules 26 | # 27 | .PHONY: all 28 | all: $(composer_dev_deps) $(js_deps) 29 | 30 | .PHONY: clean 31 | clean: clean-composer-deps clean-js-deps clean-dist clean-build 32 | 33 | 34 | # 35 | # Basic required tools 36 | # 37 | $(COMPOSER_BIN): 38 | mkdir $(build_dir) 39 | cd $(build_dir) && curl -sS https://getcomposer.org/installer | php 40 | 41 | $(BOWER): 42 | $(NPM) install --prefix $(build_dir) bower 43 | touch $(BOWER) 44 | 45 | 46 | # 47 | # ownCloud updater PHP dependencies 48 | # 49 | $(composer_deps): $(COMPOSER_BIN) composer.json composer.lock 50 | php $(COMPOSER_BIN) install --no-dev 51 | 52 | $(composer_dev_deps): $(COMPOSER_BIN) composer.json composer.lock 53 | php $(COMPOSER_BIN) install --dev 54 | 55 | .PHONY: clean-composer-deps 56 | clean-composer-deps: 57 | rm -f $(COMPOSER_BIN) 58 | rm -Rf $(composer_deps) 59 | rm -Rf vendor-bin/**/vendor vendor-bin/**/composer.lock 60 | 61 | .PHONY: update-composer 62 | update-composer: $(COMPOSER_BIN) 63 | rm -f composer.lock 64 | php $(COMPOSER_BIN) install --prefer-dist 65 | 66 | # 67 | # ownCloud updater JavaScript dependencies 68 | # 69 | $(js_deps): $(BOWER) bower.json 70 | $(BOWER) install 71 | touch $(js_deps) 72 | 73 | .PHONY: install-js-deps 74 | install-js-deps: $(js_deps) 75 | 76 | .PHONY: update-js-deps 77 | update-js-deps: $(js_deps) 78 | 79 | 80 | .PHONY: clean-js-deps 81 | clean-js-deps: 82 | rm -Rf $(js_deps) 83 | 84 | ##------------ 85 | ## Tests 86 | ##------------ 87 | 88 | .PHONY: test-lint 89 | test-lint: ## Run php lint to check for syntax errors 90 | test-lint: 91 | find . -name \*.php -exec php -l "{}" \; 92 | 93 | .PHONY: test-php-unit 94 | test-php-unit: ## Run php unit tests 95 | test-php-unit: vendor/bin/phpunit 96 | $(PHPUNIT) --configuration ./phpunit.xml --testsuite unit 97 | 98 | .PHONY: test-php-unit-dbg 99 | test-php-unit-dbg: ## Run php unit tests using phpdbg 100 | test-php-unit-dbg: vendor/bin/phpunit 101 | $(PHPUNITDBG) --configuration ./phpunit.xml --testsuite unit 102 | 103 | .PHONY: test-php-style 104 | test-php-style: ## Run php-cs-fixer and check owncloud code-style 105 | test-php-style: vendor-bin/owncloud-codestyle/vendor 106 | $(PHP_CS_FIXER) fix -v --diff --allow-risky yes --dry-run 107 | 108 | .PHONY: test-php-style-fix 109 | test-php-style-fix: ## Run php-cs-fixer and fix code style issues 110 | test-php-style-fix: vendor-bin/owncloud-codestyle/vendor 111 | $(PHP_CS_FIXER) fix -v --diff --allow-risky yes 112 | 113 | 114 | .PHONY: test-php-phan 115 | test-php-phan: vendor-bin/phan/vendor 116 | $(PHAN) --config-file .phan/config.php --require-config-exists -p 117 | 118 | 119 | .PHONY: test-php-phpstan 120 | test-php-phpstan: vendor-bin/phpstan/vendor 121 | $(PHPSTAN) analyse --memory-limit=2G --configuration=./phpstan.neon --level=0 app pub src 122 | 123 | # 124 | # dist 125 | # 126 | 127 | $(dist_dir)/updater: $(composer_deps) $(js_deps) 128 | rm -Rf $@; mkdir -p $@ 129 | cp -R $(updater_all_src) $@ 130 | find $@ -name .gitkeep -delete 131 | find $@ -name .gitignore -delete 132 | find $@/{vendor/,src/} -type d -iname Test? -print | xargs rm -Rf 133 | find $@/{vendor/,src/} -name travis -print | xargs rm -Rf 134 | find $@/{vendor/,src/} -name doc -print | xargs rm -Rf 135 | find $@/{vendor/,src/} -iname \*.sh -delete 136 | find $@/{vendor/,src/} -iname \*.exe -delete 137 | find $@/pub/js/vendor/jquery \! -name jquery.min.* -type f -exec rm -f {} + 138 | find $@/pub/js/vendor/jquery/* -type d -exec rm -rf {} + 139 | 140 | $(dist_dir)/updater.tar.gz: $(dist_dir)/updater 141 | cd $(dist_dir) && tar --format=gnu --owner=nobody --group=nogroup -czf updater.tar.gz updater 142 | 143 | .PHONY: dist 144 | dist: $(dist_dir)/updater.tar.gz 145 | 146 | .PHONY: clean-dist 147 | clean-dist: 148 | rm -Rf $(dist_dir) 149 | 150 | .PHONY: clean-build 151 | clean-build: 152 | rm -Rf $(build_dir) 153 | 154 | # 155 | # Dependency management 156 | #-------------------------------------- 157 | composer.lock: composer.json 158 | @echo composer.lock is not up to date. 159 | 160 | vendor: composer.lock 161 | composer install --no-dev 162 | 163 | vendor/bin/phpunit: composer.lock 164 | composer install 165 | 166 | vendor/bamarni/composer-bin-plugin: composer.lock 167 | composer install 168 | 169 | vendor-bin/owncloud-codestyle/vendor: vendor/bamarni/composer-bin-plugin vendor-bin/owncloud-codestyle/composer.lock 170 | composer bin owncloud-codestyle install --no-progress 171 | 172 | vendor-bin/owncloud-codestyle/composer.lock: vendor-bin/owncloud-codestyle/composer.json 173 | @echo owncloud-codestyle composer.lock is not up to date. 174 | 175 | vendor-bin/phan/vendor: vendor/bamarni/composer-bin-plugin vendor-bin/phan/composer.lock 176 | composer bin phan install --no-progress 177 | 178 | vendor-bin/phan/composer.lock: vendor-bin/phan/composer.json 179 | @echo phan composer.lock is not up to date. 180 | 181 | vendor-bin/phpstan/vendor: vendor/bamarni/composer-bin-plugin vendor-bin/phpstan/composer.lock 182 | composer bin phpstan install --no-progress 183 | 184 | vendor-bin/phpstan/composer.lock: vendor-bin/phpstan/composer.json 185 | @echo phpstan composer.lock is not up to date. 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | updater 2 | ======= 3 | 4 | 5 | ## How does it work? 6 | - creates backup directory under the ownCloud data directory. 7 | - extracts update package content into the backup/packageVersion 8 | - makes the copy of the current instance (except data dir) to backup/currentVersion-randomstring 9 | - moves all folders except data, config and themes from the current instance to backup/tmp 10 | - moves all folders from backup/packageVersion to the current version 11 | 12 | 13 | ## Dev 14 | ``` 15 | git clone git@github.com:owncloud/updater.git 16 | cd updater 17 | make 18 | ``` 19 | 20 | [![Build Status](https://travis-ci.org/owncloud/updater.svg?branch=master)](https://travis-ci.org/owncloud/updater) 21 | -------------------------------------------------------------------------------- /app/bootstrap.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | \set_time_limit(0); 23 | 24 | \define('MINIMUM_PHP_VERSION', '7.0.8'); 25 | 26 | // Check PHP version 27 | if (\version_compare(PHP_VERSION, MINIMUM_PHP_VERSION) === -1) { 28 | echo 'This application requires at least PHP ' . MINIMUM_PHP_VERSION . PHP_EOL; 29 | echo 'You are currently running ' . PHP_VERSION . '. Please update your PHP version.' . PHP_EOL; 30 | exit(50); 31 | } 32 | 33 | // symlinks are not resolved by PHP properly 34 | // getcwd always reports source and not target 35 | if (\getcwd()) { 36 | \define('CURRENT_DIR', \trim(\getcwd())); 37 | } elseif (isset($_SERVER['PWD'])) { 38 | \define('CURRENT_DIR', $_SERVER['PWD']); 39 | } elseif (isset($_SERVER['SCRIPT_FILENAME'])) { 40 | \define('CURRENT_DIR', \dirname($_SERVER['SCRIPT_FILENAME'])); 41 | } else { 42 | echo 'Failed to detect current directory'; 43 | exit(1); 44 | } 45 | 46 | \session_start(); 47 | 48 | require \dirname(__DIR__) . '/vendor/autoload.php'; 49 | $container = require(__DIR__ . '/config/container.php'); 50 | -------------------------------------------------------------------------------- /app/config/container.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | use Pimple\Container; 23 | use GuzzleHttp\Client; 24 | use Owncloud\Updater\Console\Application; 25 | use Owncloud\Updater\Utils\AppManager; 26 | use Owncloud\Updater\Utils\Checkpoint; 27 | use Owncloud\Updater\Utils\ConfigReader; 28 | use Owncloud\Updater\Utils\Fetcher; 29 | use Owncloud\Updater\Utils\FilesystemHelper; 30 | use Owncloud\Updater\Utils\Locator; 31 | use Owncloud\Updater\Utils\OccRunner; 32 | use Owncloud\Updater\Utils\Registry; 33 | use Owncloud\Updater\Command\BackupDataCommand; 34 | use Owncloud\Updater\Command\BackupDbCommand; 35 | use Owncloud\Updater\Command\CheckpointCommand; 36 | use Owncloud\Updater\Command\CheckSystemCommand; 37 | use Owncloud\Updater\Command\CleanCacheCommand; 38 | use Owncloud\Updater\Command\DetectCommand; 39 | use Owncloud\Updater\Command\ExecuteCoreUpgradeScriptsCommand; 40 | use Owncloud\Updater\Command\InfoCommand; 41 | use Owncloud\Updater\Command\MaintenanceModeCommand; 42 | use Owncloud\Updater\Command\PostUpgradeCleanupCommand; 43 | use Owncloud\Updater\Command\PostUpgradeRepairCommand; 44 | use Owncloud\Updater\Command\PreUpgradeRepairCommand; 45 | use Owncloud\Updater\Command\RestartWebServerCommand; 46 | use Owncloud\Updater\Command\UpdateConfigCommand; 47 | use Owncloud\Updater\Command\StartCommand; 48 | 49 | $c = new Container(); 50 | 51 | /* 52 | $c['parameters'] = [ 53 | 'locator.file' => 'locator.xml' 54 | ]; 55 | */ 56 | 57 | $c['guzzle.httpClient'] = function ($c) { 58 | return new Client(); 59 | }; 60 | 61 | $c['utils.locator'] = function ($c) { 62 | return new Locator(CURRENT_DIR); 63 | }; 64 | 65 | $c['utils.occrunner'] = function ($c) { 66 | $disabled = \explode(',', \ini_get('disable_functions')); 67 | $isProcOpenEnabled = \function_exists('proc_open') && !\in_array('proc_open', $disabled); 68 | return new OccRunner($c['utils.locator'], $isProcOpenEnabled && IS_CLI); 69 | }; 70 | 71 | $c['utils.registry'] = function ($c) { 72 | return new Registry(); 73 | }; 74 | 75 | $c['utils.appmanager'] = function ($c) { 76 | return new AppManager($c['utils.occrunner']); 77 | }; 78 | $c['utils.filesystemhelper'] = function ($c) { 79 | return new FilesystemHelper(); 80 | }; 81 | $c['utils.checkpoint'] = function ($c) { 82 | return new Checkpoint($c['utils.locator'], $c['utils.filesystemhelper']); 83 | }; 84 | $c['utils.configReader'] = function ($c) { 85 | return new ConfigReader($c['utils.occrunner']); 86 | }; 87 | $c['utils.fetcher'] = function ($c) { 88 | return new Fetcher($c['guzzle.httpClient'], $c['utils.locator'], $c['utils.configReader']); 89 | }; 90 | 91 | $c['command.backupData'] = function ($c) { 92 | return new BackupDataCommand(); 93 | }; 94 | $c['command.backupDb'] = function ($c) { 95 | return new BackupDbCommand(); 96 | }; 97 | $c['command.checkpoint'] = function ($c) { 98 | return new CheckpointCommand(); 99 | }; 100 | $c['command.checkSystem'] = function ($c) { 101 | return new CheckSystemCommand(); 102 | }; 103 | $c['command.cleanCache'] = function ($c) { 104 | return new CleanCacheCommand(); 105 | }; 106 | $c['command.detect'] = function ($c) { 107 | return new DetectCommand($c['utils.fetcher'], $c['utils.configReader']); 108 | }; 109 | $c['command.executeCoreUpgradeScripts'] = function ($c) { 110 | return new ExecuteCoreUpgradeScriptsCommand($c['utils.occrunner']); 111 | }; 112 | $c['command.info'] = function ($c) { 113 | return new InfoCommand(); 114 | }; 115 | $c['command.maintenanceMode'] = function ($c) { 116 | return new MaintenanceModeCommand($c['utils.occrunner']); 117 | }; 118 | $c['command.postUpgradeCleanup'] = function ($c) { 119 | return new PostUpgradeCleanupCommand(); 120 | }; 121 | $c['command.postUpgradeRepair'] = function ($c) { 122 | return new PostUpgradeRepairCommand(); 123 | }; 124 | $c['command.preUpgradeRepair'] = function ($c) { 125 | return new PreUpgradeRepairCommand(); 126 | }; 127 | $c['command.restartWebServer'] = function ($c) { 128 | return new RestartWebServerCommand(); 129 | }; 130 | $c['command.updateCoreConfig'] = function ($c) { 131 | return new UpdateConfigCommand(); 132 | }; 133 | $c['command.start'] = function ($c) { 134 | return new StartCommand(); 135 | }; 136 | 137 | $c['commands'] = function ($c) { 138 | return [ 139 | $c['command.backupData'], 140 | $c['command.backupDb'], 141 | $c['command.checkpoint'], 142 | $c['command.checkSystem'], 143 | $c['command.cleanCache'], 144 | $c['command.detect'], 145 | $c['command.executeCoreUpgradeScripts'], 146 | $c['command.info'], 147 | $c['command.maintenanceMode'], 148 | $c['command.postUpgradeCleanup'], 149 | $c['command.postUpgradeRepair'], 150 | $c['command.preUpgradeRepair'], 151 | $c['command.restartWebServer'], 152 | $c['command.updateCoreConfig'], 153 | $c['command.start'], 154 | ]; 155 | }; 156 | 157 | $c['application'] = function ($c) { 158 | $application = new Application('ownCloud updater', '1.1.1'); 159 | $application->setContainer($c); 160 | $application->addCommands($c['commands']); 161 | $application->setDefaultCommand($c['command.start']->getName()); 162 | return $application; 163 | }; 164 | 165 | return $c; 166 | -------------------------------------------------------------------------------- /application.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | \define('IS_CLI', true); 24 | 25 | $oldWorkingDir = \getcwd(); 26 | if ($oldWorkingDir === false) { 27 | echo "This script can be run from the ownCloud root directory only." . PHP_EOL; 28 | echo "Can't determine current working dir - the script will continue to work but be aware of the above fact." . PHP_EOL; 29 | } elseif ($oldWorkingDir !== __DIR__ && !\chdir(__DIR__)) { 30 | echo "This script can be run from the ownCloud root directory only." . PHP_EOL; 31 | echo "Can't change to ownCloud root directory." . PHP_EOL; 32 | exit(1); 33 | } 34 | 35 | require __DIR__ . '/app/bootstrap.php'; 36 | /** @var \Owncloud\Updater\Console\Application $application */ 37 | $application = $container['application']; 38 | $application->run(); 39 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ownCloud Updater", 3 | "version": "1.0", 4 | "homepage": "https://github.com/owncloud/updater", 5 | "license": "AGPL", 6 | "private": true, 7 | "dependencies": { 8 | "jquery": "1.10.2" 9 | } 10 | } -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "directories": ["src", "app", "vendor"], 3 | "chmod": "0755", 4 | 5 | "files": [ "application.php" ], 6 | "main": "application.php", 7 | "output": "setup.phar", 8 | "stub": true, 9 | "web": false 10 | } 11 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoload": { 3 | "psr-4": { 4 | "Owncloud\\Updater\\": "src/", 5 | "Owncloud\\Updater\\Tests\\": "tests/unit" 6 | } 7 | }, 8 | "config": { 9 | "platform": { 10 | "php": "7.3" 11 | }, 12 | "allow-plugins": { 13 | "bamarni/composer-bin-plugin": true 14 | } 15 | }, 16 | "require": { 17 | "symfony/console": "^3.4", 18 | "pimple/pimple": "^3.5", 19 | "symfony/process": "^3.4", 20 | "guzzlehttp/guzzle": "^7.7.0", 21 | "psr/log": "^1.1", 22 | "league/plates": "^3.5" 23 | }, 24 | "require-dev": { 25 | "bamarni/composer-bin-plugin": "^1.8", 26 | "phpunit/phpunit": "^9.6" 27 | }, 28 | "extra": { 29 | "bamarni-bin": { 30 | "bin-links": false 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | use Owncloud\Updater\Controller\IndexController; 24 | 25 | \define('IS_CLI', false); 26 | require __DIR__ . '/app/bootstrap.php'; 27 | 28 | /* @phan-suppress-next-line PhanUndeclaredGlobalVariable */ 29 | $controller = new IndexController($container); 30 | $response = $controller->dispatch(); 31 | echo $response; 32 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | bootstrapFiles: 3 | - %currentWorkingDirectory%/app/bootstrap.php 4 | - %currentWorkingDirectory%/app/config/container.php 5 | ignoreErrors: 6 | - 7 | message: '#File ends with a trailing whitespace. This may cause problems when running the code in the web browser. Remove the closing \?\> mark or remove the whitespace.#' 8 | path: src/Resources/views/partials/error.php 9 | count: 1 10 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | ./tests/unit 11 | 12 | 13 | 14 | ./src 15 | 16 | 17 | ./vendor 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /pub/img/actions/checkmark-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud/updater/a3d4b5568c2ef36edf03374ae0a01c5c2b938190/pub/img/actions/checkmark-color.png -------------------------------------------------------------------------------- /pub/img/actions/checkmark-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pub/img/actions/confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud/updater/a3d4b5568c2ef36edf03374ae0a01c5c2b938190/pub/img/actions/confirm.png -------------------------------------------------------------------------------- /pub/img/actions/confirm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /pub/img/actions/error-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud/updater/a3d4b5568c2ef36edf03374ae0a01c5c2b938190/pub/img/actions/error-color.png -------------------------------------------------------------------------------- /pub/img/actions/error-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pub/img/loading-dark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud/updater/a3d4b5568c2ef36edf03374ae0a01c5c2b938190/pub/img/loading-dark.gif -------------------------------------------------------------------------------- /pub/img/loading-small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud/updater/a3d4b5568c2ef36edf03374ae0a01c5c2b938190/pub/img/loading-small.gif -------------------------------------------------------------------------------- /pub/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud/updater/a3d4b5568c2ef36edf03374ae0a01c5c2b938190/pub/img/loading.gif -------------------------------------------------------------------------------- /pub/img/logo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud/updater/a3d4b5568c2ef36edf03374ae0a01c5c2b938190/pub/img/logo-icon.png -------------------------------------------------------------------------------- /pub/img/logo-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pub/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud/updater/a3d4b5568c2ef36edf03374ae0a01c5c2b938190/pub/img/logo.png -------------------------------------------------------------------------------- /pub/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pub/js/login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 3 | * Lukas Reschke 4 | * This file is licensed under the Affero General Public License version 3 or later. 5 | * See the COPYING-README file. 6 | */ 7 | 8 | var OC = {}; 9 | var loginToken; 10 | 11 | (function(OC) { 12 | OC.Login = OC.Login || {}; 13 | 14 | OC.Login = { 15 | onLogin: function () { 16 | loginToken = $('#password').val(); 17 | $.ajax({ 18 | url: '.', 19 | headers: { 20 | 'X-Updater-Auth': loginToken, 21 | }, 22 | method: 'POST', 23 | success: function(data){ 24 | if(data !== 'false') { 25 | var body = $('body'); 26 | body.html(data); 27 | body.removeAttr('id'); 28 | body.attr('id', 'body-settings'); 29 | } else { 30 | $('#invalidPasswordWarning').show(); 31 | } 32 | } 33 | }); 34 | 35 | return false; 36 | } 37 | } 38 | 39 | })(OC); 40 | 41 | $(document).ready(function() { 42 | $('form[name=login]').submit(OC.Login.onLogin); 43 | }); 44 | -------------------------------------------------------------------------------- /pub/js/main.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | // Pass the auth token with any request 3 | $.ajaxSetup({ 4 | headers: {'X-Updater-Auth': loginToken} 5 | }); 6 | 7 | 8 | $.ajaxPrefilter(function( options, originalOptions, jqXHR ) { 9 | //disallow requests that don't match endpoint 10 | var endpoint = $('#meta-information').data('endpoint'); 11 | if (endpoint && originalOptions.url.match(endpoint)===null){ 12 | jqXHR.abort(); 13 | } 14 | }); 15 | 16 | // Setup a global AJAX error handler 17 | $(document).ajaxError( 18 | function (event, xhr, options, thrownError) { 19 | $('#error').text('Server error ' 20 | + xhr.status 21 | + ': ' 22 | + xhr.statusText 23 | + "\n" 24 | + 'Message: ' 25 | + thrownError 26 | + 'See your webserver logs for details.' 27 | 28 | ).show(); 29 | } 30 | ); 31 | 32 | var accordion = { 33 | setCurrent: function (stepId) { 34 | $('#progress .step').removeClass('current-step'); 35 | if (typeof stepId !== 'undefined') { 36 | $(stepId).addClass('current-step') 37 | .removeClass('passed-step') 38 | .removeClass('failed-step'); 39 | } 40 | }, 41 | setDone: function (stepId) { 42 | $(stepId).removeClass('current-step') 43 | .removeClass('failed-step') 44 | .addClass('passed-step'); 45 | }, 46 | setFailed: function (stepId) { 47 | $(stepId).removeClass('current-step') 48 | .removeClass('passed-step') 49 | .addClass('failed-step'); 50 | }, 51 | setContent: function (stepId, content, append) { 52 | var oldContent; 53 | if (typeof append !== 'undefined' && append) { 54 | oldContent = $(stepId).find('.output').html(); 55 | } else { 56 | oldContent = ''; 57 | } 58 | $(stepId).find('.output').html(oldContent + content); 59 | }, 60 | showContent: function (stepId) { 61 | $(stepId).find('.output').show(); 62 | }, 63 | hideContent: function (stepId) { 64 | $(stepId).find('.output').hide(); 65 | }, 66 | toggleContent: function (stepId) { 67 | $(stepId).find('.output').toggle(); 68 | } 69 | }, 70 | handleResponse = function (response, callback, node) { 71 | if (typeof node === 'undefined') { 72 | if (response.error_code !== 0) { 73 | node.text('Error ' + response.error_code).show(); 74 | } else { 75 | $('#error').hide(); 76 | } 77 | $('#output').html($('#output').html() + response.output).show(); 78 | } else { 79 | accordion.setContent(node, response.output); 80 | if (response.error_code !== 0) { 81 | accordion.showContent(node); 82 | accordion.setFailed(node); 83 | } else { 84 | accordion.hideContent(node); 85 | accordion.setDone(node); 86 | } 87 | } 88 | if (typeof callback === 'function') { 89 | callback(); 90 | } 91 | }, 92 | init = function () { 93 | accordion.setCurrent('#step-init'); 94 | $.post($('#meta-information').data('endpoint'), {command: 'upgrade:detect --only-check --exit-if-none'}) 95 | .then(function (response) { 96 | handleResponse( 97 | response, 98 | function () { 99 | accordion.showContent('#step-init'); 100 | }, 101 | '#step-init' 102 | ); 103 | accordion.setDone('#step-init'); 104 | accordion.setCurrent(); 105 | if (!response.error_code) { 106 | accordion.setContent('#step-init', '', true); 107 | } else { 108 | accordion.setContent('#step-init', '', true); 109 | } 110 | 111 | }); 112 | }; 113 | 114 | //setup handlers 115 | $(document).on('click', '#create-checkpoint', function () { 116 | $(this).attr('disabled', true); 117 | $.post( 118 | $('#meta-information').data('endpoint'), 119 | { 120 | command: 'upgrade:checkpoint --create' 121 | }, 122 | function (response) { 123 | $('#create-checkpoint').attr('disabled', false); 124 | handleResponse(response); 125 | } 126 | ); 127 | }); 128 | 129 | $(document).on('click', '#progress h2', function () { 130 | if ($(this).parent('li').hasClass('passed-step')) { 131 | accordion.toggleContent('#' + $(this).parent('li').attr('id')); 132 | } 133 | }); 134 | 135 | $(document).on('click', '#start-upgrade', function () { 136 | $('#output').html(''); 137 | $(this).attr('disabled', true); 138 | accordion.setCurrent('#step-check'); 139 | $.post($('#meta-information').data('endpoint'), {command: 'upgrade:checkSystem'}) 140 | .then(function (response) { 141 | if (response.error_code === 0){ 142 | accordion.setCurrent('#step-checkpoint'); 143 | } 144 | handleResponse(response, function () {}, '#step-check'); 145 | return response.error_code === 0 146 | ? $.post($('#meta-information').data('endpoint'), {command: 'upgrade:checkpoint --create'}) 147 | : $.Deferred() 148 | ; 149 | }) 150 | .then(function (response) { 151 | if (response.error_code === 0){ 152 | accordion.setCurrent('#step-download'); 153 | } 154 | handleResponse(response, function () {}, '#step-checkpoint'); 155 | return response.error_code === 0 156 | ? $.post($('#meta-information').data('endpoint'), {command: 'upgrade:detect'}) 157 | : $.Deferred() 158 | ; 159 | }) 160 | .then(function (response) { 161 | if (response.error_code === 0){ 162 | accordion.setCurrent('#step-coreupgrade'); 163 | } 164 | handleResponse(response, function () {}, '#step-download'); 165 | return response.error_code === 0 166 | ? $.post($('#meta-information').data('endpoint'), {command: 'upgrade:executeCoreUpgradeScripts'}) 167 | : $.Deferred() 168 | ; 169 | }) 170 | .then(function (response) { 171 | handleResponse( 172 | response, 173 | function () { 174 | accordion.showContent('#step-coreupgrade'); 175 | }, 176 | '#step-coreupgrade' 177 | ); 178 | return response.error_code === 0 179 | ? accordion.setCurrent('#step-coreupgrade') 180 | || $.post($('#meta-information').data('endpoint'), {command: 'upgrade:executeCoreUpgradeScripts'}) 181 | : $.Deferred() 182 | ; 183 | }) 184 | .then(function (response) { 185 | if (response.error_code === 0){ 186 | accordion.setCurrent('#step-finalize'); 187 | } 188 | handleResponse(response, function () {}, '#step-appupgrade'); 189 | return response.error_code === 0 190 | ? $.post($('#meta-information').data('endpoint'), {command: 'upgrade:restartWebServer'}) 191 | : $.Deferred() 192 | ; 193 | }) 194 | .then(function (response) { 195 | handleResponse( 196 | response, 197 | function () { 198 | accordion.showContent('#step-finalize'); 199 | }, 200 | '#step-finalize' 201 | ); 202 | return (response.error_code === 0 203 | ? accordion.setCurrent('#step-finalize') 204 | || $.post($('#meta-information').data('endpoint'), {command: 'upgrade:postUpgradeCleanup'}) 205 | : $.Deferred() 206 | ); 207 | }) 208 | .then(function (response) { 209 | handleResponse(response, function () {}, '#step-finalize'); 210 | if (response.error_code === 0){ 211 | accordion.setDone('#step-done'); 212 | accordion.setContent('#step-done', 'All done! Redirecting you to your ownCloud.'); 213 | accordion.showContent('#step-done'); 214 | setTimeout( 215 | function(){ 216 | window.location.href = $('#meta-information').data('endpoint').replace(/\/updater\/.*$/, ''); 217 | }, 218 | 3000 219 | ); 220 | } 221 | }); 222 | }); 223 | 224 | $(document).on('click', '#recheck', init); 225 | init(); 226 | }); 227 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | # Organization and project keys are displayed in the right sidebar of the project homepage 2 | sonar.organization=owncloud-1 3 | sonar.projectKey=owncloud_updater 4 | sonar.projectVersion=1.1.1 5 | sonar.host.url=https://sonarcloud.io 6 | 7 | # ===================================================== 8 | # Meta-data for the project 9 | # ===================================================== 10 | 11 | sonar.links.homepage=https://github.com/owncloud/updater 12 | sonar.links.ci=https://drone.owncloud.com/owncloud/updater/ 13 | sonar.links.scm=https://github.com/owncloud/updater 14 | sonar.links.issue=https://github.com/owncloud/updater/issues 15 | 16 | # ===================================================== 17 | # Properties that will be shared amongst all modules 18 | # ===================================================== 19 | 20 | # Just look in these directories for code 21 | sonar.sources=. 22 | sonar.inclusions=app/**,pub/**,src/** 23 | 24 | # Pull Requests 25 | sonar.pullrequest.provider=GitHub 26 | sonar.pullrequest.github.repository=owncloud/updater 27 | sonar.pullrequest.base=${env.SONAR_PULL_REQUEST_BASE} 28 | sonar.pullrequest.branch=${env.SONAR_PULL_REQUEST_BRANCH} 29 | sonar.pullrequest.key=${env.SONAR_PULL_REQUEST_KEY} 30 | 31 | # Properties specific to language plugins: 32 | sonar.php.coverage.reportPaths=results/clover-phpunit-php7.3-mariadb10.2.xml,results/clover-phpunit-php7.3-mysql8.0.xml,results/clover-phpunit-php7.3-postgres9.4.xml,results/clover-phpunit-php7.3-oracle.xml,results/clover-phpunit-php7.3-sqlite.xml 33 | sonar.javascript.lcov.reportPaths=results/lcov.info 34 | -------------------------------------------------------------------------------- /src/Command/BackupDataCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class BackupDataCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class BackupDataCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:backupData') 36 | ->setDescription('Backup data (optionally)') 37 | ; 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output): int { 46 | $output->writeln('upgrade:backupData command is not implemented'); 47 | return 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Command/BackupDbCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class BackupDbCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class BackupDbCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:backupDb') 36 | ->setDescription('Backup DB (optionally)') 37 | ; 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output): int { 46 | $output->writeln('upgrade:backupDb command is not implemented'); 47 | return 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Command/CheckSystemCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | use Owncloud\Updater\Utils\Collection; 27 | 28 | /** 29 | * Class CheckSystemCommand 30 | * 31 | * @package Owncloud\Updater\Command 32 | */ 33 | class CheckSystemCommand extends Command { 34 | protected $message = 'Checking system health.'; 35 | 36 | protected function configure() { 37 | $this 38 | ->setName('upgrade:checkSystem') 39 | ->setDescription('System check. System health and if dependencies are OK (we also count the number of files and DB entries and give time estimations based on hardcoded estimation)') 40 | ; 41 | } 42 | 43 | /** 44 | * @param InputInterface $input 45 | * @param OutputInterface $output 46 | * @return int 47 | */ 48 | protected function execute(InputInterface $input, OutputInterface $output): int { 49 | $locator = $this->container['utils.locator']; 50 | $fsHelper = $this->container['utils.filesystemhelper']; 51 | /** @var \Owncloud\Updater\Utils\Registry $registry */ 52 | $registry = $this->container['utils.registry']; 53 | /** @var \Owncloud\Updater\Utils\AppManager $appManager */ 54 | $appManager = $this->container['utils.appmanager']; 55 | $registry->set( 56 | 'notShippedApps', 57 | $appManager->getNotShippedApps() 58 | ); 59 | $docLink = $this->container['utils.docLink']; 60 | 61 | $collection = new Collection(); 62 | 63 | $rootDirItems= $locator->getRootDirItems(); 64 | foreach ($rootDirItems as $item) { 65 | $fsHelper->checkr($item, $collection); 66 | } 67 | $notReadableFiles = $collection->getNotReadable(); 68 | $notWritableFiles = $collection->getNotWritable(); 69 | 70 | if (\count($notReadableFiles)) { 71 | $output->writeln('The following files and directories are not readable:'); 72 | $output->writeln($this->longArrayToString($notReadableFiles)); 73 | } 74 | 75 | if (\count($notWritableFiles)) { 76 | $output->writeln('The following files and directories are not writable:'); 77 | $output->writeln($this->longArrayToString($notWritableFiles)); 78 | } 79 | 80 | if (\count($notReadableFiles) || \count($notWritableFiles)) { 81 | $output->writeln('Please check if owner and permissions for these files are correct.'); 82 | $output->writeln( 83 | \sprintf( 84 | 'See %s for details.', 85 | $docLink->getAdminManualUrl('installation/installation_wizard.html#strong-perms-label') 86 | ) 87 | ); 88 | return 2; 89 | } else { 90 | $output->writeln(' - file permissions are ok.'); 91 | } 92 | 93 | return 0; 94 | } 95 | 96 | /** 97 | * @param $array 98 | * @return string 99 | */ 100 | protected function longArrayToString($array) { 101 | if (\count($array)>7) { 102 | $shortArray = \array_slice($array, 0, 7); 103 | $more = \sprintf('... and %d more items', \count($array) - \count($shortArray)); 104 | \array_push($shortArray, $more); 105 | } else { 106 | $shortArray = $array; 107 | } 108 | $string = \implode(PHP_EOL, $shortArray); 109 | return $string; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Command/CheckpointCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Input\InputOption; 26 | use Symfony\Component\Console\Output\OutputInterface; 27 | 28 | /** 29 | * Class CheckpointCommand 30 | * 31 | * @package Owncloud\Updater\Command 32 | */ 33 | class CheckpointCommand extends Command { 34 | protected function configure() { 35 | $this 36 | ->setName('upgrade:checkpoint') 37 | ->setDescription('Create or restore ownCloud core files') 38 | ->addOption( 39 | 'create', 40 | null, 41 | InputOption::VALUE_NONE, 42 | 'create a checkpoint' 43 | ) 44 | ->addOption( 45 | 'restore', 46 | null, 47 | InputOption::VALUE_REQUIRED, 48 | 'revert files to a given checkpoint' 49 | ) 50 | ->addOption( 51 | 'list', 52 | null, 53 | InputOption::VALUE_OPTIONAL, 54 | 'show all checkpoints' 55 | ) 56 | ->addOption( 57 | 'remove', 58 | null, 59 | InputOption::VALUE_REQUIRED, 60 | 'remove a checkpoint' 61 | ) 62 | ; 63 | } 64 | 65 | /** 66 | * @param InputInterface $input 67 | * @param OutputInterface $output 68 | * @return int 69 | * @throws \Exception 70 | */ 71 | protected function execute(InputInterface $input, OutputInterface $output): int { 72 | \clearstatcache(); 73 | $checkpoint = $this->container['utils.checkpoint']; 74 | if ($input->getOption('create')) { 75 | try { 76 | $checkpointId = $checkpoint->create(); 77 | $output->writeln('Created checkpoint ' . $checkpointId); 78 | } catch (\Exception $e) { 79 | $output->writeln('Error while creating a checkpoint'); 80 | throw $e; 81 | } 82 | } elseif ($input->getOption('remove')) { 83 | $checkpointId = \stripslashes($input->getOption('remove')); 84 | try { 85 | $checkpoint->remove($checkpointId); 86 | $output->writeln('Removed checkpoint ' . $checkpointId); 87 | } catch (\UnexpectedValueException $e) { 88 | $output->writeln($e->getMessage()); 89 | return 1; 90 | } catch (\Exception $e) { 91 | $output->writeln('Error while removing a checkpoint ' . $checkpointId); 92 | return 1; 93 | } 94 | } elseif ($input->getOption('restore')) { 95 | $checkpointId = \stripslashes($input->getOption('restore')); 96 | try { 97 | $checkpoint->restore($checkpointId); 98 | $checkpoint->remove($checkpointId); 99 | $output->writeln('Restored checkpoint ' . $checkpointId); 100 | } catch (\UnexpectedValueException $e) { 101 | $output->writeln($e->getMessage()); 102 | return 1; 103 | } catch (\Exception $e) { 104 | $output->writeln('Error while restoring a checkpoint ' . $checkpointId); 105 | return 1; 106 | } 107 | } else { 108 | $checkpoints = $checkpoint->getAll(); 109 | if (\count($checkpoints)) { 110 | foreach ($checkpoints as $checkpoint) { 111 | $output->writeln(\sprintf('%s - %s', $checkpoint['title'], $checkpoint['date'])); 112 | } 113 | } else { 114 | $output->writeln('No checkpoints found'); 115 | } 116 | } 117 | return 0; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Command/CleanCacheCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class CleanCacheCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class CleanCacheCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:cleanCache') 36 | ->setDescription('clean all caches') 37 | ; 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output): int { 46 | $output->writeln('upgrade:cleanCache command is not implemented'); 47 | return 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Command/Command.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | /** 25 | * Class Command 26 | * 27 | * @package Owncloud\Updater\Command 28 | */ 29 | class Command extends \Symfony\Component\Console\Command\Command { 30 | /** 31 | * @var \Pimple\Container 32 | */ 33 | protected $container; 34 | 35 | /** 36 | * @var string 37 | */ 38 | protected $message = ''; 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getMessage() { 44 | return $this->message; 45 | } 46 | 47 | /** 48 | * @param $container 49 | */ 50 | public function setContainer($container) { 51 | $this->container = $container; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Command/DetectCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2021, ownCloud GmbH 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Input\InputOption; 26 | use Symfony\Component\Console\Output\OutputInterface; 27 | use Symfony\Component\Console\Question\ChoiceQuestion; 28 | use GuzzleHttp\Exception\ClientException; 29 | use Owncloud\Updater\Utils\Fetcher; 30 | use Owncloud\Updater\Utils\ConfigReader; 31 | use \Owncloud\Updater\Controller\DownloadController; 32 | 33 | /** 34 | * Class DetectCommand 35 | * 36 | * @package Owncloud\Updater\Command 37 | */ 38 | class DetectCommand extends Command { 39 | /** 40 | * @var Fetcher $fetcher 41 | */ 42 | protected $fetcher; 43 | 44 | /** 45 | * @var ConfigReader $configReader 46 | */ 47 | protected $configReader; 48 | 49 | /** 50 | * 51 | */ 52 | protected $output; 53 | 54 | /** 55 | * Constructor 56 | * 57 | * @param Fetcher $fetcher 58 | * @param ConfigReader $configReader 59 | */ 60 | public function __construct(Fetcher $fetcher, ConfigReader $configReader) { 61 | parent::__construct(); 62 | $this->fetcher = $fetcher; 63 | $this->configReader = $configReader; 64 | } 65 | 66 | protected function configure() { 67 | $this 68 | ->setName('upgrade:detect') 69 | ->setDescription('Detect 70 | - 1. currently existing code, 71 | - 2. version in config.php, 72 | - 3. online available version. 73 | (ASK) what to do? (download, upgrade, abort, …)') 74 | ->addOption( 75 | 'exit-if-none', 76 | null, 77 | InputOption::VALUE_NONE, 78 | 'exit with non-zero status code if new version is not found' 79 | ) 80 | ->addOption( 81 | 'only-check', 82 | null, 83 | InputOption::VALUE_NONE, 84 | 'Only check if update is available' 85 | ) 86 | ; 87 | ; 88 | } 89 | 90 | /** 91 | * @param InputInterface $input 92 | * @param OutputInterface $output 93 | * @return int 94 | */ 95 | protected function execute(InputInterface $input, OutputInterface $output): int { 96 | $registry = $this->container['utils.registry']; 97 | $registry->set('feed', false); 98 | 99 | $fsHelper = $this->container['utils.filesystemhelper']; 100 | $downloadController = new DownloadController($this->fetcher, $registry, $fsHelper); 101 | try { 102 | $currentVersion = $this->configReader->getByPath('system.version'); 103 | if (!\strlen($currentVersion)) { 104 | throw new \UnexpectedValueException('Could not detect installed version.'); 105 | } 106 | 107 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 108 | $this->getApplication()->getLogger()->info('ownCloud ' . $currentVersion . ' found'); 109 | $output->writeln('Current version is ' . $currentVersion); 110 | 111 | $feedData = $downloadController->checkFeed(); 112 | if (!$feedData['success']) { 113 | // Network errors, etc 114 | $output->writeln("Can't fetch feed."); 115 | $output->writeln($feedData['exception']->getMessage()); 116 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 117 | $this->getApplication()->logException($feedData['exception']); 118 | // Return a number to stop the queue 119 | return $input->getOption('exit-if-none') ? 4 : 0; 120 | } 121 | 122 | /** @var \Owncloud\Updater\Utils\Feed $feed */ 123 | $feed = $feedData['data']['feed']; 124 | if (!$feed->isValid()) { 125 | // Feed is empty. Means there are no updates 126 | $output->writeln('No updates found online.'); 127 | return $input->getOption('exit-if-none') ? 4 : 0; 128 | } 129 | 130 | $registry->set('feed', $feed); 131 | $output->writeln( 132 | \sprintf( 133 | 'Online version is %s [%s]', 134 | $feed->getVersion(), 135 | $this->fetcher->getUpdateChannel() 136 | ) 137 | ); 138 | 139 | if ($input->getOption('only-check')) { 140 | return 0; 141 | } 142 | 143 | $action = $this->ask($input, $output); 144 | if ($action === 'abort') { 145 | $output->writeln('Exiting on user command.'); 146 | return 128; 147 | } 148 | 149 | $this->output = $output; 150 | $packageData = $downloadController->downloadOwncloud([$this, 'progress']); 151 | //Empty line, in order not to overwrite the progress message 152 | $this->output->writeln(''); 153 | if (!$packageData['success']) { 154 | $registry->set('feed', null); 155 | throw $packageData['exception']; 156 | } 157 | 158 | if ($action === 'download') { 159 | $output->writeln('Downloading has been completed. Exiting.'); 160 | return 64; 161 | } 162 | } catch (ClientException $e) { 163 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 164 | $this->getApplication()->getLogger()->error($e->getMessage()); 165 | $output->writeln('Network error'); 166 | $output->writeln( 167 | \sprintf( 168 | 'Error %d: %s while fetching an URL %s', 169 | $e->getCode(), 170 | $e->getResponse()->getReasonPhrase(), 171 | (string) $e->getRequest()->getUri() 172 | ) 173 | ); 174 | return 2; 175 | } catch (\Exception $e) { 176 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 177 | $this->getApplication()->getLogger()->error($e->getMessage()); 178 | $output->writeln(''.$e->getMessage().''); 179 | return 2; 180 | } 181 | 182 | return 0; 183 | } 184 | 185 | /** 186 | * Ask what to do 187 | * @param InputInterface $input 188 | * @param OutputInterface $output 189 | * @return string 190 | */ 191 | public function ask(InputInterface $input, OutputInterface $output) { 192 | $helper = $this->getHelper('question'); 193 | $question = new ChoiceQuestion( 194 | 'What would you do next?', 195 | ['download', 'upgrade', 'abort'], 196 | '1' 197 | ); 198 | $action = $helper->ask($input, $output, $question); 199 | 200 | return $action; 201 | } 202 | 203 | /** 204 | * Callback to output download progress 205 | * @param int $downloadTotal 206 | * @param int $downloadedBytes 207 | * @param int $uploadTotal 208 | * @param int $uploadedBytes 209 | */ 210 | public function progress($downloadTotal, $downloadedBytes, $uploadTotal, $uploadedBytes) { 211 | if ($downloadTotal > 0) { 212 | $percent = \intval(100 * $downloadedBytes / $downloadTotal); 213 | $this->output->write("Downloaded $percent% ($downloadedBytes of $downloadTotal)\r"); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/Command/ExecuteCoreUpgradeScriptsCommand.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | namespace Owncloud\Updater\Command; 24 | 25 | /* @phan-suppress-next-line PhanUnreferencedUseNormal */ 26 | use Owncloud\Updater\Utils\Checkpoint; 27 | /* @phan-suppress-next-line PhanUnreferencedUseNormal */ 28 | use Owncloud\Updater\Utils\FilesystemHelper; 29 | use Symfony\Component\Console\Input\InputInterface; 30 | use Symfony\Component\Console\Output\OutputInterface; 31 | use Symfony\Component\Process\Exception\ProcessFailedException; 32 | use Owncloud\Updater\Utils\OccRunner; 33 | use Owncloud\Updater\Utils\ZipExtractor; 34 | 35 | /** 36 | * Class ExecuteCoreUpgradeScriptsCommand 37 | * 38 | * @package Owncloud\Updater\Command 39 | */ 40 | class ExecuteCoreUpgradeScriptsCommand extends Command { 41 | /** 42 | * @var OccRunner $occRunner 43 | */ 44 | protected $occRunner; 45 | 46 | /** 47 | * ExecuteCoreUpgradeScriptsCommand constructor. 48 | * 49 | * @param null|string $occRunner 50 | */ 51 | public function __construct($occRunner) { 52 | parent::__construct(); 53 | $this->occRunner = $occRunner; 54 | } 55 | 56 | protected function configure() { 57 | $this 58 | ->setName('upgrade:executeCoreUpgradeScripts') 59 | ->setDescription('execute core upgrade scripts [danger, might take long]'); 60 | } 61 | 62 | /** 63 | * @param InputInterface $input 64 | * @param OutputInterface $output 65 | * @return int 66 | * @throws \Exception 67 | */ 68 | protected function execute(InputInterface $input, OutputInterface $output): int { 69 | $locator = $this->container['utils.locator']; 70 | /** @var FilesystemHelper $fsHelper */ 71 | $fsHelper = $this->container['utils.filesystemhelper']; 72 | $registry = $this->container['utils.registry']; 73 | $fetcher = $this->container['utils.fetcher']; 74 | /** @var Checkpoint $checkpoint */ 75 | $checkpoint = $this->container['utils.checkpoint']; 76 | 77 | $installedVersion = \implode('.', $locator->getInstalledVersion()); 78 | $registry->set('installedVersion', $installedVersion); 79 | 80 | /** @var \Owncloud\Updater\Utils\Feed $feed */ 81 | $feed = $registry->get('feed'); 82 | 83 | if ($feed) { 84 | $path = $fetcher->getBaseDownloadPath($feed); 85 | $fullExtractionPath = $locator->getExtractionBaseDir() . '/' . $feed->getVersion(); 86 | 87 | if (\file_exists($fullExtractionPath)) { 88 | $fsHelper->removeIfExists($fullExtractionPath); 89 | } 90 | try { 91 | $fsHelper->mkdir($fullExtractionPath, true); 92 | } catch (\Exception $e) { 93 | $output->writeln('Unable create directory ' . $fullExtractionPath); 94 | throw $e; 95 | } 96 | 97 | $output->writeln('Extracting source into ' . $fullExtractionPath); 98 | $extractor = new ZipExtractor($path, $fullExtractionPath, $output); 99 | 100 | try { 101 | $extractor->extract(); 102 | } catch (\Exception $e) { 103 | $output->writeln('Extraction has been failed'); 104 | $fsHelper->removeIfExists($locator->getExtractionBaseDir()); 105 | throw $e; 106 | } 107 | 108 | $tmpDir = $locator->getExtractionBaseDir() . '/' . $installedVersion; 109 | $fsHelper->removeIfExists($tmpDir); 110 | $fsHelper->mkdir($tmpDir); 111 | $oldSourcesDir = $locator->getOwncloudRootPath(); 112 | $newSourcesDir = $fullExtractionPath . '/owncloud'; 113 | 114 | $packageVersion = $this->loadVersion($newSourcesDir); 115 | $allowedPreviousVersions = $this->loadAllowedPreviousVersions($newSourcesDir); 116 | 117 | if (!$this->isUpgradeAllowed($installedVersion, $packageVersion, $allowedPreviousVersions)) { 118 | $message = \sprintf( 119 | 'Update from %s to %s is not possible. Updates between multiple major versions and downgrades are unsupported.', 120 | $installedVersion, 121 | $packageVersion 122 | ); 123 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 124 | $this->getApplication()->getLogger()->error($message); 125 | $output->writeln(''. $message .''); 126 | return 1; 127 | } 128 | 129 | $rootDirContent = \array_unique( 130 | \array_merge( 131 | $locator->getRootDirContent(), 132 | $locator->getRootDirItemsFromSignature($oldSourcesDir), 133 | $locator->getRootDirItemsFromSignature($newSourcesDir) 134 | ) 135 | ); 136 | foreach ($rootDirContent as $dir) { 137 | if ($dir === 'updater') { 138 | continue; 139 | } 140 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 141 | $this->getApplication()->getLogger()->debug('Replacing ' . $dir); 142 | $fsHelper->tripleMove($oldSourcesDir, $newSourcesDir, $tmpDir, $dir); 143 | } 144 | 145 | $fsHelper->copyr($tmpDir . '/config/config.php', $oldSourcesDir . '/config/config.php'); 146 | 147 | //Get a new shipped apps list 148 | $newAppsDir = $fullExtractionPath . '/owncloud/apps'; 149 | $newAppsList = $fsHelper->scandirFiltered($newAppsDir); 150 | 151 | //Remove old apps 152 | $appDirectories = $fsHelper->scandirFiltered($oldSourcesDir . '/apps'); 153 | $oldAppList = \array_intersect($appDirectories, $newAppsList); 154 | foreach ($oldAppList as $appDirectory) { 155 | $fsHelper->rmdirr($oldSourcesDir . '/apps/' . $appDirectory); 156 | } 157 | 158 | foreach ($newAppsList as $appId) { 159 | $output->writeln('Copying the application ' . $appId); 160 | $fsHelper->copyr($newAppsDir . '/' . $appId, $locator->getOwnCloudRootPath() . '/apps/' . $appId, false); 161 | } 162 | 163 | try { 164 | $plain = $this->occRunner->run('upgrade'); 165 | $output->writeln($plain); 166 | } catch (ProcessFailedException $e) { 167 | $lastCheckpointId = $checkpoint->getLastCheckpointId(); 168 | if ($lastCheckpointId) { 169 | $lastCheckpointPath = $checkpoint->getCheckpointPath($lastCheckpointId); 170 | $fsHelper->copyr($lastCheckpointPath . '/apps', $oldSourcesDir . '/apps', false); 171 | } 172 | if ($e->getProcess()->getExitCode() != 3) { 173 | throw ($e); 174 | } 175 | } 176 | } 177 | return 0; 178 | } 179 | 180 | public function isUpgradeAllowed($installedVersion, $packageVersion, $canBeUpgradedFrom) { 181 | if (\version_compare($installedVersion, $packageVersion, '<=')) { 182 | foreach ($canBeUpgradedFrom as $allowedPreviousVersion) { 183 | if (\version_compare($allowedPreviousVersion, $installedVersion, '<=')) { 184 | return true; 185 | } 186 | } 187 | } 188 | return false; 189 | } 190 | 191 | /** 192 | * @param string $pathToPackage 193 | * @return string 194 | */ 195 | protected function loadVersion($pathToPackage) { 196 | require $pathToPackage . '/version.php'; 197 | /** @var $OC_Version array */ 198 | return \implode('.', $OC_Version); /* @phan-suppress-current-line PhanUndeclaredVariable */ 199 | } 200 | 201 | /** 202 | * @param string $pathToPackage 203 | * @return string[] 204 | */ 205 | protected function loadAllowedPreviousVersions($pathToPackage) { 206 | $canBeUpgradedFrom = $this->loadCanBeUpgradedFrom($pathToPackage); 207 | 208 | $firstItem = \reset($canBeUpgradedFrom); 209 | if (!\is_array($firstItem)) { 210 | $canBeUpgradedFrom = [$canBeUpgradedFrom]; 211 | } 212 | 213 | $allowedVersions = []; 214 | foreach ($canBeUpgradedFrom as $version) { 215 | $allowedVersions[] = \implode('.', $version); 216 | } 217 | 218 | return $allowedVersions; 219 | } 220 | 221 | /** 222 | * @param string $pathToPackage 223 | * @return array 224 | */ 225 | protected function loadCanBeUpgradedFrom($pathToPackage) { 226 | require $pathToPackage . '/version.php'; 227 | /** @var array $OC_VersionCanBeUpgradedFrom */ 228 | return $OC_VersionCanBeUpgradedFrom; /* @phan-suppress-current-line PhanUndeclaredVariable */ 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/Command/InfoCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class InfoCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class InfoCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:info') 36 | ->setDescription( 37 | 'Your ownCloud is going to be upgraded' 38 | ) 39 | ; 40 | } 41 | 42 | /** 43 | * @param InputInterface $input 44 | * @param OutputInterface $output 45 | * @return int 46 | */ 47 | protected function execute(InputInterface $input, OutputInterface $output): int { 48 | $message = \sprintf( 49 | '%s %s - CLI based ownCloud server upgrades', 50 | $this->getApplication()->getName(), 51 | $this->getApplication()->getVersion() 52 | ); 53 | $output->writeln($message); 54 | return 0; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Command/MaintenanceModeCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Input\InputOption; 26 | use Symfony\Component\Console\Output\OutputInterface; 27 | use Owncloud\Updater\Utils\OccRunner; 28 | 29 | /** 30 | * Class MaintenanceModeCommand 31 | * 32 | * @package Owncloud\Updater\Command 33 | */ 34 | class MaintenanceModeCommand extends Command { 35 | /** 36 | * @var OccRunner $occRunner 37 | */ 38 | protected $occRunner; 39 | 40 | /** 41 | * Constructor 42 | * 43 | * @param OccRunner $occRunner 44 | */ 45 | public function __construct(OccRunner $occRunner) { 46 | parent::__construct(); 47 | $this->occRunner = $occRunner; 48 | } 49 | 50 | protected function configure() { 51 | $this 52 | ->setName('upgrade:maintenanceMode') 53 | ->setDescription('Toggle maintenance mode') 54 | ->addOption( 55 | 'on', 56 | null, 57 | InputOption::VALUE_NONE, 58 | 'enable maintenance mode' 59 | ) 60 | ->addOption( 61 | 'off', 62 | null, 63 | InputOption::VALUE_NONE, 64 | 'disable maintenance mode' 65 | ) 66 | ; 67 | } 68 | 69 | /** 70 | * @param InputInterface $input 71 | * @param OutputInterface $output 72 | * @return int 73 | */ 74 | protected function execute(InputInterface $input, OutputInterface $output): int { 75 | $args = []; 76 | if ($input->getOption('on')) { 77 | $args = ['--on' => '']; 78 | } elseif ($input->getOption('off')) { 79 | $args = ['--off' => '']; 80 | } 81 | 82 | $response = $this->occRunner->run('maintenance:mode', $args); 83 | $output->writeln($response); 84 | return 0; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Command/PostUpgradeCleanupCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class PostUpgradeCleanupCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class PostUpgradeCleanupCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:postUpgradeCleanup') 36 | ->setDescription('repair and cleanup step 2 (online) [danger, might take long]') 37 | ; 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output): int { 46 | $registry = $this->container['utils.registry']; 47 | $fsHelper = $this->container['utils.filesystemhelper']; 48 | $locator = $this->container['utils.locator']; 49 | 50 | //Update updater 51 | $feed = $registry->get('feed'); 52 | $fullExtractionPath = $locator->getExtractionBaseDir() . '/' . $feed->getVersion(); 53 | $tmpDir = $locator->getExtractionBaseDir() . '/' . $registry->get('installedVersion'); 54 | $oldSourcesDir = $locator->getOwncloudRootPath(); 55 | $newSourcesDir = $fullExtractionPath . '/owncloud'; 56 | $newUpdaterDir = $newSourcesDir . '/updater'; 57 | $oldUpdaterDir = $oldSourcesDir . '/updater'; 58 | $tmpUpdaterDir = $tmpDir . '/updater'; 59 | $fsHelper->mkdir($tmpUpdaterDir); 60 | 61 | foreach ($locator->getUpdaterContent() as $dir) { 62 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 63 | $this->getApplication()->getLogger()->debug('Moving updater/' . $dir); 64 | $fsHelper->tripleMove($oldUpdaterDir, $newUpdaterDir, $tmpUpdaterDir, $dir); 65 | } 66 | 67 | //Cleanup Filesystem 68 | $fsHelper->removeIfExists($locator->getExtractionBaseDir()); 69 | 70 | //Retrigger integrity check 71 | try { 72 | $this->container['utils.occrunner']->run('integrity:check-core'); 73 | } catch (\Exception $e) { 74 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 75 | $this->getApplication()->getLogger()->error('Integrity check failed'); 76 | /* @phan-suppress-next-line PhanUndeclaredMethod */ 77 | $this->getApplication()->logException($e); 78 | } 79 | 80 | //Cleanup updater cache 81 | $registry->clearAll(); 82 | 83 | $output->writeln('Done'); 84 | return 0; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Command/PostUpgradeRepairCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class PostUpgradeRepairCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class PostUpgradeRepairCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:postUpgradeRepair') 36 | ->setDescription('repair and cleanup step 1 (post upgrade, repair legacy storage, ..) [danger, might take long]') 37 | ; 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output): int { 46 | $output->writeln('upgrade:postUpgradeRepair command is not implemented'); 47 | return 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Command/PreUpgradeRepairCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class PreUpgradeRepairCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class PreUpgradeRepairCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:preUpgradeRepair') 36 | ->setDescription('Repair and cleanup (pre upgrade, DB collations update, ..) [danger, might take long]') 37 | ; 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output): int { 46 | $output->writeln('upgrade:preUpgradeRepair command is not implemented'); 47 | return 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Command/RestartWebServerCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class RestartWebServerCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class RestartWebServerCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:restartWebServer') 36 | ->setDescription('Please restart your Web Server') 37 | ; 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output): int { 46 | $output->writeln('upgrade:restartWebServer command is not implemented'); 47 | return 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Command/StartCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | use Symfony\Component\Console\Input\ArrayInput; 27 | 28 | /** 29 | * Class StartCommand 30 | * 31 | * @package Owncloud\Updater\Command 32 | */ 33 | class StartCommand extends Command { 34 | protected $stack = [ 35 | [ 'command' => 'upgrade:info'], 36 | [ 'command' => 'upgrade:checkSystem'], 37 | [ 'command' => 'upgrade:detect', '--exit-if-none' => '1'], 38 | //[ 'command' => 'upgrade:maintenanceMode', '--on' => '1'], 39 | [ 'command' => 'upgrade:backupDb'], 40 | [ 'command' => 'upgrade:backupData'], 41 | [ 'command' => 'upgrade:checkpoint', '--create' => '1'], 42 | [ 'command' => 'upgrade:preUpgradeRepair'], 43 | [ 'command' => 'upgrade:executeCoreUpgradeScripts'], 44 | [ 'command' => 'upgrade:cleanCache'], 45 | [ 'command' => 'upgrade:postUpgradeRepair'], 46 | [ 'command' => 'upgrade:restartWebServer'], 47 | [ 'command' => 'upgrade:updateConfig'], 48 | //[ 'command' => 'upgrade:maintenanceMode', '--off' => '1'], 49 | [ 'command' => 'upgrade:postUpgradeCleanup'], 50 | ]; 51 | 52 | protected function configure() { 53 | $this 54 | ->setName('upgrade:start') 55 | ->setDescription('automated process') 56 | ; 57 | } 58 | 59 | /** 60 | * @param InputInterface $input 61 | * @param OutputInterface $output 62 | * @return int 63 | * @throws \Throwable 64 | */ 65 | protected function execute(InputInterface $input, OutputInterface $output): int { 66 | $app = $this->getApplication(); 67 | foreach ($this->stack as $command) { 68 | $input = new ArrayInput($command); 69 | $returnCode = $app->doRun($input, $output); 70 | if ($returnCode != 0) { 71 | // Something went wrong 72 | break; 73 | } 74 | } 75 | $output->writeln('Done'); 76 | return 0; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Command/UpdateConfigCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Command; 23 | 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\Console\Output\OutputInterface; 26 | 27 | /** 28 | * Class UpdateConfigCommand 29 | * 30 | * @package Owncloud\Updater\Command 31 | */ 32 | class UpdateConfigCommand extends Command { 33 | protected function configure() { 34 | $this 35 | ->setName('upgrade:updateConfig') 36 | ->setDescription('update config.php') 37 | ; 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | */ 45 | protected function execute(InputInterface $input, OutputInterface $output): int { 46 | $output->writeln('upgrade:updateConfig command is not implemented'); 47 | return 0; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Console/Application.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | namespace Owncloud\Updater\Console; 24 | 25 | use Owncloud\Updater\Utils\DocLink; 26 | /* @phan-suppress-next-line PhanUnreferencedUseNormal */ 27 | use Owncloud\Updater\Utils\Locator; 28 | /* @phan-suppress-next-line PhanUnreferencedUseNormal */ 29 | use Owncloud\Updater\Utils\OccRunner; 30 | use Pimple\Container; 31 | use Symfony\Component\Console\Logger\ConsoleLogger; 32 | use Symfony\Component\Console\Output\ConsoleOutput; 33 | use Symfony\Component\Console\Output\StreamOutput; 34 | use Symfony\Component\Console\Input\InputInterface; 35 | use Symfony\Component\Console\Output\BufferedOutput; 36 | use Symfony\Component\Console\Output\OutputInterface; 37 | use Symfony\Component\Console\Command\Command; 38 | use Symfony\Component\Process\Exception\ProcessFailedException; 39 | 40 | /** 41 | * Class Application 42 | * 43 | * @package Owncloud\Updater\Console 44 | */ 45 | class Application extends \Symfony\Component\Console\Application { 46 | /** @var Container */ 47 | public static $container; 48 | 49 | /** @var Container */ 50 | protected $diContainer; 51 | 52 | /** @var ConsoleLogger */ 53 | protected $fallbackLogger; 54 | 55 | /** @var string */ 56 | protected $endpoint; 57 | 58 | /** @var string */ 59 | protected $authToken; 60 | 61 | /** @var array */ 62 | protected $allowFailure = [ 63 | 'upgrade:executeCoreUpgradeScripts', 64 | 'upgrade:checkpoint', 65 | 'upgrade:maintenanceMode', 66 | 'help', 67 | 'list' 68 | ]; 69 | 70 | /** 71 | * Pass Pimple container into application 72 | * @param Container $container 73 | */ 74 | public function setContainer(Container $container) { 75 | $this->diContainer = $container; 76 | self::$container = $container; 77 | } 78 | 79 | /** 80 | * Get Pimple container 81 | * @return Container 82 | */ 83 | public function getContainer() { 84 | return $this->diContainer; 85 | } 86 | 87 | /** 88 | * @param string $endpoint 89 | */ 90 | public function setEndpoint($endpoint) { 91 | $this->endpoint = $endpoint; 92 | } 93 | 94 | /** 95 | * @return string 96 | */ 97 | public function getEndpoint() { 98 | return $this->endpoint; 99 | } 100 | 101 | /** 102 | * @param $token 103 | */ 104 | public function setAuthToken($token) { 105 | $this->authToken = $token; 106 | } 107 | 108 | /** 109 | * @return string 110 | */ 111 | public function getAuthToken() { 112 | return $this->authToken; 113 | } 114 | 115 | /** 116 | * Get logger instance 117 | * @return \Psr\Log\LoggerInterface 118 | */ 119 | public function getLogger() { 120 | if (isset($this->diContainer['logger'])) { 121 | return $this->diContainer['logger']; 122 | } 123 | 124 | // Logger is not available yet, fallback to stdout 125 | if ($this->fallbackLogger === null) { 126 | $output = new ConsoleOutput(); 127 | $this->fallbackLogger = new ConsoleLogger($output); 128 | } 129 | 130 | return $this->fallbackLogger; 131 | } 132 | 133 | public function initConfig() { 134 | $configReader = $this->diContainer['utils.configReader']; 135 | try { 136 | $configReader->init(); 137 | } catch (\UnexpectedValueException $e) { 138 | // try fallback to localhost 139 | \preg_match_all('/https?:\/\/([^\/]*).*$/', $this->getEndpoint(), $matches); 140 | if (isset($matches[1][0])) { 141 | $newEndPoint = \str_replace($matches[1][0], 'localhost', $this->getEndpoint()); 142 | $this->setEndpoint($newEndPoint); 143 | try { 144 | $configReader->init(); 145 | } catch (\UnexpectedValueException $e) { 146 | // fallback to CLI 147 | $this->diContainer['utils.occrunner']->setCanUseProcess(true); 148 | $configReader->init(); 149 | } 150 | } 151 | } 152 | } 153 | 154 | /** 155 | * Log exception with trace 156 | * @param \Exception $e 157 | */ 158 | public function logException($e) { 159 | $buffer = new BufferedOutput(OutputInterface::VERBOSITY_VERBOSE); 160 | $this->renderException($e, $buffer); 161 | $this->getLogger()->error($buffer->fetch()); 162 | } 163 | 164 | /** 165 | * Runs the current application. 166 | * 167 | * @param InputInterface $input An Input instance 168 | * @param OutputInterface $output An Output instance 169 | * @return int 0 if everything went fine, or an error code 170 | * @throws \Exception 171 | */ 172 | public function doRun(InputInterface $input, OutputInterface $output) { 173 | $commandName = $this->getCommandName($input); 174 | try { 175 | $this->assertOwnCloudFound(); 176 | try { 177 | $this->initConfig(); 178 | if (!isset($this->diContainer['utils.docLink'])) { 179 | $this->diContainer['utils.docLink'] = function ($c) { 180 | /** @var Locator $locator */ 181 | $locator = $c['utils.locator']; 182 | $installedVersion = \implode('.', $locator->getInstalledVersion()); 183 | return new DocLink($installedVersion); 184 | }; 185 | } 186 | } catch (ProcessFailedException $e) { 187 | if (!\in_array($commandName, $this->allowFailure)) { 188 | $this->logException($e); 189 | $output->writeln("Initialization failed with message:"); 190 | $output->writeln($e->getProcess()->getOutput()); 191 | $output->writeln('Use upgrade:checkpoint --list to view a list of checkpoints'); 192 | $output->writeln('upgrade:checkpoint --restore [checkpointid] to revert to the last checkpoint'); 193 | $output->writeln('Please attach your update.log to the issues you reporting.'); 194 | return 1; 195 | } 196 | } 197 | // TODO: check if the current command needs a valid OC instance 198 | if (!isset($this->diContainer['logger'])) { 199 | $locator = $this->diContainer['utils.locator']; 200 | $this->initLogger($locator->getDataDir()); 201 | } 202 | } catch (\Exception $e) { 203 | if (!\in_array($commandName, $this->allowFailure)) { 204 | $this->logException($e); 205 | throw $e; 206 | } 207 | } 208 | return parent::doRun($input, $output); 209 | } 210 | 211 | /** 212 | * @param Command $command 213 | * @param InputInterface $input 214 | * @param OutputInterface $output 215 | * @return int 216 | * @throws \Exception 217 | */ 218 | protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { 219 | if ($command instanceof \Owncloud\Updater\Command\Command) { 220 | $command->setContainer($this->getContainer()); 221 | $commandName = $this->getCommandName($input); 222 | $this->getLogger()->info('Execution of ' . $commandName . ' command started'); 223 | $message = $command->getMessage(); 224 | if (!empty($message)) { 225 | $message = \sprintf('%s', $message); 226 | $output->writeln($message); 227 | } 228 | $exitCode = parent::doRunCommand($command, $input, $output); 229 | $this->getLogger()->info( 230 | 'Execution of ' . $commandName . ' command stopped. Exit code is ' . $exitCode 231 | ); 232 | } else { 233 | $exitCode = parent::doRunCommand($command, $input, $output); 234 | } 235 | return $exitCode; 236 | } 237 | 238 | /** 239 | * @param string $baseDir 240 | */ 241 | protected function initLogger($baseDir) { 242 | $container = $this->getContainer(); 243 | $container['logger.output'] = function ($c) use ($baseDir) { 244 | $stream = @\fopen($baseDir . '/update.log', 'a+'); 245 | if ($stream === false) { 246 | $stream = @\fopen('php://stderr', 'a'); 247 | } 248 | return new StreamOutput($stream, StreamOutput::VERBOSITY_DEBUG, false); 249 | }; 250 | $container['logger'] = function ($c) { 251 | return new ConsoleLogger($c['logger.output']); 252 | }; 253 | } 254 | 255 | /** 256 | * Check for ownCloud instance 257 | * @throws \RuntimeException 258 | */ 259 | public function assertOwnCloudFound() { 260 | $container = $this->getContainer(); 261 | /** @var Locator $locator */ 262 | $locator = $container['utils.locator']; 263 | $fsHelper = $container['utils.filesystemhelper']; 264 | /** @var OccRunner $occRunner */ 265 | $occRunner = $container['utils.occrunner']; 266 | 267 | // has to be installed 268 | $file = $locator->getPathToConfigFile(); 269 | $this->assertFileExists($file, 'ownCloud in ' . \dirname(\dirname($file)) . ' is not installed.'); 270 | 271 | // version.php should exist 272 | $file = $locator->getPathToVersionFile(); 273 | $this->assertFileExists($file, 'ownCloud is not found in ' . \dirname($file)); 274 | 275 | $status = $occRunner->runJson('status'); 276 | if (!isset($status['installed']) || $status['installed'] != 'true') { 277 | throw new \RuntimeException('ownCloud in ' . \dirname($file) . ' is not installed.'); 278 | } 279 | 280 | // datadir should exist 281 | $dataDir = $locator->getDataDir(); 282 | if (!$fsHelper->fileExists($dataDir)) { 283 | throw new \RuntimeException('Datadirectory ' . $dataDir . ' does not exist.'); 284 | } 285 | 286 | // datadir should be writable 287 | if (!$fsHelper->isWritable($dataDir)) { 288 | throw new \RuntimeException('Datadirectory ' . $dataDir . ' is not writable.'); 289 | } 290 | 291 | // assert minimum version 292 | $installedVersion = \implode('.', $locator->getInstalledVersion()); 293 | if (\version_compare($installedVersion, '9.0.0', '<')) { 294 | throw new \RuntimeException("Minimum ownCloud version 9.0.0 is required for the updater - $installedVersion was found in " . $locator->getOwnCloudRootPath()); 295 | } 296 | 297 | if (!$fsHelper->fileExists($locator->getUpdaterBaseDir())) { 298 | $fsHelper->mkdir($locator->getUpdaterBaseDir()); 299 | } 300 | 301 | if (!$fsHelper->fileExists($locator->getDownloadBaseDir())) { 302 | $fsHelper->mkdir($locator->getDownloadBaseDir()); 303 | } 304 | if (!$fsHelper->fileExists($locator->getCheckpointDir())) { 305 | $fsHelper->mkdir($locator->getCheckpointDir()); 306 | } 307 | } 308 | 309 | /** 310 | * @param string $path 311 | * @param string $message 312 | */ 313 | protected function assertFileExists($path, $message) { 314 | if (!\file_exists($path) || !\is_file($path)) { 315 | throw new \RuntimeException($message); 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/Controller/DownloadController.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Controller; 23 | 24 | use Owncloud\Updater\Utils\Fetcher; 25 | use Owncloud\Updater\Utils\Registry; 26 | use Owncloud\Updater\Utils\FilesystemHelper; 27 | 28 | /** 29 | * Class DownloadController 30 | * 31 | * @package Owncloud\Updater\Controller 32 | */ 33 | class DownloadController { 34 | /** 35 | * @var Fetcher 36 | */ 37 | protected $fetcher; 38 | 39 | /** 40 | * @var Registry 41 | */ 42 | protected $registry; 43 | 44 | /** 45 | * @var FilesystemHelper 46 | */ 47 | protected $fsHelper; 48 | 49 | /** 50 | * DownloadController constructor. 51 | * 52 | * @param Fetcher $fetcher 53 | * @param Registry $registry 54 | * @param FilesystemHelper $fsHelper 55 | */ 56 | public function __construct(Fetcher $fetcher, Registry $registry, FilesystemHelper $fsHelper) { 57 | $this->fetcher = $fetcher; 58 | $this->registry = $registry; 59 | $this->fsHelper = $fsHelper; 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function checkFeed() { 66 | $response = $this->getDefaultResponse(); 67 | try { 68 | $feed = $this->fetcher->getFeed(); 69 | $response['success'] = true; 70 | $response['data']['feed'] = $feed; 71 | } catch (\Exception $e) { 72 | $response['exception'] = $e; 73 | } 74 | 75 | return $response; 76 | } 77 | 78 | /** 79 | * @param null $progressCallback 80 | * @return array 81 | */ 82 | public function downloadOwncloud($progressCallback = null) { 83 | $response = $this->getDefaultResponse(); 84 | if ($progressCallback === null) { 85 | $progressCallback = function () { 86 | }; 87 | } 88 | try { 89 | $feed = $this->getFeed(); 90 | $path = $this->fetcher->getBaseDownloadPath($feed); 91 | // Fixme: Daily channel has no checksum 92 | $isDailyChannel = $this->fetcher->getUpdateChannel() == 'daily'; 93 | if (!$isDailyChannel) { 94 | $md5 = $this->fetcher->getMd5($feed); 95 | } else { 96 | // We can't check md5 so we don't trust the cache 97 | $this->fsHelper->removeIfExists($path); 98 | } 99 | /* @phan-suppress-next-line PhanPossiblyUndeclaredVariable */ 100 | if ($isDailyChannel || !$this->checkIntegrity($path, $md5)) { 101 | $this->fetcher->getOwncloud($feed, $progressCallback); 102 | } 103 | 104 | /* @phan-suppress-next-line PhanPossiblyUndeclaredVariable */ 105 | if ($isDailyChannel || $this->checkIntegrity($path, $md5)) { 106 | $response['success'] = true; 107 | $response['data']['path'] = $path; 108 | } else { 109 | $response['exception'] = new \Exception('Deleted ' . $feed->getDownloadedFileName() . ' due to wrong checksum'); 110 | } 111 | } catch (\Exception $e) { 112 | if (isset($path)) { 113 | $this->fsHelper->removeIfExists($path); 114 | } 115 | $response['exception'] = $e; 116 | } 117 | return $response; 118 | } 119 | 120 | /** 121 | * Check if package is not corrupted on download 122 | * @param string $path 123 | * @param string $md5 124 | * @return boolean 125 | */ 126 | protected function checkIntegrity($path, $md5) { 127 | $fileExists = $this->fsHelper->fileExists($path); 128 | $checksumMatch = $fileExists && $md5 === $this->fsHelper->md5File($path); 129 | if (!$checksumMatch) { 130 | $this->fsHelper->removeIfExists($path); 131 | } 132 | return $checksumMatch; 133 | } 134 | 135 | /** 136 | * Get a Feed instance 137 | * @param bool $useCache 138 | * @return \Owncloud\Updater\Utils\Feed 139 | */ 140 | protected function getFeed($useCache = true) { 141 | if ($useCache && $this->registry->get('feed') !== null) { 142 | return $this->registry->get('feed'); 143 | } 144 | return $this->fetcher->getFeed(); 145 | } 146 | 147 | /** 148 | * Init response array 149 | * @return array 150 | */ 151 | protected function getDefaultResponse() { 152 | return [ 153 | 'success' => false, 154 | 'exception' => '', 155 | 'details' => '', 156 | 'data' => [] 157 | ]; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Controller/IndexController.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | namespace Owncloud\Updater\Controller; 24 | 25 | /* @phan-suppress-next-line PhanUnreferencedUseNormal */ 26 | use Owncloud\Updater\Utils\Checkpoint; 27 | /* @phan-suppress-next-line PhanUnreferencedUseNormal */ 28 | use Owncloud\Updater\Utils\ConfigReader; 29 | use Pimple\Container; 30 | use Owncloud\Updater\Formatter\HtmlOutputFormatter; 31 | use Owncloud\Updater\Http\Request; 32 | use League\Plates\Engine; 33 | use League\Plates\Extension\Asset; 34 | use League\Plates\Extension\URI; 35 | use Symfony\Component\Console\Output\BufferedOutput; 36 | use Symfony\Component\Console\Input\StringInput; 37 | 38 | /** 39 | * Class IndexController 40 | * 41 | * @package Owncloud\Updater\Controller 42 | */ 43 | class IndexController { 44 | /** @var Container */ 45 | protected $container; 46 | 47 | /** @var Request */ 48 | protected $request; 49 | 50 | /** @var string $command */ 51 | protected $command; 52 | 53 | /** 54 | * @param Container $container 55 | * @param Request|null $request 56 | */ 57 | public function __construct( 58 | Container $container, 59 | Request $request = null 60 | ) { 61 | $this->container = $container; 62 | if ($request === null) { 63 | $this->request = new Request(['post' => $_POST, 'headers' => $_SERVER]); 64 | } else { 65 | $this->request = $request; 66 | } 67 | 68 | $this->command = $this->request->postParameter('command'); 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function dispatch() { 75 | /** @var ConfigReader $configReader */ 76 | $configReader = $this->container['utils.configReader']; 77 | 78 | // strip index.php and query string (if any) to get a real base url 79 | $baseUrl = \preg_replace('/(index\.php.*|\?.*)$/', '', $_SERVER['REQUEST_URI']); 80 | $templates = new Engine(CURRENT_DIR . '/src/Resources/views/'); 81 | $templates->loadExtension(new Asset(CURRENT_DIR . '/pub/', false)); 82 | $templates->loadExtension(new URI($baseUrl)); 83 | 84 | // Check if the user is logged-in 85 | if (!$this->isLoggedIn()) { 86 | return $this->showLogin($templates); 87 | } 88 | 89 | try { 90 | $fullEndpoint = $this->getEndpoint(); 91 | $this->container['application']->setEndpoint($fullEndpoint); 92 | $this->container['application']->setAuthToken($this->request->header('X_Updater_Auth')); 93 | $this->container['application']->initConfig(); 94 | $this->container['application']->assertOwnCloudFound(); 95 | } catch (\Exception $e) { 96 | $content = $templates->render( 97 | 'partials/error', 98 | [ 99 | 'title' => 'Updater', 100 | 'version' => $this->container['application']->getVersion(), 101 | 'error' => $e->getMessage() 102 | ] 103 | ); 104 | return $content; 105 | } 106 | 107 | if ($this->command === null) { 108 | /** @var Checkpoint $checkpoint */ 109 | $checkpoint = $this->container['utils.checkpoint']; 110 | $checkpoints = $checkpoint->getAll(); 111 | $content = $templates->render( 112 | 'partials/inner', 113 | [ 114 | 'title' => 'Updater', 115 | 'version' => $this->container['application']->getVersion(), 116 | 'checkpoints' => $checkpoints 117 | ] 118 | ); 119 | } else { 120 | \header('Content-Type: application/json'); 121 | $content = \json_encode($this->ajaxAction(), JSON_UNESCAPED_SLASHES); 122 | } 123 | return $content; 124 | } 125 | 126 | /** 127 | * @return bool 128 | */ 129 | protected function isLoggedIn() { 130 | /** @var ConfigReader $configReader */ 131 | $locator = $this->container['utils.locator']; 132 | $storedSecret = $locator->getSecretFromConfig(); 133 | if ($storedSecret === '') { 134 | die('updater.secret is undefined in config/config.php. Either browse the admin settings in your ownCloud and click "Open updater" or define a strong secret using
php -r \'echo password_hash("MyStrongSecretDoUseYourOwn!", PASSWORD_DEFAULT)."\n";\'
and set this in the config.php.'); 135 | } 136 | $sentAuthHeader = ($this->request->header('X_Updater_Auth') !== null) ? $this->request->header('X_Updater_Auth') : ''; 137 | 138 | if (\password_verify($sentAuthHeader, $storedSecret)) { 139 | return true; 140 | } 141 | 142 | return false; 143 | } 144 | 145 | /** 146 | * @param Engine $templates 147 | * @return string 148 | */ 149 | public function showLogin(Engine $templates) { 150 | // If it is a request with invalid token just return "false" so that we can catch this 151 | $token = ($this->request->header('X_Updater_Auth') !== null) ? $this->request->header('X_Updater_Auth') : ''; 152 | if ($token !== '') { 153 | return 'false'; 154 | } 155 | 156 | $content = $templates->render( 157 | 'partials/login', 158 | [ 159 | 'title' => 'Login Required', 160 | ] 161 | ); 162 | return $content; 163 | } 164 | 165 | /** 166 | * @return array 167 | */ 168 | public function ajaxAction() { 169 | $application = $this->container['application']; 170 | 171 | $input = new StringInput($this->command); 172 | $input->setInteractive(false); 173 | 174 | $output = new BufferedOutput(); 175 | $formatter = $output->getFormatter(); 176 | $formatter->setDecorated(true); 177 | $output->setFormatter(new HtmlOutputFormatter($formatter)); 178 | 179 | $application->setAutoExit(false); 180 | 181 | // Some commands dump things out instead of returning a value 182 | \ob_start(); 183 | $errorCode = $application->run($input, $output); 184 | if (!$result = $output->fetch()) { 185 | $result = \ob_get_contents(); // If empty, replace it by the catched output 186 | } 187 | \ob_end_clean(); 188 | $result = \nl2br($result); 189 | $result = \preg_replace('|
\r.*
(\r.*?)
|', '$1
', $result); 190 | 191 | return [ 192 | 'input' => $this->command, 193 | 'output' => $result, 194 | 'environment' => '', 195 | 'error_code' => $errorCode 196 | ]; 197 | } 198 | 199 | protected function getEndpoint() { 200 | $endpoint = \preg_replace('/(updater\/|updater\/index.php)$/', '', $this->request->getRequestUri()); 201 | $fullEndpoint = \sprintf( 202 | '%s://%s%sindex.php/occ/', 203 | $this->request->getServerProtocol(), 204 | $this->request->getHost(), 205 | $endpoint !== '' ? $endpoint : '/' 206 | ); 207 | 208 | return $fullEndpoint; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/Formatter/HtmlOutputFormatter.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | namespace Owncloud\Updater\Formatter; 24 | 25 | use Symfony\Component\Console\Formatter\OutputFormatterInterface; 26 | use Symfony\Component\Console\Formatter\OutputFormatterStyleInterface; 27 | 28 | /** 29 | * Class HtmlOutputFormatter 30 | * 31 | * @package Owncloud\Updater\Formatter 32 | */ 33 | class HtmlOutputFormatter implements OutputFormatterInterface { 34 | public const PATTERN = "/\[(([\d+];?)*)m(.*?)\[(([\d+];?)*)m/i"; 35 | 36 | private static $styles = [ 37 | '30' => 'color:rgba(0,0,0,1)', 38 | '31' => 'color:rgba(230,50,50,1)', 39 | '32' => 'color:rgba(50,230,50,1)', 40 | '33' => 'color:rgba(230,230,50,1)', 41 | '34' => 'color:rgba(50,50,230,1)', 42 | '35' => 'color:rgba(230,50,150,1)', 43 | '36' => 'color:rgba(50,230,230,1)', 44 | '37' => 'color:rgba(250,250,250,1)', 45 | '40' => 'color:rgba(0,0,0,1)', 46 | '41' => 'background-color:rgba(230,50,50,1)', 47 | '42' => 'background-color:rgba(50,230,50,1)', 48 | '43' => 'background-color:rgba(230,230,50,1)', 49 | '44' => 'background-color:rgba(50,50,230,1)', 50 | '45' => 'background-color:rgba(230,50,150,1)', 51 | '46' => 'background-color:rgba(50,230,230,1)', 52 | '47' => 'background-color:rgba(250,250,250,1)', 53 | '1' => 'font-weight:bold', 54 | '4' => 'text-decoration:underline', 55 | '8' => 'visibility:hidden', 56 | ]; 57 | private $formatter; 58 | 59 | /** 60 | * HtmlOutputFormatter constructor. 61 | * 62 | * @param $formatter 63 | */ 64 | public function __construct($formatter) { 65 | $this->formatter = $formatter; 66 | } 67 | 68 | /** 69 | * @param bool $decorated 70 | * @return mixed 71 | */ 72 | public function setDecorated($decorated) { 73 | return $this->formatter->setDecorated($decorated); 74 | } 75 | 76 | /** 77 | * @return mixed 78 | */ 79 | public function isDecorated() { 80 | return $this->formatter->isDecorated(); 81 | } 82 | 83 | /** 84 | * @param string $name 85 | * @param OutputFormatterStyleInterface $style 86 | * @return mixed 87 | */ 88 | public function setStyle($name, OutputFormatterStyleInterface $style) { 89 | return $this->formatter->setStyle($name, $style); 90 | } 91 | 92 | /** 93 | * @param string $name 94 | * @return mixed 95 | */ 96 | public function hasStyle($name) { 97 | return $this->formatter->hasStyle($name); 98 | } 99 | 100 | /** 101 | * @param string $name 102 | * @return mixed 103 | */ 104 | public function getStyle($name) { 105 | return $this->formatter->getStyle($name); 106 | } 107 | 108 | /** 109 | * @param string $message 110 | * @return mixed 111 | */ 112 | public function format($message) { 113 | $formatted = $this->formatter->format($message); 114 | $escaped = \htmlspecialchars($formatted, ENT_QUOTES, 'UTF-8'); 115 | $converted = \preg_replace_callback(self::PATTERN, [$this, 'replaceFormat'], $escaped); 116 | 117 | return $converted; 118 | } 119 | 120 | /** 121 | * @param $matches 122 | * @return string 123 | */ 124 | protected function replaceFormat($matches) { 125 | $text = $matches[3]; 126 | $styles = \explode(';', $matches[1]); 127 | $css = []; 128 | 129 | foreach ($styles as $style) { 130 | if (isset(self::$styles[$style])) { 131 | /* @phan-suppress-next-line PhanTypeMismatchDimFetch */ 132 | $css[] = self::$styles[$style]; 133 | } 134 | } 135 | 136 | return \sprintf('%s', \implode(';', $css), $text); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Http/Request.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | namespace Owncloud\Updater\Http; 24 | 25 | /** 26 | * Class Request 27 | * 28 | * @package Owncloud\Updater\Http 29 | */ 30 | class Request { 31 | protected $vars; 32 | 33 | /** 34 | * Request constructor. 35 | * 36 | * @param array $vars 37 | */ 38 | public function __construct($vars = []) { 39 | $this->vars = $vars; 40 | } 41 | 42 | /** 43 | * Returns the request uri 44 | * @return string 45 | */ 46 | public function getRequestUri() { 47 | return $this->server('REQUEST_URI'); 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getServerProtocol() { 54 | $forwardedProto = $this->server('HTTP_X_FORWARDED_PROTO'); 55 | if ($forwardedProto !== null) { 56 | $proto = \strtolower($this->getPartBeforeComma($forwardedProto)); 57 | // Verify that the protocol is always HTTP or HTTPS 58 | // default to http if an invalid value is provided 59 | return $proto === 'https' ? 'https' : 'http'; 60 | } 61 | 62 | $isHttps = $this->server('HTTPS'); 63 | if ($isHttps !== null 64 | && $isHttps !== 'off' 65 | && $isHttps !== '' 66 | ) { 67 | return 'https'; 68 | } 69 | 70 | return 'http'; 71 | } 72 | 73 | /** 74 | * @return mixed|string 75 | */ 76 | public function getHost() { 77 | $host = 'localhost'; 78 | $forwardedHost = $this->server('HTTP_X_FORWARDED_HOST'); 79 | if ($forwardedHost !== null) { 80 | $host = $this->getPartBeforeComma($forwardedHost); 81 | } else { 82 | $httpHost = $this->server('HTTP_HOST'); 83 | if ($httpHost === null) { 84 | $serverName = $this->server('SERVER_NAME'); 85 | if ($serverName !== null) { 86 | $host = $serverName; 87 | } 88 | } else { 89 | $host = $httpHost; 90 | } 91 | } 92 | return $host; 93 | } 94 | 95 | /** 96 | * @param string $name 97 | * @return mixed 98 | */ 99 | public function postParameter($name) { 100 | return isset($this->vars['post'][$name]) ? $this->vars['post'][$name] : null; 101 | } 102 | 103 | /** 104 | * @param string $name 105 | * @return mixed 106 | */ 107 | public function header($name) { 108 | $name = \strtoupper($name); 109 | return $this->server('HTTP_'.$name); 110 | } 111 | 112 | /** 113 | * @param string $name 114 | * @return mixed 115 | */ 116 | public function server($name) { 117 | return isset($this->vars['headers'][$name]) ? $this->vars['headers'][$name] : null; 118 | } 119 | 120 | /** 121 | * Return first part before comma or the string itself if there is no comma 122 | * @param string $str 123 | * @return string 124 | */ 125 | private function getPartBeforeComma($str) { 126 | if (\strpos($str, ',') !== false) { 127 | $parts = \explode(',', $str); 128 | $result = $parts[0]; 129 | } else { 130 | $result = $str; 131 | } 132 | return \trim($result); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Resources/views/base.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ownCloud Updater - <?=$this->e($title)?> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | section('login')?> 24 | section('inner')?> 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Resources/views/partials/error.php: -------------------------------------------------------------------------------- 1 | e($error) ?> 2 | 3 | -------------------------------------------------------------------------------- /src/Resources/views/partials/inner.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
18 |
19 |
20 | 21 |
22 | 26 |
27 |
28 | 29 | 30 | 31 |
    32 |
  • 33 |

    Initializing

    34 | 35 |
  • 36 |
  • 37 |

    Checking system

    38 | 39 |
  • 40 |
  • 41 |

    Creating a checkpoint

    42 | 43 |
  • 44 |
  • 45 |

    Downloading

    46 | 47 |
  • 48 |
  • 49 |

    Updating core

    50 | 51 |
  • 52 |
  • 53 |

    Updating apps

    54 | 55 |
  • 56 |
  • 57 |

    Finishing the update

    58 | 59 |
  • 60 |
  • 61 |

    Done

    62 | 63 |
  • 64 |
65 | 66 |
67 |

This app will only backup core files (no personal data).

68 |

Please always do a separate backup of database and personal data before updating.

69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
BackupDone on 
e($checkpoint['title']) ?>e($checkpoint['date']) ?>
93 | 94 |
95 |
96 |
97 |
98 | -------------------------------------------------------------------------------- /src/Resources/views/partials/login.php: -------------------------------------------------------------------------------- 1 | 2 | layout('base', ['title' => $title, 'bodyId' => 'body-login']) ?> 3 | start('login') ?> 4 | 5 |
6 |
7 |
8 | 14 |
15 | 16 |
17 |

Please provide the unhashed "updater.secret" from your ownCloud's config/config.php:

18 |
19 |
20 | 23 | 24 | 25 | 26 |
27 |

Invalid password

28 |
29 | 30 |
31 |
32 |
33 |
34 |

35 | ownCloud – web services under your control 36 |

37 |
38 | stop() ?> 39 | -------------------------------------------------------------------------------- /src/Utils/AppManager.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | /** 25 | * Class AppManager 26 | * 27 | * @package Owncloud\Updater\Utils 28 | */ 29 | class AppManager { 30 | /** 31 | * @var OccRunner $occRunner 32 | */ 33 | protected $occRunner; 34 | 35 | /** 36 | * @var array $disabledApps 37 | */ 38 | protected $disabledApps = []; 39 | 40 | /** 41 | * 42 | * @param OccRunner $occRunner 43 | */ 44 | public function __construct(OccRunner $occRunner) { 45 | $this->occRunner = $occRunner; 46 | } 47 | 48 | /** 49 | * @param $appId 50 | * @return bool 51 | */ 52 | public function disableApp($appId) { 53 | try { 54 | $this->occRunner->run('app:disable', ['app-id' => $appId]); 55 | } catch (\Exception $e) { 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | /** 62 | * @param $appId 63 | * @return bool 64 | */ 65 | public function enableApp($appId) { 66 | try { 67 | $this->occRunner->run('app:enable', ['app-id' => $appId]); 68 | \array_unshift($this->disabledApps, $appId); 69 | } catch (\Exception $e) { 70 | return false; 71 | } 72 | return true; 73 | } 74 | 75 | /** 76 | * @return array 77 | */ 78 | public function getAllApps() { 79 | $shippedApps = $this->occRunner->runJson('app:list'); 80 | $allApps = \array_merge(\array_keys($shippedApps['enabled']), \array_keys($shippedApps['disabled'])); 81 | return $allApps; 82 | } 83 | 84 | /** 85 | * @return array 86 | */ 87 | public function getNotShippedApps() { 88 | $shippedApps = $this->occRunner->runJson('app:list', ['--shipped' => 'false']); 89 | $allApps = \array_merge(\array_keys($shippedApps['enabled']), \array_keys($shippedApps['disabled'])); 90 | return $allApps; 91 | } 92 | 93 | /** 94 | * @return array 95 | */ 96 | public function getShippedApps() { 97 | $shippedApps = $this->occRunner->runJson('app:list', ['--shipped' => 'true']); 98 | $allApps = \array_merge(\array_keys($shippedApps['enabled']), \array_keys($shippedApps['disabled'])); 99 | return $allApps; 100 | } 101 | 102 | /** 103 | * @param $appId 104 | * @return string 105 | */ 106 | public function getAppPath($appId) { 107 | try { 108 | $response = $this->occRunner->run('app:getpath', ['app' => $appId]); 109 | } catch (\Exception $e) { 110 | return ''; 111 | } 112 | return \trim($response); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Utils/Checkpoint.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | use Owncloud\Updater\Console\Application; 25 | 26 | /** 27 | * Class Checkpoint 28 | * 29 | * @package Owncloud\Updater\Utils 30 | */ 31 | class Checkpoint { 32 | public const CORE_DIR = 'core'; 33 | public const APP_DIR = 'apps'; 34 | 35 | /** 36 | * @var Locator $locator 37 | */ 38 | protected $locator; 39 | 40 | /** 41 | * @var Filesystemhelper $fsHelper 42 | */ 43 | protected $fsHelper; 44 | 45 | /** 46 | * 47 | * @param Locator $locator 48 | * @param FilesystemHelper $fsHelper 49 | */ 50 | public function __construct(Locator $locator, FilesystemHelper $fsHelper) { 51 | $this->locator = $locator; 52 | $this->fsHelper = $fsHelper; 53 | } 54 | 55 | /** 56 | * Creates a checkpoint 57 | * @return string 58 | * @throws \Exception if base checkpoint directory is not writable 59 | */ 60 | public function create() { 61 | $checkpointId = $this->createCheckpointId(); 62 | $checkpointPath = $this->getCheckpointPath($checkpointId); 63 | try { 64 | if (!$this->fsHelper->isWritable($this->locator->getCheckpointDir())) { 65 | throw new \Exception($this->locator->getCheckpointDir() . ' is not writable.'); 66 | } 67 | $this->fsHelper->mkdir($checkpointPath); 68 | 69 | $checkpointCorePath = $checkpointPath . '/' . self::CORE_DIR; 70 | $this->fsHelper->mkdir($checkpointCorePath); 71 | $core = $this->locator->getRootDirItems(); 72 | foreach ($core as $coreItem) { 73 | $cpItemPath = $checkpointCorePath . '/' . \basename($coreItem); 74 | $this->fsHelper->copyr($coreItem, $cpItemPath, true); 75 | } 76 | //copy config.php 77 | $configDirSrc = $this->locator->getOwnCloudRootPath() . '/config'; 78 | $configDirDst = $checkpointCorePath . '/config'; 79 | $this->fsHelper->copyr($configDirSrc, $configDirDst, true); 80 | 81 | $checkpointAppPath = $checkpointPath . '/' . self::APP_DIR; 82 | $this->fsHelper->mkdir($checkpointAppPath); 83 | $appManager = Application::$container['utils.appmanager']; 84 | $apps = $appManager->getAllApps(); 85 | foreach ($apps as $appId) { 86 | $appPath = $appManager->getAppPath($appId); 87 | if ($appPath) { 88 | $this->fsHelper->copyr($appPath, $checkpointAppPath . '/' . $appId, true); 89 | } 90 | } 91 | } catch (\Exception $e) { 92 | $application = Application::$container['application']; 93 | $application->getLogger()->error($e->getMessage()); 94 | $this->fsHelper->removeIfExists($checkpointPath); 95 | throw $e; 96 | } 97 | return $checkpointId; 98 | } 99 | 100 | /** 101 | * Restore a checkpoint by id 102 | * @param string $checkpointId id of checkpoint 103 | * @throws \UnexpectedValueException if there is no checkpoint with this id 104 | */ 105 | public function restore($checkpointId) { 106 | $this->assertCheckpointExists($checkpointId); 107 | $checkpointDir = $this->locator->getCheckpointDir() . '/' . $checkpointId; 108 | $ocRoot = $this->locator->getOwnCloudRootPath(); 109 | $this->fsHelper->copyr($checkpointDir . '/' . self::CORE_DIR, $ocRoot, false); 110 | $this->fsHelper->copyr($checkpointDir . '/' . self::APP_DIR, $ocRoot . '/' . self::APP_DIR, false); 111 | } 112 | 113 | /** 114 | * Remove a checkpoint by id 115 | * @param string $checkpointId id of checkpoint 116 | * @throws \UnexpectedValueException if there is no checkpoint with this id 117 | */ 118 | public function remove($checkpointId) { 119 | $this->assertCheckpointExists($checkpointId); 120 | $checkpointPath = $this->getCheckpointPath($checkpointId); 121 | $this->fsHelper->removeIfExists($checkpointPath); 122 | } 123 | 124 | /** 125 | * Return all checkpoints as an array of items [ 'title', 'date' ] 126 | * @return array 127 | */ 128 | public function getAll() { 129 | $checkpoints = []; 130 | foreach ($this->getAllCheckpointIds() as $dir) { 131 | $checkpoints[] = [ 132 | 'title' => $dir, 133 | 'date' => \date( 134 | "F d Y H:i", 135 | $this->fsHelper->filemtime( 136 | $this->locator->getCheckpointDir() . '/' . $dir 137 | ) 138 | ) 139 | ]; 140 | } 141 | return $checkpoints; 142 | } 143 | 144 | /** 145 | * Check if there is a checkpoint with a given id 146 | * @param string $checkpointId id of checkpoint 147 | * @return bool 148 | */ 149 | public function checkpointExists($checkpointId) { 150 | return \in_array($checkpointId, $this->getAllCheckpointIds()); 151 | } 152 | 153 | /** 154 | * Get the most recent checkpoint Id 155 | * @return string|bool 156 | */ 157 | public function getLastCheckpointId() { 158 | $allCheckpointIds = $this->getAllCheckpointIds(); 159 | return \count($allCheckpointIds) > 0 ? \end($allCheckpointIds) : false; 160 | } 161 | 162 | /** 163 | * Return array of all checkpoint ids 164 | * @return array 165 | */ 166 | public function getAllCheckpointIds() { 167 | $checkpointDir = $this->locator->getCheckpointDir(); 168 | $content = $this->fsHelper->isDir($checkpointDir) ? $this->fsHelper->scandir($checkpointDir) : []; 169 | $checkpoints = \array_filter( 170 | $content, 171 | function ($dir) { 172 | $checkpointPath = $this->getCheckpointPath($dir); 173 | return !\in_array($dir, ['.', '..']) && $this->fsHelper->isDir($checkpointPath); 174 | } 175 | ); 176 | return $checkpoints; 177 | } 178 | 179 | /** 180 | * Create an unique checkpoint id 181 | * @return string 182 | */ 183 | protected function createCheckpointId() { 184 | $versionString = \implode('.', $this->locator->getInstalledVersion()); 185 | return \uniqid($versionString . '-'); 186 | } 187 | 188 | /** 189 | * Get an absolute path to the checkpoint directory by checkpoint Id 190 | * @param string $checkpointId id of checkpoint 191 | * @return string 192 | */ 193 | public function getCheckpointPath($checkpointId) { 194 | return $this->locator->getCheckpointDir() . '/' . $checkpointId; 195 | } 196 | 197 | /** 198 | * Produce an error on non-existing checkpoints 199 | * @param string $checkpointId id of checkpoint 200 | * @throws \UnexpectedValueException if there is no checkpoint with this id 201 | */ 202 | private function assertCheckpointExists($checkpointId) { 203 | if (!$this->checkpointExists($checkpointId) || $checkpointId === '') { 204 | $message = \sprintf('Checkpoint %s does not exist.', $checkpointId); 205 | throw new \UnexpectedValueException($message); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/Utils/Collection.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | /** 25 | * Class Collection 26 | * 27 | * @package Owncloud\Updater\Utils 28 | */ 29 | class Collection { 30 | private $notReadable = []; 31 | private $notWritable = []; 32 | 33 | public function reset() { 34 | $this->notReadable = []; 35 | $this->notWritable = []; 36 | } 37 | 38 | /** 39 | * @param $item 40 | */ 41 | public function addNotReadable($item) { 42 | if (!\in_array($item, $this->notReadable)) { 43 | $this->notReadable[] = $item; 44 | } 45 | } 46 | 47 | /** 48 | * @param $item 49 | */ 50 | public function addNotWritable($item) { 51 | if (!\in_array($item, $this->notWritable)) { 52 | $this->notWritable[] = $item; 53 | } 54 | } 55 | 56 | /** 57 | * @return array 58 | */ 59 | public function getNotReadable() { 60 | return $this->notReadable; 61 | } 62 | 63 | /** 64 | * @return array 65 | */ 66 | public function getNotWritable() { 67 | return $this->notWritable; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Utils/ConfigReader.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | /** 25 | * Class ConfigReader 26 | * 27 | * @package Owncloud\Updater\Utils 28 | */ 29 | class ConfigReader { 30 | /** @var array Associative array ($key => $value) */ 31 | protected $cache = []; 32 | 33 | /** 34 | * @var OccRunner $occRunner 35 | */ 36 | protected $occRunner; 37 | 38 | /** 39 | * @var bool 40 | */ 41 | protected $isLoaded = false; 42 | 43 | /** 44 | * 45 | * @param OccRunner $occRunner 46 | */ 47 | public function __construct(OccRunner $occRunner) { 48 | $this->occRunner = $occRunner; 49 | } 50 | 51 | public function init() { 52 | $this->load(); 53 | } 54 | 55 | /** 56 | * @return bool 57 | */ 58 | public function getIsLoaded() { 59 | return $this->isLoaded; 60 | } 61 | 62 | /** 63 | * Get a value from OC config by 64 | * path key1.key2.key3 65 | * @param string $path 66 | * @return mixed 67 | */ 68 | public function getByPath($path) { 69 | return $this->get(\explode('.', $path)); 70 | } 71 | 72 | /** 73 | * Get a value from OC config by keys 74 | * @param array $keys 75 | * @return mixed 76 | */ 77 | public function get($keys) { 78 | $config = $this->cache; 79 | do { 80 | $key = \array_shift($keys); 81 | if (!\count($keys)>0 && !\is_array($config)) { 82 | return null; 83 | } 84 | if (!\array_key_exists($key, $config)) { 85 | return null; 86 | } 87 | $config = $config[$key]; 88 | } while ($keys); 89 | return $config; 90 | } 91 | 92 | /** 93 | * Get OC Edition 94 | * @return string 95 | * @throws \Symfony\Component\Process\Exception\ProcessFailedException 96 | */ 97 | public function getEdition() { 98 | $response = $this->occRunner->runJson('status'); 99 | return $response['edition']; 100 | } 101 | 102 | /** 103 | * Export OC config as JSON and parse it into the cache 104 | * @throws \Symfony\Component\Process\Exception\ProcessFailedException 105 | * @throws \UnexpectedValueException 106 | */ 107 | private function load() { 108 | $this->cache = $this->occRunner->runJson('config:list', ['--private']); 109 | $this->isLoaded = true; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Utils/DocLink.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | /** 25 | * Class DocLink 26 | * 27 | * @package Owncloud\Updater\Utils 28 | */ 29 | class DocLink { 30 | public const BASE_DOC_URL = 'https://doc.owncloud.com/server'; 31 | 32 | private $version; 33 | 34 | /** 35 | * DocLink constructor. 36 | * 37 | * @param string $version 38 | */ 39 | public function __construct($version) { 40 | $this->version = $this->trimVersion($version); 41 | } 42 | 43 | /** 44 | * Cut everything except Major.Minor 45 | * @param string $version 46 | * @return string 47 | */ 48 | protected function trimVersion($version) { 49 | if (\preg_match('|^\d+\.\d+|', $version, $matches)>0) { 50 | return $matches[0]; 51 | } 52 | return ''; 53 | } 54 | 55 | /** 56 | * @param string $relativePart 57 | * @return string 58 | */ 59 | public function getAdminManualUrl($relativePart) { 60 | return \sprintf( 61 | '%s/%s/admin_manual/%s', 62 | self::BASE_DOC_URL, 63 | $this->version, 64 | $relativePart 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Utils/Feed.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | /** 25 | * Class Feed 26 | * 27 | * @package Owncloud\Updater\Utils 28 | */ 29 | class Feed { 30 | /** string $version */ 31 | protected $version; 32 | 33 | /** string $versionString */ 34 | protected $versionString; 35 | 36 | /** string $url */ 37 | protected $url; 38 | 39 | /** string $web */ 40 | protected $web; 41 | 42 | /** array $requiredFeedEntries */ 43 | protected $requiredFeedEntries = [ 44 | 'version', 45 | 'versionstring', 46 | 'url' 47 | ]; 48 | 49 | /** bool $isValid */ 50 | protected $isValid = true; 51 | 52 | /** 53 | * 54 | * @param array $data 55 | */ 56 | public function __construct($data) { 57 | $missingEntries = []; 58 | foreach ($this->requiredFeedEntries as $index) { 59 | if (!isset($data[$index]) || empty($data[$index])) { 60 | $missingEntries[] = $index; 61 | $data[$index] = ''; 62 | } 63 | } 64 | 65 | if (\count($missingEntries)) { 66 | $this->isValid = false; 67 | //'Got missing or empty fileds for: ' . implode(',', $missingEntries) . '. No updates found.'; 68 | } 69 | $this->version = $data['version']; 70 | $this->versionString = $data['versionstring']; 71 | $this->url = $data['url']; 72 | } 73 | 74 | /** 75 | * Build filename to download as a.b.c.d.zip 76 | * @return string 77 | */ 78 | public function getDownloadedFileName() { 79 | $extension = \preg_replace('|.*?((\.tar)?\.[^.]*)$|s', '\1', $this->getUrl()); 80 | return $this->getVersion() . $extension; 81 | } 82 | 83 | /** 84 | * Does feed contain all the data required? 85 | * @return bool 86 | */ 87 | public function isValid() { 88 | return $this->isValid; 89 | } 90 | 91 | /** 92 | * 93 | * @return string 94 | */ 95 | public function getVersion() { 96 | return $this->version; 97 | } 98 | 99 | /** 100 | * 101 | * @return string 102 | */ 103 | public function getVersionString() { 104 | return $this->versionString; 105 | } 106 | 107 | /** 108 | * Get url to download a new version from 109 | * @return string 110 | */ 111 | public function getUrl() { 112 | return $this->url; 113 | } 114 | 115 | /** 116 | * Get url to download a checksum from 117 | * @return string 118 | */ 119 | public function getChecksumUrl() { 120 | return $this->url . '.md5'; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Utils/Fetcher.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2021, ownCloud GmbH 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | use GuzzleHttp\Client; 25 | use GuzzleHttp\RequestOptions; 26 | use Psr\Http\Message\ResponseInterface; 27 | 28 | /** 29 | * Class Fetcher 30 | * 31 | * @package Owncloud\Updater\Utils 32 | */ 33 | class Fetcher { 34 | public const DEFAULT_BASE_URL = 'https://updates.owncloud.com/server/'; 35 | 36 | /** 37 | * @var Locator $locator 38 | */ 39 | protected $locator; 40 | 41 | /** 42 | * @var ConfigReader $configReader 43 | */ 44 | protected $configReader; 45 | 46 | /** 47 | * @var Client $httpClient 48 | */ 49 | protected $httpClient; 50 | protected $requiredFeedEntries = [ 51 | 'version', 52 | 'versionstring', 53 | 'url' 54 | ]; 55 | 56 | /** 57 | * Constructor 58 | * 59 | * @param Client $httpClient 60 | * @param Locator $locator 61 | * @param ConfigReader $configReader 62 | */ 63 | public function __construct(Client $httpClient, Locator $locator, ConfigReader $configReader) { 64 | $this->httpClient = $httpClient; 65 | $this->locator = $locator; 66 | $this->configReader = $configReader; 67 | } 68 | 69 | /** 70 | * Download new ownCloud package 71 | * @param Feed $feed 72 | * @param Callable $onProgress 73 | * @throws \Exception 74 | * @throws \UnexpectedValueException 75 | */ 76 | public function getOwncloud(Feed $feed, callable $onProgress) { 77 | if ($feed->isValid()) { 78 | $downloadPath = $this->getBaseDownloadPath($feed); 79 | if (!\is_writable(\dirname($downloadPath))) { 80 | throw new \Exception(\dirname($downloadPath) . ' is not writable.'); 81 | } 82 | $url = $feed->getUrl(); 83 | $response = $this->httpClient->request( 84 | 'GET', 85 | $url, 86 | [ 87 | RequestOptions::PROGRESS => $onProgress, 88 | RequestOptions::SINK => $downloadPath, 89 | RequestOptions::TIMEOUT => 600 90 | ] 91 | ); 92 | $this->validateResponse($response, $url); 93 | } 94 | } 95 | 96 | /** 97 | * Produce a local path to save the package to 98 | * @param Feed $feed 99 | * @return string 100 | */ 101 | public function getBaseDownloadPath(Feed $feed) { 102 | $basePath = $this->locator->getDownloadBaseDir(); 103 | return $basePath . '/' . $feed->getDownloadedFileName(); 104 | } 105 | 106 | /** 107 | * Get md5 sum for the package 108 | * @param Feed $feed 109 | * @return string 110 | */ 111 | public function getMd5(Feed $feed) { 112 | $fullChecksum = $this->download($feed->getChecksumUrl()); 113 | // we got smth like "5776cbd0a95637ade4b2c0d8694d8fca -" 114 | //strip trailing space & dash 115 | return \substr($fullChecksum, 0, 32); 116 | } 117 | 118 | /** 119 | * Read update feed for new releases 120 | * @return Feed 121 | */ 122 | public function getFeed() { 123 | $url = $this->getFeedUrl(); 124 | $xml = $this->download($url); 125 | $tmp = []; 126 | if ($xml) { 127 | $loadEntities = \libxml_disable_entity_loader(true); 128 | $data = @\simplexml_load_string($xml); 129 | \libxml_disable_entity_loader($loadEntities); 130 | if ($data !== false) { 131 | $tmp['version'] = (string) $data->version; 132 | $tmp['versionstring'] = (string) $data->versionstring; 133 | $tmp['url'] = (string) $data->url; 134 | $tmp['web'] = (string) $data->web; 135 | } 136 | } 137 | 138 | return new Feed($tmp); 139 | } 140 | 141 | /** 142 | * @return mixed|string 143 | */ 144 | public function getUpdateChannel() { 145 | $channel = $this->configReader->getByPath('apps.core.OC_Channel'); 146 | if ($channel === null) { 147 | return $this->locator->getChannelFromVersionsFile(); 148 | } 149 | 150 | return $channel; 151 | } 152 | 153 | /** 154 | * Produce complete feed URL 155 | * @return string 156 | */ 157 | protected function getFeedUrl() { 158 | $currentVersion = $this->configReader->getByPath('system.version'); 159 | $version = \explode('.', $currentVersion); 160 | $version['installed'] = $this->configReader->getByPath('apps.core.installedat'); 161 | $version['updated'] = $this->configReader->getByPath('apps.core.lastupdatedat'); 162 | $version['updatechannel'] = $this->getUpdateChannel(); 163 | $version['edition'] = $this->configReader->getEdition(); 164 | $version['build'] = $this->locator->getBuild(); 165 | 166 | // Read updater server URL from config 167 | $updaterServerUrl = $this->configReader->get(['system', 'updater.server.url']); 168 | if ((bool) $updaterServerUrl === false) { 169 | $updaterServerUrl = self::DEFAULT_BASE_URL; 170 | } 171 | 172 | $url = $updaterServerUrl . '?version=' . \implode('x', $version); 173 | return $url; 174 | } 175 | 176 | /** 177 | * Get URL content 178 | * @param string $url 179 | * @return string 180 | * @throws \UnexpectedValueException 181 | */ 182 | protected function download($url) { 183 | $response = $this->httpClient->request('GET', $url, [RequestOptions::TIMEOUT => 600]); 184 | $this->validateResponse($response, $url); 185 | return $response->getBody()->getContents(); 186 | } 187 | 188 | /** 189 | * Check if request was successful 190 | * @param ResponseInterface $response 191 | * @param string $url 192 | * @throws \UnexpectedValueException 193 | */ 194 | protected function validateResponse(ResponseInterface $response, $url) { 195 | $statusCode = $response->getStatusCode(); 196 | if ($statusCode !== 200) { 197 | throw new \UnexpectedValueException( 198 | "Failed to download $url. Server responded with $statusCode instead of 200." 199 | ); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/Utils/FilesystemHelper.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | /** 25 | * Class FilesystemHelper 26 | * 27 | * @package Owncloud\Updater\Utils 28 | */ 29 | class FilesystemHelper { 30 | /** 31 | * Wrapper for filemtime function 32 | * @param string $path 33 | * @return integer 34 | */ 35 | public function filemtime($path) { 36 | return \filemtime($path); 37 | } 38 | 39 | /** 40 | * Wrapper for scandir function. 41 | * Filters current and parent directories 42 | * @param string $path 43 | * @return array 44 | */ 45 | public function scandirFiltered($path) { 46 | $content = $this->scandir($path); 47 | if (\is_array($content)) { 48 | return \array_diff($content, ['.', '..']); 49 | } 50 | return []; 51 | } 52 | 53 | /** 54 | * Wrapper for scandir function 55 | * @param string $path 56 | * @return array 57 | */ 58 | public function scandir($path) { 59 | return \scandir($path); 60 | } 61 | 62 | /** 63 | * Wrapper for file_exists function 64 | * @param string $path 65 | * @return bool 66 | */ 67 | public function fileExists($path) { 68 | return \file_exists($path); 69 | } 70 | 71 | /** 72 | * Wrapper for is_writable function 73 | * @param string $path 74 | * @return bool 75 | */ 76 | public function isWritable($path) { 77 | return \is_writable($path); 78 | } 79 | 80 | /** 81 | * Wrapper for is_dir function 82 | * @param string $path 83 | * @return bool 84 | */ 85 | public function isDir($path) { 86 | return \is_dir($path); 87 | } 88 | 89 | /** 90 | * Wrapper for md5_file function 91 | * @param string $path 92 | * @return string 93 | */ 94 | public function md5File($path) { 95 | return \md5_file($path); 96 | } 97 | 98 | /** 99 | * Wrapper for mkdir 100 | * @param string $path 101 | * @param bool $isRecursive 102 | * @throws \Exception on error 103 | */ 104 | public function mkdir($path, $isRecursive = false) { 105 | if (!\mkdir($path, 0755, $isRecursive)) { 106 | throw new \Exception("Unable to create $path"); 107 | } 108 | } 109 | 110 | /** 111 | * Copy recursive 112 | * @param string $src - source path 113 | * @param string $dest - destination path 114 | * @throws \Exception on error 115 | */ 116 | public function copyr($src, $dest, $stopOnError = true) { 117 | if (\is_dir($src)) { 118 | if (!\is_dir($dest)) { 119 | try { 120 | $this->mkdir($dest); 121 | } catch (\Exception $e) { 122 | if ($stopOnError) { 123 | throw $e; 124 | } 125 | } 126 | } 127 | $files = \scandir($src); 128 | foreach ($files as $file) { 129 | if (!\in_array($file, [".", ".."])) { 130 | $this->copyr("$src/$file", "$dest/$file", $stopOnError); 131 | } 132 | } 133 | } elseif (\file_exists($src)) { 134 | if (!\copy($src, $dest) && $stopOnError) { 135 | throw new \Exception("Unable to copy $src to $dest"); 136 | } 137 | } 138 | } 139 | 140 | /** 141 | * Moves file/directory 142 | * @param string $src - source path 143 | * @param string $dest - destination path 144 | * @throws \Exception on error 145 | */ 146 | public function move($src, $dest) { 147 | if (!\rename($src, $dest)) { 148 | throw new \Exception("Unable to move $src to $dest"); 149 | } 150 | } 151 | 152 | /** 153 | * Check permissions recursive 154 | * @param string $src - path to check 155 | * @param Collection $collection - object to store incorrect permissions 156 | */ 157 | public function checkr($src, $collection) { 158 | if (!\file_exists($src)) { 159 | return; 160 | } 161 | if (!\is_writable($src)) { 162 | $collection->addNotWritable($src); 163 | } 164 | if (!\is_readable($src)) { 165 | $collection->addNotReadable($src); 166 | } 167 | if (\is_dir($src)) { 168 | $files = \scandir($src); 169 | foreach ($files as $file) { 170 | if (!\in_array($file, [".", ".."])) { 171 | $this->checkr("$src/$file", $collection); 172 | } 173 | } 174 | } 175 | } 176 | 177 | /** 178 | * @param string $path 179 | */ 180 | public function removeIfExists($path) { 181 | if (!\file_exists($path)) { 182 | return; 183 | } 184 | 185 | if (\is_dir($path)) { 186 | $this->rmdirr($path); 187 | } else { 188 | @\unlink($path); 189 | } 190 | } 191 | 192 | /** 193 | * @param $dir 194 | * @return bool 195 | */ 196 | public function rmdirr($dir) { 197 | if (\is_dir($dir)) { 198 | $files = \scandir($dir); 199 | foreach ($files as $file) { 200 | if ($file != "." && $file != "..") { 201 | $this->rmdirr("$dir/$file"); 202 | } 203 | } 204 | @\rmdir($dir); 205 | } elseif (\file_exists($dir)) { 206 | @\unlink($dir); 207 | } 208 | if (\file_exists($dir)) { 209 | return false; 210 | } else { 211 | return true; 212 | } 213 | } 214 | 215 | /** 216 | * 217 | * @param string $old 218 | * @param string $new 219 | * @param string $temp 220 | * @param string $dirName 221 | */ 222 | public function tripleMove($old, $new, $temp, $dirName) { 223 | if ($this->fileExists($old . '/' . $dirName)) { 224 | $this->copyr($old . '/' . $dirName, $temp . '/' . $dirName, false); 225 | $this->rmdirr($old . '/' . $dirName); 226 | } 227 | if ($this->fileExists($new . '/' . $dirName)) { 228 | $this->copyr($new . '/' . $dirName, $old . '/' . $dirName, false); 229 | $this->rmdirr($new . '/' . $dirName); 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/Utils/Locator.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | use \Owncloud\Updater\Console\Application; 25 | 26 | /** 27 | * Class Locator 28 | * 29 | * @package Owncloud\Updater\Utils 30 | */ 31 | class Locator { 32 | /** 33 | * absolute path to ownCloud root 34 | * @var string 35 | */ 36 | protected $ownCloudRootPath; 37 | 38 | /** 39 | * absolute path to updater root 40 | * @var string 41 | */ 42 | protected $updaterRootPath; 43 | 44 | /** 45 | * 46 | * @param string $baseDir 47 | */ 48 | public function __construct($baseDir) { 49 | $this->updaterRootPath = $baseDir; 50 | $this->ownCloudRootPath = \dirname($baseDir); 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function getOwnCloudRootPath() { 57 | return $this->ownCloudRootPath; 58 | } 59 | 60 | /** 61 | * expected items in the core 62 | * @return string[] 63 | */ 64 | public function getRootDirContent() { 65 | return [ 66 | "3rdparty", 67 | "config", 68 | "core", 69 | "l10n", 70 | "lib", 71 | "ocm-provider", 72 | "ocs", 73 | "ocs-provider", 74 | "resources", 75 | "settings", 76 | ".htaccess", 77 | ".mailmap", 78 | ".tag", 79 | ".user.ini", 80 | "AUTHORS", 81 | "CHANGELOG.md", 82 | "console.php", 83 | "COPYING", 84 | "cron.php", 85 | "db_structure.xml", 86 | "index.html", 87 | "index.php", 88 | "indie.json", 89 | "occ", 90 | "public.php", 91 | "remote.php", 92 | "robots.txt", 93 | "status.php", 94 | "version.php", 95 | ]; 96 | } 97 | 98 | /** 99 | * @return array 100 | */ 101 | public function getUpdaterContent() { 102 | return [ 103 | 'app', 104 | 'application.php', 105 | 'box.json', 106 | 'composer.json', 107 | 'composer.lock', 108 | 'CHANGELOG.md', 109 | 'CONTRIBUTING.md', 110 | 'COPYING-AGPL', 111 | 'index.php', 112 | 'pub', 113 | 'src', 114 | 'vendor', 115 | 'README.md', 116 | '.travis.yml', 117 | '.scrutinizer.yml', 118 | 'nbproject', 119 | ]; 120 | } 121 | 122 | /** 123 | * Get all files and directories in the OC root dir using signature.json as a source 124 | * 125 | * @param string $basePath 126 | * 127 | * @return array 128 | */ 129 | public function getRootDirItemsFromSignature($basePath) { 130 | $signature = $this->getSignature($basePath); 131 | $items = []; 132 | if (isset($signature['hashes'])) { 133 | $allItems = \array_keys($signature['hashes']); 134 | foreach ($allItems as $k => $v) { 135 | // Get the part of the string before the first slash or entire string if there is no slash 136 | $allItems[$k] = \strtok($v, '/'); 137 | } 138 | $items = \array_unique($allItems); 139 | } 140 | return $items; 141 | } 142 | 143 | /** 144 | * Absolute path to core root dir content 145 | * @return array 146 | */ 147 | public function getRootDirItems() { 148 | $items = $this->getRootDirContent(); 149 | $items = \array_map( 150 | function ($item) { 151 | return $this->ownCloudRootPath . "/" . $item; 152 | }, 153 | $items 154 | ); 155 | return $items; 156 | } 157 | 158 | /** 159 | * Absolute path 160 | * @return string 161 | * @throws \Exception 162 | */ 163 | public function getDataDir() { 164 | $container = Application::$container; 165 | if (isset($container['utils.configReader']) && $container['utils.configReader']->getIsLoaded()) { 166 | return $container['utils.configReader']->getByPath('system.datadirectory'); 167 | } 168 | 169 | // Fallback case 170 | include $this->getPathToConfigFile(); 171 | if (isset($CONFIG['datadirectory'])) { 172 | return $CONFIG['datadirectory']; 173 | } 174 | 175 | // Something went wrong 176 | throw new \Exception('Unable to detect datadirectory'); 177 | } 178 | 179 | /** 180 | * Absolute path to updater root dir 181 | * @return string 182 | */ 183 | public function getUpdaterBaseDir() { 184 | return $this->getDataDir() . '/updater-data'; 185 | } 186 | 187 | /** 188 | * Absolute path to create a core and apps backups 189 | * @return string 190 | */ 191 | public function getCheckpointDir() { 192 | return $this->getUpdaterBaseDir() . '/checkpoint'; 193 | } 194 | 195 | /** 196 | * Absolute path to store downloaded packages 197 | * @return string 198 | */ 199 | public function getDownloadBaseDir() { 200 | return $this->getUpdaterBaseDir() . '/download'; 201 | } 202 | 203 | /** 204 | * Absolute path to a temporary directory 205 | * to extract downloaded packages into 206 | * @return string 207 | */ 208 | public function getExtractionBaseDir() { 209 | return $this->getUpdaterBaseDir() . "/_oc_upgrade"; 210 | } 211 | 212 | /** 213 | * 214 | * @return string 215 | */ 216 | public function getPathToOccFile() { 217 | return $this->ownCloudRootPath . '/occ'; 218 | } 219 | 220 | /** 221 | * 222 | * @return array 223 | */ 224 | public function getInstalledVersion() { 225 | include $this->getPathToVersionFile(); 226 | 227 | /** @var $OC_Version array */ 228 | return $OC_Version; /* @phan-suppress-current-line PhanUndeclaredVariable */ 229 | } 230 | 231 | /** 232 | * 233 | * @return string 234 | */ 235 | public function getChannelFromVersionsFile() { 236 | include $this->getPathToVersionFile(); 237 | 238 | /** @var $OC_Channel string */ 239 | return $OC_Channel; /* @phan-suppress-current-line PhanUndeclaredVariable */ 240 | } 241 | 242 | /** 243 | * 244 | * @return string 245 | */ 246 | public function getBuild() { 247 | include $this->getPathToVersionFile(); 248 | 249 | /** @var $OC_Build string */ 250 | return $OC_Build; /* @phan-suppress-current-line PhanUndeclaredVariable */ 251 | } 252 | 253 | /** 254 | * @return string 255 | */ 256 | public function getSecretFromConfig() { 257 | include $this->getPathToConfigFile(); 258 | if (isset($CONFIG['updater.secret'])) { 259 | return $CONFIG['updater.secret']; 260 | } 261 | return ''; 262 | } 263 | 264 | /** 265 | * @param string $filePostfix 266 | * @return array 267 | */ 268 | public function getPathtoConfigFiles($filePostfix = 'config.php') { 269 | // Only config.php for now 270 | return [ 271 | $this->ownCloudRootPath . '/config/' . $filePostfix 272 | ]; 273 | } 274 | 275 | /** 276 | * @return string 277 | */ 278 | public function getPathToConfigFile() { 279 | return $this->ownCloudRootPath . '/config/config.php'; 280 | } 281 | 282 | /** 283 | * 284 | * @return string 285 | */ 286 | public function getPathToVersionFile() { 287 | return $this->ownCloudRootPath . '/version.php'; 288 | } 289 | 290 | /** 291 | * @param string $rootPath 292 | * 293 | * @return array|mixed 294 | */ 295 | private function getSignature($rootPath) { 296 | $signature = []; 297 | $signaturePath = $rootPath . '/core/signature.json'; 298 | if (\is_file($signaturePath)) { 299 | $signature = \json_decode(\file_get_contents($signaturePath), true); 300 | if (!\is_array($signature)) { 301 | $signature = []; 302 | } 303 | } 304 | return $signature; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/Utils/OccRunner.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2021, ownCloud GmbH 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | use GuzzleHttp\Client; 25 | use GuzzleHttp\RequestOptions; 26 | use Owncloud\Updater\Console\Application; 27 | use Symfony\Component\Process\Process; 28 | use Symfony\Component\Process\ProcessUtils; 29 | use Symfony\Component\Process\Exception\ProcessFailedException; 30 | 31 | /** 32 | * Class OccRunner 33 | * 34 | * @package Owncloud\Updater\Utils 35 | */ 36 | class OccRunner { 37 | /** 38 | * @var Locator $locator 39 | */ 40 | protected $locator; 41 | 42 | /** 43 | * @var bool 44 | */ 45 | protected $canUseProcess; 46 | 47 | /** 48 | * 49 | * @param Locator $locator 50 | * @param bool $canUseProcess 51 | */ 52 | public function __construct(Locator $locator, $canUseProcess) { 53 | $this->locator = $locator; 54 | $this->canUseProcess = $canUseProcess; 55 | } 56 | 57 | /** 58 | * @param bool $canUseProcess 59 | */ 60 | public function setCanUseProcess($canUseProcess) { 61 | $this->canUseProcess = $canUseProcess; 62 | } 63 | 64 | /** 65 | * @param $command 66 | * @param array $args 67 | * @param bool $asJson 68 | * @return string 69 | */ 70 | public function run($command, $args = [], $asJson = false) { 71 | if ($this->canUseProcess) { 72 | $extra = $asJson ? '--output=json' : ''; 73 | $cmdLine = \trim($command . ' ' . $extra); 74 | foreach ($args as $optionTitle => $optionValue) { 75 | if (\strpos($optionTitle, '--') === 0) { 76 | $line = \trim("$optionTitle=$optionValue"); 77 | } else { 78 | $line = $optionValue; 79 | } 80 | /* @phan-suppress-next-line PhanDeprecatedFunction */ 81 | $escapedLine = ProcessUtils::escapeArgument($line); 82 | $cmdLine .= " $escapedLine"; 83 | } 84 | return $this->runAsProcess($cmdLine); 85 | } else { 86 | if ($asJson) { 87 | $args['--output'] = 'json'; 88 | } 89 | $response = $this->runAsRequest($command, $args); 90 | $decodedResponse = \json_decode($response, true); 91 | /* @phan-suppress-next-line PhanTypeArraySuspiciousNullable */ 92 | return $decodedResponse['response']; 93 | } 94 | } 95 | 96 | /** 97 | * @param $command 98 | * @param array $args 99 | * @return mixed 100 | */ 101 | public function runJson($command, $args = []) { 102 | $plain = $this->run($command, $args, true); 103 | // trim response to always be a valid json. Capture everything between the first and the last curly brace 104 | \preg_match_all('!(\{.*\})!ms', $plain, $matches); 105 | $clean = isset($matches[1][0]) ? $matches[1][0] : ''; 106 | $decoded = \json_decode($clean, true); 107 | if (!\is_array($decoded)) { 108 | throw new \UnexpectedValueException('Could not parse a response for ' . $command . '. Please check if the current shell user can run occ command. Raw output: ' . PHP_EOL . $plain); 109 | } 110 | return $decoded; 111 | } 112 | 113 | /** 114 | * @param $command 115 | * @param $args 116 | * @return string 117 | */ 118 | protected function runAsRequest($command, $args) { 119 | $application = $this->getApplication(); 120 | $client = new Client(); 121 | $endpointBase = $application->getEndpoint(); 122 | $params = [ 123 | RequestOptions::TIMEOUT => 0, 124 | RequestOptions::JSON => [ 125 | 'token' => $application->getAuthToken(), 126 | 'params'=> $args 127 | ] 128 | ]; 129 | 130 | // Skip SSL validation for localhost only as localhost never has a valid cert 131 | if (\preg_match('/^https:\/\/localhost\/.*/i', $endpointBase)) { 132 | $params[RequestOptions::VERIFY] = false; 133 | } 134 | 135 | $response = $client->request('POST', "$endpointBase$command", $params); 136 | $responseBody = $response->getBody()->getContents(); 137 | return $responseBody; 138 | } 139 | 140 | /** 141 | * @return mixed 142 | */ 143 | protected function getApplication() { 144 | $container = Application::$container; 145 | $application = $container['application']; 146 | return $application; 147 | } 148 | 149 | /** 150 | * @param $cmdLine 151 | * @return string 152 | */ 153 | protected function runAsProcess($cmdLine) { 154 | $occPath = $this->locator->getPathToOccFile(); 155 | $cmd = "php $occPath --no-warnings $cmdLine"; 156 | $process = new Process($cmd); 157 | $process->setTimeout(null); 158 | $process->run(); 159 | 160 | if (!$process->isSuccessful()) { 161 | throw new ProcessFailedException($process); 162 | } 163 | return $process->getOutput(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Utils/Registry.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | namespace Owncloud\Updater\Utils; 24 | 25 | /** 26 | * Class Registry 27 | * 28 | * @package Owncloud\Updater\Utils 29 | */ 30 | class Registry { 31 | protected $objects = []; 32 | 33 | /** 34 | * 35 | * @param string $name 36 | * @param mixed $object 37 | */ 38 | public function set($name, $object) { 39 | $this->objects[$name] = $object; 40 | $_SESSION[$name] = \serialize($object); 41 | } 42 | 43 | /** 44 | * 45 | * @param string $name 46 | * @return mixed 47 | */ 48 | public function get($name) { 49 | if (isset($this->objects[$name])) { 50 | return $this->objects[$name]; 51 | } elseif (isset($_SESSION[$name])) { 52 | $this->objects[$name] = \unserialize($_SESSION[$name]); 53 | return $this->objects[$name]; 54 | } 55 | return null; 56 | } 57 | 58 | /** 59 | * 60 | * @param string $name 61 | */ 62 | public function clear($name) { 63 | unset($this->objects[$name], $_SESSION[$name]); 64 | } 65 | 66 | public function clearAll() { 67 | foreach ($this->objects as $name=>$value) { 68 | $this->clear($name); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Utils/ZipExtractor.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * @copyright Copyright (c) 2015, ownCloud, Inc. 6 | * @license AGPL-3.0 7 | * 8 | * This code is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License, version 3, 10 | * as published by the Free Software Foundation. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License, version 3, 18 | * along with this program. If not, see 19 | * 20 | */ 21 | 22 | namespace Owncloud\Updater\Utils; 23 | 24 | use Symfony\Component\Console\Output\OutputInterface; 25 | use Symfony\Component\Process\Process; 26 | use Symfony\Component\Process\ProcessUtils; 27 | use ZipArchive; 28 | 29 | /** 30 | * Class ZipExtractor 31 | * 32 | * @package Owncloud\Updater\Utils 33 | */ 34 | class ZipExtractor { 35 | protected $file; 36 | protected $path; 37 | 38 | /** @var OutputInterface */ 39 | protected $output; 40 | 41 | /** 42 | * @param string $file 43 | * @param string $path 44 | * @param OutputInterface|null $output 45 | */ 46 | public function __construct($file, $path, OutputInterface $output = null) { 47 | $this->file = $file; 48 | $this->path = $path; 49 | if ($output !== null) { 50 | $this->output = $output; 51 | } 52 | } 53 | 54 | /** 55 | * @return bool 56 | */ 57 | public function extract() { 58 | if ($this->extractShell()) { 59 | return true; 60 | } 61 | if (!\class_exists('ZipArchive')) { 62 | throw new \RuntimeException("Could not decompress the archive, enable the PHP zip extension or install unzip."); 63 | } 64 | return $this->extractZipArchive(); 65 | } 66 | 67 | /** 68 | * @return bool 69 | */ 70 | private function extractShell() { 71 | /* @phan-suppress-next-line PhanDeprecatedFunction */ 72 | $command = 'unzip ' . ProcessUtils::escapeArgument($this->file) . ' -d ' . ProcessUtils::escapeArgument($this->path) . ' && chmod -R u+w ' . ProcessUtils::escapeArgument($this->path); 73 | $process = new Process($command); 74 | $process->setTimeout(null); 75 | $process->run(); 76 | if ($this->output) { 77 | $this->output->writeln($process->getErrorOutput()); 78 | } 79 | return $process->isSuccessful(); 80 | } 81 | 82 | /** 83 | * @return bool 84 | */ 85 | private function extractZipArchive() { 86 | $zipArchive = new ZipArchive(); 87 | 88 | if (true !== ($exitCode = $zipArchive->open($this->file))) { 89 | throw new \UnexpectedValueException($this->getErrorMessage($exitCode), $exitCode); 90 | } 91 | 92 | if ($zipArchive->extractTo($this->path) !== true) { 93 | throw new \RuntimeException("There was an error extracting the ZIP file. Corrupt file?"); 94 | } 95 | 96 | $zipArchive->close(); 97 | return true; 98 | } 99 | 100 | /** 101 | * @param int $exitCode 102 | * @return string 103 | */ 104 | protected function getErrorMessage($exitCode) { 105 | switch ($exitCode) { 106 | case ZipArchive::ER_EXISTS: 107 | return \sprintf("File '%s' already exists.", $this->file); 108 | case ZipArchive::ER_INCONS: 109 | return \sprintf("Zip archive '%s' is inconsistent.", $this->file); 110 | case ZipArchive::ER_INVAL: 111 | return \sprintf("Invalid argument (%s)", $this->file); 112 | case ZipArchive::ER_MEMORY: 113 | return \sprintf("Malloc failure (%s)", $this->file); 114 | case ZipArchive::ER_NOENT: 115 | return \sprintf("No such zip file: '%s'", $this->file); 116 | case ZipArchive::ER_NOZIP: 117 | return \sprintf("'%s' is not a zip archive.", $this->file); 118 | case ZipArchive::ER_OPEN: 119 | return \sprintf("Can't open zip file: %s", $this->file); 120 | case ZipArchive::ER_READ: 121 | return \sprintf("Zip read error (%s)", $this->file); 122 | case ZipArchive::ER_SEEK: 123 | return \sprintf("Zip seek error (%s)", $this->file); 124 | default: 125 | return \sprintf("'%s' is not a valid zip archive, got error code: %s", $this->file, $exitCode); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/unit/Command/ExecuteCoreUpgradeScriptsCommandTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\Owncloud\Updater\Command\ExecuteCoreUpgradeScriptsCommand') 15 | ->disableOriginalConstructor() 16 | ->setMethods(null) 17 | ->getMock() 18 | ; 19 | $actualResult = $commmandMock->isUpgradeAllowed($installedVersion, $packageVersion, $canBeUpgradedFrom); 20 | $this->assertSame($expectedResult, $actualResult); 21 | } 22 | 23 | public function versionProvider() { 24 | return [ 25 | [ '9.0.1.2', '9.0.4.2', ['8.2'], true ], 26 | [ '9.0.4.2', '9.0.1.2', ['8.2'], false ], 27 | [ '9.0.4.2', '9.1.1.3', ['9.0'], true ], 28 | 29 | [ '9.1.1.3', '9.1.2.0', ['9.0'], true ], 30 | [ '9.1.2.0', '9.1.1.3', ['9.0'], false ], 31 | [ '9.1.1.3', '9.2.0.3', ['9.1'], true ], 32 | 33 | [ '9.2.0.3', '9.2.1.2', ['9.1'], true ], 34 | [ '9.2.1.2', '9.2.0.3', ['9.1'], false ], 35 | ]; 36 | } 37 | 38 | /** 39 | * @dataProvider allowedPreviousVersionsProvider 40 | * @param array $canBeUpgradedFrom 41 | * @param array $expectedVersions 42 | */ 43 | public function testLoadAllowedPreviousVersions($canBeUpgradedFrom, $expectedVersions) { 44 | $commmandMock = $this->getMockBuilder('\Owncloud\Updater\Command\ExecuteCoreUpgradeScriptsCommand') 45 | ->disableOriginalConstructor() 46 | ->setMethods(['loadCanBeUpgradedFrom']) 47 | ->getMock() 48 | ; 49 | $commmandMock->method('loadCanBeUpgradedFrom') 50 | ->willReturn($canBeUpgradedFrom); 51 | 52 | $class = new \ReflectionClass($commmandMock); 53 | $method = $class->getMethod('loadAllowedPreviousVersions'); 54 | $method->setAccessible(true); 55 | $actualResult = $method->invokeArgs($commmandMock, ['dummyPath']); 56 | $this->assertSame($expectedVersions, $actualResult); 57 | } 58 | 59 | public function allowedPreviousVersionsProvider() { 60 | return [ 61 | [[8,2], ['8.2']], 62 | [[ [9,0], [9,1]], ['9.0', '9.1']], 63 | ]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/unit/Controller/DownloadControllerTest.php: -------------------------------------------------------------------------------- 1 | '7.5.5', 17 | 'versionstring' => 'version 7.5.5', 18 | 'url' => 'http://nowhere.example.org', 19 | 'web' => 'http://nowhere.example.org/rtfm', 20 | ]; 21 | 22 | public function testCheckFeedSuccess() { 23 | $feed = new Feed($this->feedData); 24 | 25 | $fetcherMock = $this->getMockBuilder('Owncloud\Updater\Utils\Fetcher') 26 | ->disableOriginalConstructor() 27 | ->getMock() 28 | ; 29 | $fetcherMock->method('getFeed') 30 | ->willReturn($feed) 31 | ; 32 | $fsHelperMock = $this->getMockBuilder('Owncloud\Updater\Utils\FilesystemHelper') 33 | ->disableOriginalConstructor() 34 | ->getMock() 35 | ; 36 | $downloadController = new DownloadController( 37 | $fetcherMock, 38 | new Registry(), 39 | $fsHelperMock 40 | ); 41 | $result = $downloadController->checkFeed(); 42 | $this->assertSame(true, $result['success']); 43 | $this->assertSame('', $result['exception']); 44 | $this->assertSame($feed, $result['data']['feed']); 45 | } 46 | 47 | public function testCheckFeedFailure() { 48 | $badNewsException = new \Exception('Bad news'); 49 | $fetcherMock = $this->getMockBuilder('Owncloud\Updater\Utils\Fetcher') 50 | ->disableOriginalConstructor() 51 | ->getMock() 52 | ; 53 | $fetcherMock->method('getFeed') 54 | ->will($this->throwException($badNewsException)) 55 | ; 56 | $fsHelperMock = $this->getMockBuilder('Owncloud\Updater\Utils\FilesystemHelper') 57 | ->disableOriginalConstructor() 58 | ->getMock() 59 | ; 60 | $downloadController = new DownloadController( 61 | $fetcherMock, 62 | new Registry(), 63 | $fsHelperMock 64 | ); 65 | $result = $downloadController->checkFeed(); 66 | $this->assertSame(false, $result['success']); 67 | $this->assertSame([], $result['data']); 68 | $this->assertSame($badNewsException, $result['exception']); 69 | } 70 | 71 | public function testDownloadOwncloudSuccess() { 72 | $md5 = '911'; 73 | $path = '/dev/null/o'; 74 | $registry = new Registry(); 75 | $registry->set('feed', new Feed($this->feedData)); 76 | 77 | $fetcherMock = $this->getMockBuilder('Owncloud\Updater\Utils\Fetcher') 78 | ->disableOriginalConstructor() 79 | ->getMock() 80 | ; 81 | $fetcherMock->method('getBaseDownloadPath') 82 | ->willReturn($path) 83 | ; 84 | $fetcherMock->method('getOwncloud') 85 | ->willReturn(null) 86 | ; 87 | $fetcherMock->method('getMd5') 88 | ->willReturn($md5) 89 | ; 90 | 91 | $fsHelperMock = $this->getMockBuilder('Owncloud\Updater\Utils\FilesystemHelper') 92 | ->disableOriginalConstructor() 93 | ->getMock() 94 | ; 95 | $fsHelperMock->method('md5File') 96 | ->willReturn($md5) 97 | ; 98 | $fsHelperMock->method('fileExists') 99 | ->willReturn(true) 100 | ; 101 | 102 | $downloadController = new DownloadController( 103 | $fetcherMock, 104 | $registry, 105 | $fsHelperMock 106 | ); 107 | $result = $downloadController->downloadOwncloud(); 108 | $this->assertSame(true, $result['success']); 109 | $this->assertSame('', $result['exception']); 110 | $this->assertSame($path, $result['data']['path']); 111 | } 112 | 113 | public function testDownloadOwncloudFailure() { 114 | $md5 = '911'; 115 | $path = '/dev/null/o'; 116 | $registry = new Registry(); 117 | $registry->set('feed', new Feed($this->feedData)); 118 | $badNewsException = new \Exception('Bad news'); 119 | 120 | $fetcherMock = $this->getMockBuilder('Owncloud\Updater\Utils\Fetcher') 121 | ->disableOriginalConstructor() 122 | ->getMock() 123 | ; 124 | $fetcherMock->method('getBaseDownloadPath') 125 | ->willReturn($path) 126 | ; 127 | $fetcherMock->method('getOwncloud') 128 | ->will($this->throwException($badNewsException)) 129 | ; 130 | $fetcherMock->method('getMd5') 131 | ->willReturn($md5 . '0') 132 | ; 133 | 134 | $fsHelperMock = $this->getMockBuilder('Owncloud\Updater\Utils\FilesystemHelper') 135 | ->disableOriginalConstructor() 136 | ->getMock() 137 | ; 138 | $fsHelperMock->method('md5File') 139 | ->willReturn($md5) 140 | ; 141 | $fsHelperMock->method('fileExists') 142 | ->willReturn(true) 143 | ; 144 | 145 | $downloadController = new DownloadController( 146 | $fetcherMock, 147 | $registry, 148 | $fsHelperMock 149 | ); 150 | $result = $downloadController->downloadOwncloud(); 151 | $this->assertSame(false, $result['success']); 152 | $this->assertSame([], $result['data']); 153 | $this->assertSame($badNewsException, $result['exception']); 154 | ; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tests/unit/Http/RequestTest.php: -------------------------------------------------------------------------------- 1 | [ 'command' => 'jump'] ], 'dummy', null ], 20 | [ [ 'post'=> [ 'command' => 'jump'] ], 'command', 'jump' ], 21 | [ [ 'post'=> [ 'testArray' => ['key' => 'value'] ] ], 'testArray', ['key' => 'value'] ], 22 | ]; 23 | } 24 | 25 | /** 26 | * @dataProvider varsProvider 27 | */ 28 | public function testPostParameter($vars, $key, $expected) { 29 | $request = new Request($vars); 30 | $actual = $request->postParameter($key); 31 | $this->assertSame($expected, $actual); 32 | } 33 | 34 | /** 35 | * @return array 36 | */ 37 | public function serverProvider() { 38 | return [ 39 | [ [], 'abcd', null ], 40 | [ [ 'headers'=> [ 'command' => 'jump'] ], 'dummy', null ], 41 | [ [ 'headers'=> [ 'command' => 'jump'] ], 'command', 'jump' ], 42 | [ [ 'headers'=> [ 'testArray' => ['key' => 'value'] ] ], 'testArray', ['key' => 'value'] ], 43 | ]; 44 | } 45 | 46 | /** 47 | * @dataProvider serverProvider 48 | */ 49 | public function testServerVar($vars, $key, $expected) { 50 | $request = new Request($vars); 51 | $actual = $request->server($key); 52 | $this->assertSame($expected, $actual); 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function headerProvider() { 59 | return [ 60 | [ [], 'meow', null ], 61 | [ [ 'headers'=> [ 'command' => 'jump'] ], 'dummy', null ], 62 | [ [ 'headers'=> [ 'command' => 'jump'] ], 'command', null ], 63 | [ [ 'headers'=> [ 'testArray' => ['key' => 'value'] ] ], 'testArray', null ], 64 | [ [ 'headers'=> [ 'HTTP_TESTARRAY' => ['key' => 'value'] ] ], 'testArray', ['key' => 'value'] ], 65 | ]; 66 | } 67 | 68 | /** 69 | * @dataProvider headerProvider 70 | */ 71 | public function testHeaderVar($vars, $key, $expected) { 72 | $request = new Request($vars); 73 | $actual = $request->header($key); 74 | $this->assertSame($expected, $actual); 75 | } 76 | 77 | /** 78 | * @return array 79 | */ 80 | public function hostProvider() { 81 | return [ 82 | [ [ 'headers'=> [ 'SERVER_NAME' => 'jump' ] ], 'jump', null ], 83 | [ [ 'headers'=> [ 'HTTP_HOST'=> 'duck', 'SERVER_NAME' => 'jump'] ], 'duck' ], 84 | [ [ 'headers'=> [ 'HTTP_X_FORWARDED_HOST'=>'go', 'HTTP_HOST'=> 'duck', 'SERVER_NAME' => 'jump'] ], 'go' ], 85 | [ [ 'headers'=> [ 'HTTP_X_FORWARDED_HOST'=>'go,', 'HTTP_HOST'=> 'duck', 'SERVER_NAME' => 'jump'] ], 'go' ], 86 | [ [ 'headers'=> [ 'HTTP_X_FORWARDED_HOST'=>'run,forrest,run', 'HTTP_HOST'=> 'duck', 'SERVER_NAME' => 'jump'] ], 'run' ], 87 | ]; 88 | } 89 | 90 | /** 91 | * @dataProvider hostProvider 92 | * @param $vars 93 | * @param $expected 94 | */ 95 | public function testGetHost($vars, $expected) { 96 | $request = new Request($vars); 97 | $actual = $request->getHost(); 98 | $this->assertSame($expected, $actual); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/unit/Utils/AppManagerTest.php: -------------------------------------------------------------------------------- 1 | getOccRunnerMock('')); 16 | $result = $appManager->disableApp($appId); 17 | $this->assertTrue($result); 18 | } 19 | 20 | public function testEnableApp() { 21 | $appId = 'anyapp'; 22 | $appManager = new AppManager($this->getOccRunnerMock('')); 23 | $result = $appManager->enableApp($appId); 24 | $this->assertTrue($result); 25 | } 26 | 27 | /** 28 | * @return array 29 | */ 30 | public function appListProvider() { 31 | return [ 32 | 33 | [ 34 | [ 35 | 'enabled' => [ 'app1' => '1.0.1', 'app2' => '2.4.1' ], 36 | 'disabled' => [ 'dapp1' => '0.0.1', 'dapp2' => '5.1.1' ] 37 | ], 38 | [ 'app1', 'app2', 'dapp1', 'dapp2' ] 39 | ] 40 | ]; 41 | } 42 | 43 | /** 44 | * @dataProvider appListProvider 45 | */ 46 | public function testGetAllApps($apps, $expected) { 47 | $encoded = \json_encode($apps); 48 | $appManager = new AppManager($this->getOccRunnerMock($encoded)); 49 | $actual = $appManager->getShippedApps(); 50 | $this->assertSame($expected, $actual); 51 | } 52 | 53 | /** 54 | * @dataProvider appListProvider 55 | */ 56 | public function testGetShippedApps($apps, $expected) { 57 | $encoded = \json_encode($apps); 58 | $appManager = new AppManager($this->getOccRunnerMock($encoded)); 59 | $actual = $appManager->getShippedApps(); 60 | $this->assertSame($expected, $actual); 61 | } 62 | 63 | public function testGetAppPath() { 64 | $expected = '/dev/null'; 65 | $appId = 'anyapp'; 66 | $appManager = new AppManager($this->getOccRunnerMock($expected)); 67 | $actual = $appManager->getAppPath($appId); 68 | $this->assertSame($expected, $actual); 69 | } 70 | 71 | /** 72 | * @param $result 73 | * @return mixed 74 | */ 75 | protected function getOccRunnerMock($result) { 76 | $runnerMock = $this->getMockBuilder('Owncloud\Updater\Utils\OccRunner') 77 | ->setMethods(['run']) 78 | ->disableOriginalConstructor() 79 | ->getMock() 80 | ; 81 | $runnerMock 82 | ->expects($this->any()) 83 | ->method('run') 84 | ->willReturn($result) 85 | ; 86 | return $runnerMock; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/unit/Utils/CheckpointTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('Owncloud\Updater\Utils\FilesystemHelper') 18 | ->disableOriginalConstructor() 19 | ->getMock() 20 | ; 21 | $fsHelper->method('scandir') 22 | ->willReturn($checkpointList) 23 | ; 24 | $fsHelper->method('isDir') 25 | ->willReturn(true) 26 | ; 27 | $fsHelper->method('filemtime') 28 | ->willReturn(199) 29 | ; 30 | $locator = $this->getMockBuilder('Owncloud\Updater\Utils\Locator') 31 | ->disableOriginalConstructor() 32 | ->getMock() 33 | ; 34 | $checkpointMock = $this->getCheckpointInstance($locator, $fsHelper); 35 | $actual = $checkpointMock->getAll(); 36 | $this->assertSame( 37 | [ 38 | [ 'title'=>'a', 'date' => 'January 01 1970 00:03'], 39 | [ 'title'=>'b', 'date' => 'January 01 1970 00:03'], 40 | [ 'title'=>'c', 'date' => 'January 01 1970 00:03'], 41 | ], 42 | $actual 43 | ); 44 | } 45 | 46 | public function testGetAllWithNotExistingFolder() { 47 | $checkpointList = ['a', 'b', 'c']; 48 | $fsHelper = $this->getMockBuilder('Owncloud\Updater\Utils\FilesystemHelper') 49 | ->disableOriginalConstructor() 50 | ->getMock() 51 | ; 52 | $fsHelper->method('scandir') 53 | ->willReturn($checkpointList) 54 | ; 55 | $fsHelper->method('isDir') 56 | ->willReturn(false) 57 | ; 58 | $locator = $this->getMockBuilder('Owncloud\Updater\Utils\Locator') 59 | ->disableOriginalConstructor() 60 | ->getMock() 61 | ; 62 | $checkpointMock = $this->getCheckpointInstance($locator, $fsHelper); 63 | $actual = $checkpointMock->getAll(); 64 | $this->assertSame([], $actual); 65 | } 66 | 67 | /** 68 | * @param Locator $locator 69 | * @param FilesystemHelper $fsHelper 70 | * @return Checkpoint 71 | */ 72 | protected function getCheckpointInstance(Locator $locator, FilesystemHelper $fsHelper) { 73 | return new Checkpoint($locator, $fsHelper); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/unit/Utils/ConfigReaderTest.php: -------------------------------------------------------------------------------- 1 | [ 15 | "instanceid" => "oc8v9kkjo6bh", 16 | "ldapIgnoreNamingRules" => false, 17 | ], 18 | "apps" => [ 19 | "backgroundjob" => [ 20 | "lastjob" => "3" 21 | ], 22 | "core" => [ 23 | "installedat" => "1423763974.698", 24 | "lastupdatedat" => "1450277990", 25 | "lastcron" => "1444753126", 26 | "OC_Channel" => "beta", 27 | ], 28 | "dav" => [ 29 | "installed_version" => "0.1.3", 30 | "types" => "filesystem", 31 | "enabled" => "yes" 32 | ] 33 | ] 34 | ]; 35 | 36 | /** 37 | * @return array 38 | */ 39 | public function getByPathProvider() { 40 | return [ 41 | [ 'apps.core.OC_Channel', 'beta'] 42 | ]; 43 | } 44 | 45 | /** 46 | * @dataProvider getByPathProvider 47 | */ 48 | public function testGetByPath($key, $expected) { 49 | $occRunnerMock = $this->getOccRunnerMock(\json_encode($this->config)); 50 | $configReader = new ConfigReader($occRunnerMock); 51 | $configReader->init(); 52 | $value = $configReader->getByPath($key); 53 | $this->assertSame($expected, $value); 54 | } 55 | 56 | /** 57 | * @param $result 58 | * @return mixed 59 | */ 60 | protected function getOccRunnerMock($result) { 61 | $runnerMock = $this->getMockBuilder('Owncloud\Updater\Utils\OccRunner') 62 | ->setMethods(['run']) 63 | ->disableOriginalConstructor() 64 | ->getMock() 65 | ; 66 | $runnerMock 67 | ->expects($this->any()) 68 | ->method('run') 69 | ->willReturn($result) 70 | ; 71 | return $runnerMock; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/unit/Utils/DocLinkTest.php: -------------------------------------------------------------------------------- 1 | assertSame($expected, $docLink->getAdminManualUrl($relativePart)); 23 | } 24 | 25 | /** 26 | * @return array 27 | */ 28 | public function versionDataProvider() { 29 | return [ 30 | [ '1.2.3.4', 'https://doc.owncloud.com/server/1.2/admin_manual/' ], 31 | [ '41.421.31.4.7.5.5', 'https://doc.owncloud.com/server/41.421/admin_manual/' ], 32 | [ '42.24', 'https://doc.owncloud.com/server/42.24/admin_manual/' ], 33 | ]; 34 | } 35 | 36 | /** 37 | * @dataProvider versionDataProvider 38 | * @param string $version 39 | * @param string $expected 40 | */ 41 | public function testTrimVersion($version, $expected) { 42 | $docLink = new DocLink($version); 43 | $trimmedVersion = $docLink->getAdminManualUrl(''); 44 | $this->assertSame($expected, $trimmedVersion); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/unit/Utils/FeedTest.php: -------------------------------------------------------------------------------- 1 | '123' ], false ], 20 | [ [ 'url'=>'123', 'version' => '123' ], false ], 21 | [ [ 'url'=>'123', 'version' => '123', 'versionstring' => '123' ], true ], 22 | ]; 23 | } 24 | 25 | /** 26 | * @dataProvider resultProvider 27 | */ 28 | public function testValidity($feedData, $expectedValidity) { 29 | $feed = new Feed($feedData); 30 | $this->assertSame($expectedValidity, $feed->isValid()); 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function feedFileNameProvider() { 37 | return [ 38 | [ [ 'url'=>'http://example.org/package.zip', 'version' => '1.2.3', 'versionstring' => '1.2.3' ], '1.2.3.zip' ], 39 | [ [ 'url'=>'https://download.owncloud.org/community/owncloud-daily-master.tar.bz2', 'version' => '1.2.3', 'versionstring' => '1.2.3' ], '1.2.3.tar.bz2' ], 40 | ]; 41 | } 42 | 43 | /** 44 | * @dataProvider feedFileNameProvider 45 | */ 46 | public function testGetDowngetDownloadedFileName($feedData, $filename) { 47 | $feed = new Feed($feedData); 48 | $this->assertSame($filename, $feed->getDownloadedFileName()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/unit/Utils/FetcherTest.php: -------------------------------------------------------------------------------- 1 | httpClient = $this->createMock(Client::class); 25 | $this->locator = $this->createMock(Locator::class); 26 | $this->configReader = $this->createMock(ConfigReader::class); 27 | 28 | $map = [ 29 | ['apps.core.installedat', '100500'], 30 | ['apps.core.lastupdatedat', '500100'], 31 | ['apps.core.OC_Channel', 'stable'], 32 | ['system.version', '8.2.0.3'], 33 | ]; 34 | 35 | $this->configReader 36 | ->method('getByPath') 37 | ->will($this->returnValueMap($map)) 38 | ; 39 | $this->configReader 40 | ->method('getEdition') 41 | ->willReturn('') 42 | ; 43 | $this->locator 44 | ->method('getBuild') 45 | ->willReturn('2015-03-09T13:29:12+00:00 8db687a1cddd13c2a6fb6b16038d20275bd31e17') 46 | ; 47 | } 48 | 49 | public function testGetValidFeed() { 50 | $responseMock = $this->getResponseMock(' 8.1.3.0ownCloud 8.1.3 51 | https://download.owncloud.org/community/owncloud-8.1.3.zip 52 | https://doc.owncloud.com/server/next/admin_manual/maintenance/upgrading/upgrade.html 53 | '); 54 | $this->httpClient 55 | ->method('request') 56 | ->willReturn($responseMock) 57 | ; 58 | $fetcher = new Fetcher($this->httpClient, $this->locator, $this->configReader); 59 | $feed = $fetcher->getFeed(); 60 | $this->assertInstanceOf('Owncloud\Updater\Utils\Feed', $feed); 61 | $this->assertTrue($feed->isValid()); 62 | $this->assertSame('8.1.3.0', $feed->getVersion()); 63 | } 64 | 65 | public function testGetEmptyFeed() { 66 | $responseMock = $this->getResponseMock(''); 67 | $this->httpClient 68 | ->method('request') 69 | ->willReturn($responseMock) 70 | ; 71 | $fetcher = new Fetcher($this->httpClient, $this->locator, $this->configReader); 72 | $feed = $fetcher->getFeed(); 73 | $this->assertInstanceOf('Owncloud\Updater\Utils\Feed', $feed); 74 | $this->assertFalse($feed->isValid()); 75 | } 76 | 77 | public function testGetGarbageFeed() { 78 | $responseMock = $this->getResponseMock(' '); 79 | $this->httpClient 80 | ->method('request') 81 | ->willReturn($responseMock) 82 | ; 83 | $fetcher = new Fetcher($this->httpClient, $this->locator, $this->configReader); 84 | $feed = $fetcher->getFeed(); 85 | $this->assertInstanceOf('Owncloud\Updater\Utils\Feed', $feed); 86 | $this->assertFalse($feed->isValid()); 87 | } 88 | 89 | /** 90 | * @param $body 91 | * @return mixed 92 | */ 93 | private function getResponseMock($body) { 94 | $bodyMock = $this->getMockBuilder(StreamInterface::class) 95 | ->disableOriginalConstructor() 96 | ->getMock() 97 | ; 98 | $bodyMock 99 | ->expects($this->any()) 100 | ->method('getContents') 101 | ->willReturn($body) 102 | ; 103 | 104 | $responseMock = $this->getMockBuilder(ResponseInterface::class) 105 | ->disableOriginalConstructor() 106 | ->getMock() 107 | ; 108 | $responseMock 109 | ->expects($this->any()) 110 | ->method('getStatusCode') 111 | ->willReturn(200) 112 | ; 113 | $responseMock 114 | ->expects($this->any()) 115 | ->method('getBody') 116 | ->willReturn($bodyMock) 117 | ; 118 | return $responseMock; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/unit/Utils/OccRunnerTest.php: -------------------------------------------------------------------------------- 1 | 5 | * 6 | * @copyright Copyright (c) 2015, ownCloud, Inc. 7 | * @license AGPL-3.0 8 | * 9 | * This code is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Affero General Public License, version 3, 11 | * as published by the Free Software Foundation. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License, version 3, 19 | * along with this program. If not, see 20 | * 21 | */ 22 | 23 | namespace Owncloud\Updater\Tests\Utils; 24 | 25 | /** 26 | * Class OccRunnerTest 27 | * 28 | * @package Owncloud\Updater\Tests\Utils 29 | */ 30 | class OccRunnerTest extends \PHPUnit\Framework\TestCase { 31 | /** 32 | */ 33 | public function testInvalidJson() { 34 | $this->expectException(\UnexpectedValueException::class); 35 | 36 | $occRunner = $this->getMockBuilder('Owncloud\Updater\Utils\OccRunner') 37 | ->setConstructorArgs([ 38 | $this->getLocatorMock(), 39 | true 40 | ]) 41 | ->setMethods(['runAsRequest', 'runAsProcess']) 42 | ->getMock(); 43 | ; 44 | $occRunner->method('runAsRequest') 45 | ->willReturn('not-a-json') 46 | ; 47 | $occRunner->runJson('status'); 48 | } 49 | 50 | public function testValidJson() { 51 | $occRunner = $this->getMockBuilder('Owncloud\Updater\Utils\OccRunner') 52 | ->setConstructorArgs([ 53 | $this->getLocatorMock(), 54 | false 55 | ]) 56 | ->setMethods(['runAsRequest', 'runAsProcess']) 57 | ->getMock(); 58 | ; 59 | $occRunner->method('runAsRequest') 60 | ->willReturn('{"exitCode":0,"response":"{\"installed\":true,\"version\":\"9.1.0.10\",\"versionstring\":\"9.1.0 beta 2\",\"edition\":\"\"}\n"}') 61 | ; 62 | $result = $occRunner->runJson('status'); 63 | $this->assertSame( 64 | [ 65 | 'installed' => true, 66 | 'version' => "9.1.0.10", 67 | 'versionstring' => "9.1.0 beta 2", 68 | 'edition' => "" 69 | ], 70 | $result 71 | ); 72 | } 73 | 74 | /** 75 | * @return mixed 76 | */ 77 | private function getLocatorMock() { 78 | $locatorMock = $this->getMockBuilder('Owncloud\Updater\Utils\Locator') 79 | ->disableOriginalConstructor() 80 | ->getMock() 81 | ; 82 | return $locatorMock; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/unit/Utils/RegistryTest.php: -------------------------------------------------------------------------------- 1 | get('random_key'); 16 | $this->assertNull($value); 17 | } 18 | 19 | public function testGetExistingValue() { 20 | $data = ['someKey' => 'someValue' ]; 21 | $registry = new Registry(); 22 | $registry->set('key', $data); 23 | $value = $registry->get('key'); 24 | 25 | $this->assertSame($data, $value); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/unit/bootstrap.php: -------------------------------------------------------------------------------- 1 |