migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Michael Gapczynski <GapczynskiM@gmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Tom Needham <tom@owncloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use OCP\API;
use OCP\AppFramework\Http;
class OC_API {
/**
* api actions
*/
protected static $actions = [];
/**
* respond to a call
* @param \OC\OCS\Result $result
* @param string $format the format xml|json
* @psalm-taint-escape html
*/
public static function respond($result, $format = 'xml') {
$request = \OC::$server->getRequest();
// Send 401 headers if unauthorised
if ($result->getStatusCode() === \OCP\AppFramework\OCSController::RESPOND_UNAUTHORISED) {
// If request comes from JS return dummy auth request
if ($request->getHeader('X-Requested-With') === 'XMLHttpRequest') {
header('WWW-Authenticate: DummyBasic realm="Authorisation Required"');
} else {
header('WWW-Authenticate: Basic realm="Authorisation Required"');
}
http_response_code(401);
}
foreach ($result->getHeaders() as $name => $value) {
header($name . ': ' . $value);
}
$meta = $result->getMeta();
$data = $result->getData();
if (self::isV2($request)) {
$statusCode = self::mapStatusCodes($result->getStatusCode());
if (!is_null($statusCode)) {
$meta['statuscode'] = $statusCode;
http_response_code($statusCode);
}
}
self::setContentType($format);
$body = self::renderResult($format, $meta, $data);
echo $body;
}
/**
* @param XMLWriter $writer
*/
private static function toXML($array, $writer) {
foreach ($array as $k => $v) {
if ($k[0] === '@') {
$writer->writeAttribute(substr($k, 1), $v);
continue;
} elseif (is_numeric($k)) {
$k = 'element';
}
if (is_array($v)) {
$writer->startElement($k);
self::toXML($v, $writer);
$writer->endElement();
} else {
$writer->writeElement($k, $v);
}
}
}
public static function requestedFormat(): string {
$formats = ['json', 'xml'];
$format = (isset($_GET['format']) && is_string($_GET['format']) && in_array($_GET['format'], $formats)) ? $_GET['format'] : 'xml';
return $format;
}
/**
* Based on the requested format the response content type is set
* @param string $format
*/
public static function setContentType($format = null) {
$format = is_null($format) ? self::requestedFormat() : $format;
if ($format === 'xml') {
header('Content-type: text/xml; charset=UTF-8');
return;
}
if ($format === 'json') {
header('Content-Type: application/json; charset=utf-8');
return;
}
header('Content-Type: application/octet-stream; charset=utf-8');
}
/**
* @param \OCP\IRequest $request
* @return bool
*/
protected static function isV2(\OCP\IRequest $request) {
$script = $request->getScriptName();
return str_ends_with($script, '/ocs/v2.php');
}
/**
* @param integer $sc
* @return int
*/
public static function mapStatusCodes($sc) {
switch ($sc) {
case \OCP\AppFramework\OCSController::RESPOND_NOT_FOUND:
return Http::STATUS_NOT_FOUND;
case \OCP\AppFramework\OCSController::RESPOND_SERVER_ERROR:
return Http::STATUS_INTERNAL_SERVER_ERROR;
case \OCP\AppFramework\OCSController::RESPOND_UNKNOWN_ERROR:
return Http::STATUS_INTERNAL_SERVER_ERROR;
case \OCP\AppFramework\OCSController::RESPOND_UNAUTHORISED:
// already handled for v1
return null;
case 100:
return Http::STATUS_OK;
}
// any 2xx, 4xx and 5xx will be used as is
if ($sc >= 200 && $sc < 600) {
return $sc;
}
return Http::STATUS_BAD_REQUEST;
}
/**
* @param string $format
* @return string
*/
public static function renderResult($format, $meta, $data) {
$response = [
'ocs' => [
'meta' => $meta,
'data' => $data,
],
];
if ($format == 'json') {
return OC_JSON::encode($response);
}
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->startDocument();
self::toXML($response, $writer);
$writer->endDocument();
return $writer->outputMemory(true);
}
}
+974
View File
@@ -0,0 +1,974 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bart Visscher <bartv@thisnet.nl>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Borjan Tchakaloff <borjan@tchakaloff.fr>
* @author Brice Maron <brice@bmaron.net>
* @author Christopher Schäpers <kondou@ts.unde.re>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Rudolf <github.com@daniel-rudolf.de>
* @author Frank Karlitschek <frank@karlitschek.de>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Jakob Sack <mail@jakobsack.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Julius Haertl <jus@bitgrid.net>
* @author Julius Härtl <jus@bitgrid.net>
* @author Kamil Domanski <kdomanski@kdemail.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Markus Goetz <markus@woboq.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author RealRancor <Fisch.666@gmx.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Sam Tuke <mail@samtuke.com>
* @author Sebastian Wessalowski <sebastian@wessalowski.org>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Thomas Tanghus <thomas@tanghus.net>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use OC\App\DependencyAnalyzer;
use OC\App\Platform;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\DB\MigrationService;
use OC\Installer;
use OC\Repair;
use OC\Repair\Events\RepairErrorEvent;
use OCP\App\Events\AppUpdateEvent;
use OCP\App\IAppManager;
use OCP\App\ManagerEvent;
use OCP\Authentication\IAlternativeLogin;
use OCP\EventDispatcher\IEventDispatcher;
use Psr\Container\ContainerExceptionInterface;
use Psr\Log\LoggerInterface;
/**
* This class manages the apps. It allows them to register and integrate in the
* ownCloud ecosystem. Furthermore, this class is responsible for installing,
* upgrading and removing apps.
*/
class OC_App {
private static $adminForms = [];
private static $personalForms = [];
private static $altLogin = [];
private static $alreadyRegistered = [];
public const supportedApp = 300;
public const officialApp = 200;
/**
* clean the appId
*
* @psalm-taint-escape file
* @psalm-taint-escape include
* @psalm-taint-escape html
* @psalm-taint-escape has_quotes
*
* @param string $app AppId that needs to be cleaned
* @return string
*/
public static function cleanAppId(string $app): string {
return str_replace(['<', '>', '"', "'", '\0', '/', '\\', '..'], '', $app);
}
/**
* Check if an app is loaded
*
* @param string $app
* @return bool
* @deprecated 27.0.0 use IAppManager::isAppLoaded
*/
public static function isAppLoaded(string $app): bool {
return \OC::$server->get(IAppManager::class)->isAppLoaded($app);
}
/**
* loads all apps
*
* @param string[] $types
* @return bool
*
* This function walks through the ownCloud directory and loads all apps
* it can find. A directory contains an app if the file /appinfo/info.xml
* exists.
*
* if $types is set to non-empty array, only apps of those types will be loaded
*/
public static function loadApps(array $types = []): bool {
if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
// This should be done before calling this method so that appmanager can be used
return false;
}
return \OC::$server->get(IAppManager::class)->loadApps($types);
}
/**
* load a single app
*
* @param string $app
* @throws Exception
* @deprecated 27.0.0 use IAppManager::loadApp
*/
public static function loadApp(string $app): void {
\OC::$server->get(IAppManager::class)->loadApp($app);
}
/**
* @internal
* @param string $app
* @param string $path
* @param bool $force
*/
public static function registerAutoloading(string $app, string $path, bool $force = false) {
$key = $app . '-' . $path;
if (!$force && isset(self::$alreadyRegistered[$key])) {
return;
}
self::$alreadyRegistered[$key] = true;
// Register on PSR-4 composer autoloader
$appNamespace = \OC\AppFramework\App::buildAppNamespace($app);
\OC::$server->registerNamespace($app, $appNamespace);
if (file_exists($path . '/composer/autoload.php')) {
require_once $path . '/composer/autoload.php';
} else {
\OC::$composerAutoloader->addPsr4($appNamespace . '\\', $path . '/lib/', true);
}
// Register Test namespace only when testing
if (defined('PHPUNIT_RUN') || defined('CLI_TEST_RUN')) {
\OC::$composerAutoloader->addPsr4($appNamespace . '\\Tests\\', $path . '/tests/', true);
}
}
/**
* check if an app is of a specific type
*
* @param string $app
* @param array $types
* @return bool
* @deprecated 27.0.0 use IAppManager::isType
*/
public static function isType(string $app, array $types): bool {
return \OC::$server->get(IAppManager::class)->isType($app, $types);
}
/**
* read app types from info.xml and cache them in the database
*/
public static function setAppTypes(string $app) {
$appManager = \OC::$server->getAppManager();
$appData = $appManager->getAppInfo($app);
if (!is_array($appData)) {
return;
}
if (isset($appData['types'])) {
$appTypes = implode(',', $appData['types']);
} else {
$appTypes = '';
$appData['types'] = [];
}
$config = \OC::$server->getConfig();
$config->setAppValue($app, 'types', $appTypes);
if ($appManager->hasProtectedAppType($appData['types'])) {
$enabled = $config->getAppValue($app, 'enabled', 'yes');
if ($enabled !== 'yes' && $enabled !== 'no') {
$config->setAppValue($app, 'enabled', 'yes');
}
}
}
/**
* Returns apps enabled for the current user.
*
* @param bool $forceRefresh whether to refresh the cache
* @param bool $all whether to return apps for all users, not only the
* currently logged in one
* @return string[]
*/
public static function getEnabledApps(bool $forceRefresh = false, bool $all = false): array {
if (!\OC::$server->getSystemConfig()->getValue('installed', false)) {
return [];
}
// in incognito mode or when logged out, $user will be false,
// which is also the case during an upgrade
$appManager = \OC::$server->getAppManager();
if ($all) {
$user = null;
} else {
$user = \OC::$server->getUserSession()->getUser();
}
if (is_null($user)) {
$apps = $appManager->getInstalledApps();
} else {
$apps = $appManager->getEnabledAppsForUser($user);
}
$apps = array_filter($apps, function ($app) {
return $app !== 'files';//we add this manually
});
sort($apps);
array_unshift($apps, 'files');
return $apps;
}
/**
* enables an app
*
* @param string $appId
* @param array $groups (optional) when set, only these groups will have access to the app
* @throws \Exception
* @return void
*
* This function set an app as enabled in appconfig.
*/
public function enable(string $appId,
array $groups = []) {
// Check if app is already downloaded
/** @var Installer $installer */
$installer = \OCP\Server::get(Installer::class);
$isDownloaded = $installer->isDownloaded($appId);
if (!$isDownloaded) {
$installer->downloadApp($appId);
}
$installer->installApp($appId);
$appManager = \OC::$server->getAppManager();
if ($groups !== []) {
$groupManager = \OC::$server->getGroupManager();
$groupsList = [];
foreach ($groups as $group) {
$groupItem = $groupManager->get($group);
if ($groupItem instanceof \OCP\IGroup) {
$groupsList[] = $groupManager->get($group);
}
}
$appManager->enableAppForGroups($appId, $groupsList);
} else {
$appManager->enableApp($appId);
}
}
/**
* Get the path where to install apps
*
* @return string|false
*/
public static function getInstallPath() {
foreach (OC::$APPSROOTS as $dir) {
if (isset($dir['writable']) && $dir['writable'] === true) {
return $dir['path'];
}
}
\OCP\Server::get(LoggerInterface::class)->error('No application directories are marked as writable.', ['app' => 'core']);
return null;
}
/**
* search for an app in all app-directories
*
* @param string $appId
* @param bool $ignoreCache ignore cache and rebuild it
* @return false|string
*/
public static function findAppInDirectories(string $appId, bool $ignoreCache = false) {
$sanitizedAppId = self::cleanAppId($appId);
if ($sanitizedAppId !== $appId) {
return false;
}
static $app_dir = [];
if (isset($app_dir[$appId]) && !$ignoreCache) {
return $app_dir[$appId];
}
$possibleApps = [];
foreach (OC::$APPSROOTS as $dir) {
if (file_exists($dir['path'] . '/' . $appId)) {
$possibleApps[] = $dir;
}
}
if (empty($possibleApps)) {
return false;
} elseif (count($possibleApps) === 1) {
$dir = array_shift($possibleApps);
$app_dir[$appId] = $dir;
return $dir;
} else {
$versionToLoad = [];
foreach ($possibleApps as $possibleApp) {
$version = self::getAppVersionByPath($possibleApp['path'] . '/' . $appId);
if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
$versionToLoad = [
'dir' => $possibleApp,
'version' => $version,
];
}
}
$app_dir[$appId] = $versionToLoad['dir'];
return $versionToLoad['dir'];
//TODO - write test
}
}
/**
* Get the directory for the given app.
* If the app is defined in multiple directories, the first one is taken. (false if not found)
*
* @psalm-taint-specialize
*
* @param string $appId
* @param bool $refreshAppPath should be set to true only during install/upgrade
* @return string|false
* @deprecated 11.0.0 use \OC::$server->getAppManager()->getAppPath()
*/
public static function getAppPath(string $appId, bool $refreshAppPath = false) {
if ($appId === null || trim($appId) === '') {
return false;
}
if (($dir = self::findAppInDirectories($appId, $refreshAppPath)) != false) {
return $dir['path'] . '/' . $appId;
}
return false;
}
/**
* Get the path for the given app on the access
* If the app is defined in multiple directories, the first one is taken. (false if not found)
*
* @param string $appId
* @return string|false
* @deprecated 18.0.0 use \OC::$server->getAppManager()->getAppWebPath()
*/
public static function getAppWebPath(string $appId) {
if (($dir = self::findAppInDirectories($appId)) != false) {
return OC::$WEBROOT . $dir['url'] . '/' . $appId;
}
return false;
}
/**
* get app's version based on it's path
*
* @param string $path
* @return string
*/
public static function getAppVersionByPath(string $path): string {
$infoFile = $path . '/appinfo/info.xml';
$appData = \OC::$server->getAppManager()->getAppInfo($infoFile, true);
return $appData['version'] ?? '';
}
/**
* get the id of loaded app
*
* @return string
*/
public static function getCurrentApp(): string {
if (\OC::$CLI) {
return '';
}
$request = \OC::$server->getRequest();
$script = substr($request->getScriptName(), strlen(OC::$WEBROOT) + 1);
$topFolder = substr($script, 0, strpos($script, '/') ?: 0);
if (empty($topFolder)) {
try {
$path_info = $request->getPathInfo();
} catch (Exception $e) {
// Can happen from unit tests because the script name is `./vendor/bin/phpunit` or something a like then.
\OC::$server->get(LoggerInterface::class)->error('Failed to detect current app from script path', ['exception' => $e]);
return '';
}
if ($path_info) {
$topFolder = substr($path_info, 1, strpos($path_info, '/', 1) - 1);
}
}
if ($topFolder == 'apps') {
$length = strlen($topFolder);
return substr($script, $length + 1, strpos($script, '/', $length + 1) - $length - 1) ?: '';
} else {
return $topFolder;
}
}
/**
* @param string $type
* @return array
*/
public static function getForms(string $type): array {
$forms = [];
switch ($type) {
case 'admin':
$source = self::$adminForms;
break;
case 'personal':
$source = self::$personalForms;
break;
default:
return [];
}
foreach ($source as $form) {
$forms[] = include $form;
}
return $forms;
}
/**
* @param array $entry
* @deprecated 20.0.0 Please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface
*/
public static function registerLogIn(array $entry) {
\OC::$server->getLogger()->debug('OC_App::registerLogIn() is deprecated, please register your alternative login option using the registerAlternativeLogin() on the RegistrationContext in your Application class implementing the OCP\Authentication\IAlternativeLogin interface');
self::$altLogin[] = $entry;
}
/**
* @return array
*/
public static function getAlternativeLogIns(): array {
/** @var Coordinator $bootstrapCoordinator */
$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
foreach ($bootstrapCoordinator->getRegistrationContext()->getAlternativeLogins() as $registration) {
if (!in_array(IAlternativeLogin::class, class_implements($registration->getService()), true)) {
\OC::$server->getLogger()->error('Alternative login option {option} does not implement {interface} and is therefore ignored.', [
'option' => $registration->getService(),
'interface' => IAlternativeLogin::class,
'app' => $registration->getAppId(),
]);
continue;
}
try {
/** @var IAlternativeLogin $provider */
$provider = \OCP\Server::get($registration->getService());
} catch (ContainerExceptionInterface $e) {
\OC::$server->getLogger()->logException($e, [
'message' => 'Alternative login option {option} can not be initialised.',
'option' => $registration->getService(),
'app' => $registration->getAppId(),
]);
}
try {
$provider->load();
self::$altLogin[] = [
'name' => $provider->getLabel(),
'href' => $provider->getLink(),
'class' => $provider->getClass(),
];
} catch (Throwable $e) {
\OC::$server->getLogger()->logException($e, [
'message' => 'Alternative login option {option} had an error while loading.',
'option' => $registration->getService(),
'app' => $registration->getAppId(),
]);
}
}
return self::$altLogin;
}
/**
* get a list of all apps in the apps folder
*
* @return string[] an array of app names (string IDs)
* @todo: change the name of this method to getInstalledApps, which is more accurate
*/
public static function getAllApps(): array {
$apps = [];
foreach (OC::$APPSROOTS as $apps_dir) {
if (!is_readable($apps_dir['path'])) {
\OCP\Server::get(LoggerInterface::class)->warning('unable to read app folder : ' . $apps_dir['path'], ['app' => 'core']);
continue;
}
$dh = opendir($apps_dir['path']);
if (is_resource($dh)) {
while (($file = readdir($dh)) !== false) {
if ($file[0] != '.' and is_dir($apps_dir['path'] . '/' . $file) and is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')) {
$apps[] = $file;
}
}
}
}
$apps = array_unique($apps);
return $apps;
}
/**
* List all supported apps
*
* @return array
*/
public function getSupportedApps(): array {
/** @var \OCP\Support\Subscription\IRegistry $subscriptionRegistry */
$subscriptionRegistry = \OCP\Server::get(\OCP\Support\Subscription\IRegistry::class);
$supportedApps = $subscriptionRegistry->delegateGetSupportedApps();
return $supportedApps;
}
/**
* List all apps, this is used in apps.php
*
* @return array
*/
public function listAllApps(): array {
$installedApps = OC_App::getAllApps();
$appManager = \OC::$server->getAppManager();
//we don't want to show configuration for these
$blacklist = $appManager->getAlwaysEnabledApps();
$appList = [];
$langCode = \OC::$server->getL10N('core')->getLanguageCode();
$urlGenerator = \OC::$server->getURLGenerator();
$supportedApps = $this->getSupportedApps();
foreach ($installedApps as $app) {
if (!in_array($app, $blacklist)) {
$info = $appManager->getAppInfo($app, false, $langCode);
if (!is_array($info)) {
\OCP\Server::get(LoggerInterface::class)->error('Could not read app info file for app "' . $app . '"', ['app' => 'core']);
continue;
}
if (!isset($info['name'])) {
\OCP\Server::get(LoggerInterface::class)->error('App id "' . $app . '" has no name in appinfo', ['app' => 'core']);
continue;
}
$enabled = \OC::$server->getConfig()->getAppValue($app, 'enabled', 'no');
$info['groups'] = null;
if ($enabled === 'yes') {
$active = true;
} elseif ($enabled === 'no') {
$active = false;
} else {
$active = true;
$info['groups'] = $enabled;
}
$info['active'] = $active;
if ($appManager->isShipped($app)) {
$info['internal'] = true;
$info['level'] = self::officialApp;
$info['removable'] = false;
} else {
$info['internal'] = false;
$info['removable'] = true;
}
if (in_array($app, $supportedApps)) {
$info['level'] = self::supportedApp;
}
$appPath = self::getAppPath($app);
if ($appPath !== false) {
$appIcon = $appPath . '/img/' . $app . '.svg';
if (file_exists($appIcon)) {
$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
$info['previewAsIcon'] = true;
} else {
$appIcon = $appPath . '/img/app.svg';
if (file_exists($appIcon)) {
$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
$info['previewAsIcon'] = true;
}
}
}
// fix documentation
if (isset($info['documentation']) && is_array($info['documentation'])) {
foreach ($info['documentation'] as $key => $url) {
// If it is not an absolute URL we assume it is a key
// i.e. admin-ldap will get converted to go.php?to=admin-ldap
if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) {
$url = $urlGenerator->linkToDocs($url);
}
$info['documentation'][$key] = $url;
}
}
$info['version'] = $appManager->getAppVersion($app);
$appList[] = $info;
}
}
return $appList;
}
public static function shouldUpgrade(string $app): bool {
$versions = self::getAppVersions();
$currentVersion = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppVersion($app);
if ($currentVersion && isset($versions[$app])) {
$installedVersion = $versions[$app];
if (!version_compare($currentVersion, $installedVersion, '=')) {
return true;
}
}
return false;
}
/**
* Adjust the number of version parts of $version1 to match
* the number of version parts of $version2.
*
* @param string $version1 version to adjust
* @param string $version2 version to take the number of parts from
* @return string shortened $version1
*/
private static function adjustVersionParts(string $version1, string $version2): string {
$version1 = explode('.', $version1);
$version2 = explode('.', $version2);
// reduce $version1 to match the number of parts in $version2
while (count($version1) > count($version2)) {
array_pop($version1);
}
// if $version1 does not have enough parts, add some
while (count($version1) < count($version2)) {
$version1[] = '0';
}
return implode('.', $version1);
}
/**
* Check whether the current ownCloud version matches the given
* application's version requirements.
*
* The comparison is made based on the number of parts that the
* app info version has. For example for ownCloud 6.0.3 if the
* app info version is expecting version 6.0, the comparison is
* made on the first two parts of the ownCloud version.
* This means that it's possible to specify "requiremin" => 6
* and "requiremax" => 6 and it will still match ownCloud 6.0.3.
*
* @param string $ocVersion ownCloud version to check against
* @param array $appInfo app info (from xml)
*
* @return boolean true if compatible, otherwise false
*/
public static function isAppCompatible(string $ocVersion, array $appInfo, bool $ignoreMax = false): bool {
$requireMin = '';
$requireMax = '';
if (isset($appInfo['dependencies']['nextcloud']['@attributes']['min-version'])) {
$requireMin = $appInfo['dependencies']['nextcloud']['@attributes']['min-version'];
} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['min-version'])) {
$requireMin = $appInfo['dependencies']['owncloud']['@attributes']['min-version'];
} elseif (isset($appInfo['requiremin'])) {
$requireMin = $appInfo['requiremin'];
} elseif (isset($appInfo['require'])) {
$requireMin = $appInfo['require'];
}
if (isset($appInfo['dependencies']['nextcloud']['@attributes']['max-version'])) {
$requireMax = $appInfo['dependencies']['nextcloud']['@attributes']['max-version'];
} elseif (isset($appInfo['dependencies']['owncloud']['@attributes']['max-version'])) {
$requireMax = $appInfo['dependencies']['owncloud']['@attributes']['max-version'];
} elseif (isset($appInfo['requiremax'])) {
$requireMax = $appInfo['requiremax'];
}
if (!empty($requireMin)
&& version_compare(self::adjustVersionParts($ocVersion, $requireMin), $requireMin, '<')
) {
return false;
}
if (!$ignoreMax && !empty($requireMax)
&& version_compare(self::adjustVersionParts($ocVersion, $requireMax), $requireMax, '>')
) {
return false;
}
return true;
}
/**
* get the installed version of all apps
*/
public static function getAppVersions() {
static $versions;
if (!$versions) {
$appConfig = \OC::$server->getAppConfig();
$versions = $appConfig->getValues(false, 'installed_version');
}
return $versions;
}
/**
* update the database for the app and call the update script
*
* @param string $appId
* @return bool
*/
public static function updateApp(string $appId): bool {
// for apps distributed with core, we refresh app path in case the downloaded version
// have been installed in custom apps and not in the default path
$appPath = self::getAppPath($appId, true);
if ($appPath === false) {
return false;
}
if (is_file($appPath . '/appinfo/database.xml')) {
\OC::$server->getLogger()->error('The appinfo/database.xml file is not longer supported. Used in ' . $appId);
return false;
}
\OC::$server->getAppManager()->clearAppsCache();
$l = \OC::$server->getL10N('core');
$appData = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppInfo($appId, false, $l->getLanguageCode());
$ignoreMaxApps = \OC::$server->getConfig()->getSystemValue('app_install_overwrite', []);
$ignoreMax = in_array($appId, $ignoreMaxApps, true);
\OC_App::checkAppDependencies(
\OC::$server->getConfig(),
$l,
$appData,
$ignoreMax
);
self::registerAutoloading($appId, $appPath, true);
self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
$ms = new MigrationService($appId, \OC::$server->get(\OC\DB\Connection::class));
$ms->migrate();
self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
// update appversion in app manager
\OC::$server->getAppManager()->clearAppsCache();
\OC::$server->getAppManager()->getAppVersion($appId, false);
self::setupBackgroundJobs($appData['background-jobs']);
//set remote/public handlers
if (array_key_exists('ocsid', $appData)) {
\OC::$server->getConfig()->setAppValue($appId, 'ocsid', $appData['ocsid']);
} elseif (\OC::$server->getConfig()->getAppValue($appId, 'ocsid', null) !== null) {
\OC::$server->getConfig()->deleteAppValue($appId, 'ocsid');
}
foreach ($appData['remote'] as $name => $path) {
\OC::$server->getConfig()->setAppValue('core', 'remote_' . $name, $appId . '/' . $path);
}
foreach ($appData['public'] as $name => $path) {
\OC::$server->getConfig()->setAppValue('core', 'public_' . $name, $appId . '/' . $path);
}
self::setAppTypes($appId);
$version = \OCP\Server::get(\OCP\App\IAppManager::class)->getAppVersion($appId);
\OC::$server->getConfig()->setAppValue($appId, 'installed_version', $version);
\OC::$server->get(IEventDispatcher::class)->dispatchTyped(new AppUpdateEvent($appId));
\OC::$server->get(IEventDispatcher::class)->dispatch(ManagerEvent::EVENT_APP_UPDATE, new ManagerEvent(
ManagerEvent::EVENT_APP_UPDATE, $appId
));
return true;
}
/**
* @param string $appId
* @param string[] $steps
* @throws \OC\NeedsUpdateException
*/
public static function executeRepairSteps(string $appId, array $steps) {
if (empty($steps)) {
return;
}
// load the app
self::loadApp($appId);
$dispatcher = \OC::$server->get(IEventDispatcher::class);
// load the steps
$r = new Repair([], $dispatcher, \OC::$server->get(LoggerInterface::class));
foreach ($steps as $step) {
try {
$r->addStep($step);
} catch (Exception $ex) {
$dispatcher->dispatchTyped(new RepairErrorEvent($ex->getMessage()));
\OC::$server->getLogger()->logException($ex);
}
}
// run the steps
$r->run();
}
public static function setupBackgroundJobs(array $jobs) {
$queue = \OC::$server->getJobList();
foreach ($jobs as $job) {
$queue->add($job);
}
}
/**
* @param string $appId
* @param string[] $steps
*/
private static function setupLiveMigrations(string $appId, array $steps) {
$queue = \OC::$server->getJobList();
foreach ($steps as $step) {
$queue->add('OC\Migration\BackgroundRepair', [
'app' => $appId,
'step' => $step]);
}
}
/**
* @param string $appId
* @return \OC\Files\View|false
*/
public static function getStorage(string $appId) {
if (\OC::$server->getAppManager()->isEnabledForUser($appId)) { //sanity check
if (\OC::$server->getUserSession()->isLoggedIn()) {
$view = new \OC\Files\View('/' . OC_User::getUser());
if (!$view->file_exists($appId)) {
$view->mkdir($appId);
}
return new \OC\Files\View('/' . OC_User::getUser() . '/' . $appId);
} else {
\OCP\Server::get(LoggerInterface::class)->error('Can\'t get app storage, app ' . $appId . ', user not logged in', ['app' => 'core']);
return false;
}
} else {
\OCP\Server::get(LoggerInterface::class)->error('Can\'t get app storage, app ' . $appId . ' not enabled', ['app' => 'core']);
return false;
}
}
protected static function findBestL10NOption(array $options, string $lang): string {
// only a single option
if (isset($options['@value'])) {
return $options['@value'];
}
$fallback = $similarLangFallback = $englishFallback = false;
$lang = strtolower($lang);
$similarLang = $lang;
if (strpos($similarLang, '_')) {
// For "de_DE" we want to find "de" and the other way around
$similarLang = substr($lang, 0, strpos($lang, '_'));
}
foreach ($options as $option) {
if (is_array($option)) {
if ($fallback === false) {
$fallback = $option['@value'];
}
if (!isset($option['@attributes']['lang'])) {
continue;
}
$attributeLang = strtolower($option['@attributes']['lang']);
if ($attributeLang === $lang) {
return $option['@value'];
}
if ($attributeLang === $similarLang) {
$similarLangFallback = $option['@value'];
} elseif (str_starts_with($attributeLang, $similarLang . '_')) {
if ($similarLangFallback === false) {
$similarLangFallback = $option['@value'];
}
}
} else {
$englishFallback = $option;
}
}
if ($similarLangFallback !== false) {
return $similarLangFallback;
} elseif ($englishFallback !== false) {
return $englishFallback;
}
return (string) $fallback;
}
/**
* parses the app data array and enhanced the 'description' value
*
* @param array $data the app data
* @param string $lang
* @return array improved app data
*/
public static function parseAppInfo(array $data, $lang = null): array {
if ($lang && isset($data['name']) && is_array($data['name'])) {
$data['name'] = self::findBestL10NOption($data['name'], $lang);
}
if ($lang && isset($data['summary']) && is_array($data['summary'])) {
$data['summary'] = self::findBestL10NOption($data['summary'], $lang);
}
if ($lang && isset($data['description']) && is_array($data['description'])) {
$data['description'] = trim(self::findBestL10NOption($data['description'], $lang));
} elseif (isset($data['description']) && is_string($data['description'])) {
$data['description'] = trim($data['description']);
} else {
$data['description'] = '';
}
return $data;
}
/**
* @param \OCP\IConfig $config
* @param \OCP\IL10N $l
* @param array $info
* @throws \Exception
*/
public static function checkAppDependencies(\OCP\IConfig $config, \OCP\IL10N $l, array $info, bool $ignoreMax) {
$dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l);
$missing = $dependencyAnalyzer->analyze($info, $ignoreMax);
if (!empty($missing)) {
$missingMsg = implode(PHP_EOL, $missing);
throw new \Exception(
$l->t('App "%1$s" cannot be installed because the following dependencies are not fulfilled: %2$s',
[$info['name'], $missingMsg]
)
);
}
}
}
@@ -0,0 +1,348 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Julius Haertl <jus@bitgrid.net>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Markus Staab <markus.staab@redaxo.de>
* @author Michael Weimann <mail@michael-weimann.eu>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Pascal de Bruijn <pmjdebruijn@pcode.nl>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author scolebrook <scolebrook@mac.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Volkan Gezer <volkangezer@gmail.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
class OC_Defaults {
private $theme;
private $defaultEntity;
private $defaultName;
private $defaultTitle;
private $defaultBaseUrl;
private $defaultSyncClientUrl;
private $defaultiOSClientUrl;
private $defaultiTunesAppId;
private $defaultAndroidClientUrl;
private $defaultFDroidClientUrl;
private $defaultDocBaseUrl;
private $defaultDocVersion;
private $defaultSlogan;
private $defaultColorPrimary;
private $defaultTextColorPrimary;
private $defaultProductName;
public function __construct() {
$config = \OC::$server->getConfig();
$this->defaultEntity = 'Nextcloud'; /* e.g. company name, used for footers and copyright notices */
$this->defaultName = 'Nextcloud'; /* short name, used when referring to the software */
$this->defaultTitle = 'Nextcloud'; /* can be a longer name, for titles */
$this->defaultBaseUrl = 'https://nextcloud.com';
$this->defaultSyncClientUrl = $config->getSystemValue('customclient_desktop', 'https://nextcloud.com/install/#install-clients');
$this->defaultiOSClientUrl = $config->getSystemValue('customclient_ios', 'https://geo.itunes.apple.com/us/app/nextcloud/id1125420102?mt=8');
$this->defaultiTunesAppId = $config->getSystemValue('customclient_ios_appid', '1125420102');
$this->defaultAndroidClientUrl = $config->getSystemValue('customclient_android', 'https://play.google.com/store/apps/details?id=com.nextcloud.client');
$this->defaultFDroidClientUrl = $config->getSystemValue('customclient_fdroid', 'https://f-droid.org/packages/com.nextcloud.client/');
$this->defaultDocBaseUrl = 'https://docs.nextcloud.com';
$this->defaultDocVersion = \OC_Util::getVersion()[0]; // used to generate doc links
$this->defaultColorPrimary = '#0082c9';
$this->defaultTextColorPrimary = '#ffffff';
$this->defaultProductName = 'Nextcloud';
$themePath = OC::$SERVERROOT . '/themes/' . OC_Util::getTheme() . '/defaults.php';
if (file_exists($themePath)) {
// prevent defaults.php from printing output
ob_start();
require_once $themePath;
ob_end_clean();
if (class_exists('OC_Theme')) {
$this->theme = new OC_Theme();
}
}
}
/**
* @param string $method
*/
private function themeExist($method) {
if (isset($this->theme) && method_exists($this->theme, $method)) {
return true;
}
return false;
}
/**
* Returns the base URL
* @return string URL
*/
public function getBaseUrl() {
if ($this->themeExist('getBaseUrl')) {
return $this->theme->getBaseUrl();
} else {
return $this->defaultBaseUrl;
}
}
/**
* Returns the URL where the sync clients are listed
* @return string URL
*/
public function getSyncClientUrl() {
if ($this->themeExist('getSyncClientUrl')) {
return $this->theme->getSyncClientUrl();
} else {
return $this->defaultSyncClientUrl;
}
}
/**
* Returns the URL to the App Store for the iOS Client
* @return string URL
*/
public function getiOSClientUrl() {
if ($this->themeExist('getiOSClientUrl')) {
return $this->theme->getiOSClientUrl();
} else {
return $this->defaultiOSClientUrl;
}
}
/**
* Returns the AppId for the App Store for the iOS Client
* @return string AppId
*/
public function getiTunesAppId() {
if ($this->themeExist('getiTunesAppId')) {
return $this->theme->getiTunesAppId();
} else {
return $this->defaultiTunesAppId;
}
}
/**
* Returns the URL to Google Play for the Android Client
* @return string URL
*/
public function getAndroidClientUrl() {
if ($this->themeExist('getAndroidClientUrl')) {
return $this->theme->getAndroidClientUrl();
} else {
return $this->defaultAndroidClientUrl;
}
}
/**
* Returns the URL to Google Play for the Android Client
* @return string URL
*/
public function getFDroidClientUrl() {
if ($this->themeExist('getFDroidClientUrl')) {
return $this->theme->getFDroidClientUrl();
} else {
return $this->defaultFDroidClientUrl;
}
}
/**
* Returns the documentation URL
* @return string URL
*/
public function getDocBaseUrl() {
if ($this->themeExist('getDocBaseUrl')) {
return $this->theme->getDocBaseUrl();
} else {
return $this->defaultDocBaseUrl;
}
}
/**
* Returns the title
* @return string title
*/
public function getTitle() {
if ($this->themeExist('getTitle')) {
return $this->theme->getTitle();
} else {
return $this->defaultTitle;
}
}
/**
* Returns the short name of the software
* @return string title
*/
public function getName() {
if ($this->themeExist('getName')) {
return $this->theme->getName();
} else {
return $this->defaultName;
}
}
/**
* Returns the short name of the software containing HTML strings
* @return string title
*/
public function getHTMLName() {
if ($this->themeExist('getHTMLName')) {
return $this->theme->getHTMLName();
} else {
return $this->defaultName;
}
}
/**
* Returns entity (e.g. company name) - used for footer, copyright
* @return string entity name
*/
public function getEntity() {
if ($this->themeExist('getEntity')) {
return $this->theme->getEntity();
} else {
return $this->defaultEntity;
}
}
/**
* Returns slogan
* @return string slogan
*/
public function getSlogan(?string $lang = null) {
if ($this->themeExist('getSlogan')) {
return $this->theme->getSlogan($lang);
} else {
if ($this->defaultSlogan === null) {
$l10n = \OC::$server->getL10N('lib', $lang);
$this->defaultSlogan = $l10n->t('a safe home for all your data');
}
return $this->defaultSlogan;
}
}
/**
* Returns short version of the footer
* @return string short footer
*/
public function getShortFooter() {
if ($this->themeExist('getShortFooter')) {
$footer = $this->theme->getShortFooter();
} else {
$footer = '<a href="'. $this->getBaseUrl() . '" target="_blank"' .
' rel="noreferrer noopener">' .$this->getEntity() . '</a>'.
' – ' . $this->getSlogan();
}
return $footer;
}
/**
* Returns long version of the footer
* @return string long footer
*/
public function getLongFooter() {
if ($this->themeExist('getLongFooter')) {
$footer = $this->theme->getLongFooter();
} else {
$footer = $this->getShortFooter();
}
return $footer;
}
/**
* @param string $key
* @return string URL to doc with key
*/
public function buildDocLinkToKey($key) {
if ($this->themeExist('buildDocLinkToKey')) {
return $this->theme->buildDocLinkToKey($key);
}
return $this->getDocBaseUrl() . '/server/' . $this->defaultDocVersion . '/go.php?to=' . $key;
}
/**
* Returns primary color
* @return string
*/
public function getColorPrimary() {
if ($this->themeExist('getColorPrimary')) {
return $this->theme->getColorPrimary();
}
if ($this->themeExist('getMailHeaderColor')) {
return $this->theme->getMailHeaderColor();
}
return $this->defaultColorPrimary;
}
/**
* @return array scss variables to overwrite
*/
public function getScssVariables() {
if ($this->themeExist('getScssVariables')) {
return $this->theme->getScssVariables();
}
return [];
}
public function shouldReplaceIcons() {
return false;
}
/**
* Themed logo url
*
* @param bool $useSvg Whether to point to the SVG image or a fallback
* @return string
*/
public function getLogo($useSvg = true) {
if ($this->themeExist('getLogo')) {
return $this->theme->getLogo($useSvg);
}
if ($useSvg) {
$logo = \OC::$server->getURLGenerator()->imagePath('core', 'logo/logo.svg');
} else {
$logo = \OC::$server->getURLGenerator()->imagePath('core', 'logo/logo.png');
}
return $logo . '?v=' . hash('sha1', implode('.', \OCP\Util::getVersion()));
}
public function getTextColorPrimary() {
if ($this->themeExist('getTextColorPrimary')) {
return $this->theme->getTextColorPrimary();
}
return $this->defaultTextColorPrimary;
}
public function getProductName() {
if ($this->themeExist('getProductName')) {
return $this->theme->getProductName();
}
return $this->defaultProductName;
}
}
@@ -0,0 +1,134 @@
<?php
use OCP\IRequest;
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Christian Oliff <christianoliff@yahoo.com>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Felix Moeller <mail@felixmoeller.de>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
class OC_EventSource implements \OCP\IEventSource {
/**
* @var bool
*/
private $fallback;
/**
* @var int
*/
private $fallBackId = 0;
/**
* @var bool
*/
private $started = false;
private IRequest $request;
public function __construct(IRequest $request) {
$this->request = $request;
}
protected function init() {
if ($this->started) {
return;
}
$this->started = true;
// prevent php output buffering, caching and nginx buffering
OC_Util::obEnd();
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');
$this->fallback = isset($_GET['fallback']) and $_GET['fallback'] == 'true';
if ($this->fallback) {
$this->fallBackId = (int)$_GET['fallback_id'];
/**
* FIXME: The default content-security-policy of ownCloud forbids inline
* JavaScript for security reasons. IE starting on Windows 10 will
* however also obey the CSP which will break the event source fallback.
*
* As a workaround thus we set a custom policy which allows the execution
* of inline JavaScript.
*
* @link https://github.com/owncloud/core/issues/14286
*/
header("Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'");
header("Content-Type: text/html");
echo str_repeat('<span></span>' . PHP_EOL, 10); //dummy data to keep IE happy
} else {
header("Content-Type: text/event-stream");
}
if (!$this->request->passesStrictCookieCheck()) {
header('Location: '.\OC::$WEBROOT);
exit();
}
if (!$this->request->passesCSRFCheck()) {
$this->send('error', 'Possible CSRF attack. Connection will be closed.');
$this->close();
exit();
}
flush();
}
/**
* send a message to the client
*
* @param string $type
* @param mixed $data
*
* @throws \BadMethodCallException
* if only one parameter is given, a typeless message will be send with that parameter as data
* @suppress PhanDeprecatedFunction
*/
public function send($type, $data = null) {
if ($data and !preg_match('/^[A-Za-z0-9_]+$/', $type)) {
throw new BadMethodCallException('Type needs to be alphanumeric ('. $type .')');
}
$this->init();
if (is_null($data)) {
$data = $type;
$type = null;
}
if ($this->fallback) {
$response = '<script type="text/javascript">window.parent.OC.EventSource.fallBackCallBack('
. $this->fallBackId . ',"' . $type . '",' . OC_JSON::encode($data) . ')</script>' . PHP_EOL;
echo $response;
} else {
if ($type) {
echo 'event: ' . $type . PHP_EOL;
}
echo 'data: ' . OC_JSON::encode($data) . PHP_EOL;
}
echo PHP_EOL;
flush();
}
/**
* close the connection of the event source
*/
public function close() {
$this->send('__internal__', 'close'); //server side closing can be an issue, let the client do it
}
}
@@ -0,0 +1,184 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Felix Moeller <mail@felixmoeller.de>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Thomas Tanghus <thomas@tanghus.net>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
class OC_FileChunking {
protected $info;
protected $cache;
/**
* TTL of chunks
*
* @var int
*/
protected $ttl;
public static function decodeName($name) {
preg_match('/(?P<name>.*)-chunking-(?P<transferid>\d+)-(?P<chunkcount>\d+)-(?P<index>\d+)/', $name, $matches);
return $matches;
}
/**
* @param string[] $info
*/
public function __construct($info) {
$this->info = $info;
$this->ttl = \OC::$server->getConfig()->getSystemValueInt('cache_chunk_gc_ttl', 86400);
}
public function getPrefix() {
$name = $this->info['name'];
$transferid = $this->info['transferid'];
return $name.'-chunking-'.$transferid.'-';
}
protected function getCache() {
if (!isset($this->cache)) {
$this->cache = new \OC\Cache\File();
}
return $this->cache;
}
/**
* Stores the given $data under the given $key - the number of stored bytes is returned
*
* @param string $index
* @param resource $data
* @return int
*/
public function store($index, $data) {
$cache = $this->getCache();
$name = $this->getPrefix().$index;
$cache->set($name, $data, $this->ttl);
return $cache->size($name);
}
public function isComplete() {
$prefix = $this->getPrefix();
$cache = $this->getCache();
$chunkcount = (int)$this->info['chunkcount'];
for ($i = ($chunkcount - 1); $i >= 0; $i--) {
if (!$cache->hasKey($prefix.$i)) {
return false;
}
}
return true;
}
/**
* Assembles the chunks into the file specified by the path.
* Chunks are deleted afterwards.
*
* @param resource $f target path
*
* @return integer assembled file size
*
* @throws \OC\InsufficientStorageException when file could not be fully
* assembled due to lack of free space
*/
public function assemble($f) {
$cache = $this->getCache();
$prefix = $this->getPrefix();
$count = 0;
for ($i = 0; $i < $this->info['chunkcount']; $i++) {
$chunk = $cache->get($prefix.$i);
// remove after reading to directly save space
$cache->remove($prefix.$i);
$count += fwrite($f, $chunk);
// let php release the memory to work around memory exhausted error with php 5.6
$chunk = null;
}
return $count;
}
/**
* Returns the size of the chunks already present
* @return integer size in bytes
*/
public function getCurrentSize() {
$cache = $this->getCache();
$prefix = $this->getPrefix();
$total = 0;
for ($i = 0; $i < $this->info['chunkcount']; $i++) {
$total += $cache->size($prefix.$i);
}
return $total;
}
/**
* Removes all chunks which belong to this transmission
*/
public function cleanup() {
$cache = $this->getCache();
$prefix = $this->getPrefix();
for ($i = 0; $i < $this->info['chunkcount']; $i++) {
$cache->remove($prefix.$i);
}
}
/**
* Removes one specific chunk
* @param string $index
*/
public function remove($index) {
$cache = $this->getCache();
$prefix = $this->getPrefix();
$cache->remove($prefix.$index);
}
/**
* Assembles the chunks into the file specified by the path.
* Also triggers the relevant hooks and proxies.
*
* @param \OC\Files\Storage\Storage $storage storage
* @param string $path target path relative to the storage
* @return bool true on success or false if file could not be created
*
* @throws \OC\ServerNotAvailableException
*/
public function file_assemble($storage, $path) {
// use file_put_contents as method because that best matches what this function does
if (\OC\Files\Filesystem::isValidPath($path)) {
$target = $storage->fopen($path, 'w');
if ($target) {
$count = $this->assemble($target);
fclose($target);
return $count > 0;
} else {
return false;
}
}
return false;
}
}
@@ -0,0 +1,459 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Calviño Sánchez <danxuliu@gmail.com>
* @author Frank Karlitschek <frank@karlitschek.de>
* @author Jakob Sack <mail@jakobsack.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Julius Härtl <jus@bitgrid.net>
* @author Ko- <k.stoffelen@cs.ru.nl>
* @author Michael Gapczynski <GapczynskiM@gmail.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Nicolai Ehemann <en@enlightened.de>
* @author Piotr Filiciak <piotr@filiciak.pl>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thibaut GRIDEL <tgridel@free.fr>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Valdnet <47037905+Valdnet@users.noreply.github.com>
* @author Victor Dubiniuk <dubiniuk@owncloud.com>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Files\View;
use OC\Streamer;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
use OCP\Files\Events\BeforeZipCreatedEvent;
use OCP\Lock\ILockingProvider;
/**
* Class for file server access
*/
class OC_Files {
public const FILE = 1;
public const ZIP_FILES = 2;
public const ZIP_DIR = 3;
public const UPLOAD_MIN_LIMIT_BYTES = 1048576; // 1 MiB
private static string $multipartBoundary = '';
private static function getBoundary(): string {
if (empty(self::$multipartBoundary)) {
self::$multipartBoundary = md5((string)mt_rand());
}
return self::$multipartBoundary;
}
/**
* @param string $filename
* @param string $name
* @param array $rangeArray ('from'=>int,'to'=>int), ...
*/
private static function sendHeaders($filename, $name, array $rangeArray): void {
OC_Response::setContentDispositionHeader($name, 'attachment');
header('Content-Transfer-Encoding: binary', true);
header('Expires: 0');
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
$fileSize = \OC\Files\Filesystem::filesize($filename);
$type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename));
if ($fileSize > -1) {
if (!empty($rangeArray)) {
http_response_code(206);
header('Accept-Ranges: bytes', true);
if (count($rangeArray) > 1) {
$type = 'multipart/byteranges; boundary='.self::getBoundary();
// no Content-Length header here
} else {
header(sprintf('Content-Range: bytes %d-%d/%d', $rangeArray[0]['from'], $rangeArray[0]['to'], $fileSize), true);
OC_Response::setContentLengthHeader($rangeArray[0]['to'] - $rangeArray[0]['from'] + 1);
}
} else {
OC_Response::setContentLengthHeader($fileSize);
}
}
header('Content-Type: '.$type, true);
header('X-Accel-Buffering: no');
}
/**
* return the content of a file or return a zip file containing multiple files
*
* @param string $dir
* @param string $files ; separated list of files to download
* @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header
*/
public static function get($dir, $files, $params = null) {
OC_Util::setupFS();
$view = \OC\Files\Filesystem::getView();
$getType = self::FILE;
$filename = $dir;
try {
if (is_array($files) && count($files) === 1) {
$files = $files[0];
}
if (!is_array($files)) {
$filename = $dir . '/' . $files;
if (!$view->is_dir($filename)) {
self::getSingleFile($view, $dir, $files, is_null($params) ? [] : $params);
return;
}
}
$name = 'download';
if (is_array($files)) {
$getType = self::ZIP_FILES;
$basename = basename($dir);
if ($basename) {
$name = $basename;
}
$filename = $dir . '/' . $name;
} else {
$filename = $dir . '/' . $files;
$getType = self::ZIP_DIR;
// downloading root ?
if ($files !== '') {
$name = $files;
}
}
self::lockFiles($view, $dir, $files);
$numberOfFiles = 0;
$fileSize = 0;
/* Calculate filesize and number of files */
if ($getType === self::ZIP_FILES) {
$fileInfos = [];
foreach ($files as $file) {
$fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $file);
if ($fileInfo) {
$fileSize += $fileInfo->getSize();
$fileInfos[] = $fileInfo;
}
}
$numberOfFiles = self::getNumberOfFiles($fileInfos);
} elseif ($getType === self::ZIP_DIR) {
$fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $files);
if ($fileInfo) {
$fileSize = $fileInfo->getSize();
$numberOfFiles = self::getNumberOfFiles([$fileInfo]);
}
}
//Dispatch an event to see if any apps have problem with download
$event = new BeforeZipCreatedEvent($dir, is_array($files) ? $files : [$files]);
$dispatcher = \OCP\Server::get(IEventDispatcher::class);
$dispatcher->dispatchTyped($event);
if ((!$event->isSuccessful()) || $event->getErrorMessage() !== null) {
throw new \OC\ForbiddenException($event->getErrorMessage());
}
$streamer = new Streamer(\OC::$server->getRequest(), $fileSize, $numberOfFiles);
OC_Util::obEnd();
$streamer->sendHeaders($name);
$executionTime = (int)OC::$server->get(IniGetWrapper::class)->getNumeric('max_execution_time');
if (!str_contains(@ini_get('disable_functions'), 'set_time_limit')) {
@set_time_limit(0);
}
ignore_user_abort(true);
if ($getType === self::ZIP_FILES) {
foreach ($files as $file) {
$file = $dir . '/' . $file;
if (\OC\Files\Filesystem::is_file($file)) {
$userFolder = \OC::$server->getRootFolder()->get(\OC\Files\Filesystem::getRoot());
$file = $userFolder->get($file);
if ($file instanceof \OC\Files\Node\File) {
try {
$fh = $file->fopen('r');
} catch (\OCP\Files\NotPermittedException $e) {
continue;
}
$fileSize = $file->getSize();
$fileTime = $file->getMTime();
} else {
// File is not a file? …
\OC::$server->getLogger()->debug(
'File given, but no Node available. Name {file}',
[ 'app' => 'files', 'file' => $file ]
);
continue;
}
$streamer->addFileFromStream($fh, $file->getName(), $fileSize, $fileTime);
fclose($fh);
} elseif (\OC\Files\Filesystem::is_dir($file)) {
$streamer->addDirRecursive($file);
}
}
} elseif ($getType === self::ZIP_DIR) {
$file = $dir . '/' . $files;
$streamer->addDirRecursive($file);
}
$streamer->finalize();
set_time_limit($executionTime);
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
} catch (\OCP\Lock\LockedException $ex) {
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
OC::$server->getLogger()->logException($ex);
$l = \OC::$server->getL10N('lib');
$hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
\OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint, 200);
} catch (\OCP\Files\ForbiddenException $ex) {
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
OC::$server->getLogger()->logException($ex);
$l = \OC::$server->getL10N('lib');
\OC_Template::printErrorPage($l->t('Cannot download file'), $ex->getMessage(), 200);
} catch (\OCP\Files\ConnectionLostException $ex) {
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
OC::$server->getLogger()->logException($ex, ['level' => \OCP\ILogger::DEBUG]);
\OC_Template::printErrorPage('Connection lost', $ex->getMessage(), 200);
} catch (\Exception $ex) {
self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
OC::$server->getLogger()->logException($ex);
$l = \OC::$server->getL10N('lib');
$hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
if ($event && $event->getErrorMessage() !== null) {
$hint .= ' ' . $event->getErrorMessage();
}
\OC_Template::printErrorPage($l->t('Cannot download file'), $hint, 200);
}
}
/**
* @param string $rangeHeaderPos
* @param int|float $fileSize
* @return array $rangeArray ('from'=>int,'to'=>int), ...
*/
private static function parseHttpRangeHeader($rangeHeaderPos, $fileSize): array {
$rArray = explode(',', $rangeHeaderPos);
$minOffset = 0;
$ind = 0;
$rangeArray = [];
foreach ($rArray as $value) {
$ranges = explode('-', $value);
if (is_numeric($ranges[0])) {
if ($ranges[0] < $minOffset) { // case: bytes=500-700,601-999
$ranges[0] = $minOffset;
}
if ($ind > 0 && $rangeArray[$ind - 1]['to'] + 1 == $ranges[0]) { // case: bytes=500-600,601-999
$ind--;
$ranges[0] = $rangeArray[$ind]['from'];
}
}
if (is_numeric($ranges[0]) && is_numeric($ranges[1]) && $ranges[0] < $fileSize && $ranges[0] <= $ranges[1]) {
// case: x-x
if ($ranges[1] >= $fileSize) {
$ranges[1] = $fileSize - 1;
}
$rangeArray[$ind++] = [ 'from' => $ranges[0], 'to' => $ranges[1], 'size' => $fileSize ];
$minOffset = $ranges[1] + 1;
if ($minOffset >= $fileSize) {
break;
}
} elseif (is_numeric($ranges[0]) && $ranges[0] < $fileSize) {
// case: x-
$rangeArray[$ind++] = [ 'from' => $ranges[0], 'to' => $fileSize - 1, 'size' => $fileSize ];
break;
} elseif (is_numeric($ranges[1])) {
// case: -x
if ($ranges[1] > $fileSize) {
$ranges[1] = $fileSize;
}
$rangeArray[$ind++] = [ 'from' => $fileSize - $ranges[1], 'to' => $fileSize - 1, 'size' => $fileSize ];
break;
}
}
return $rangeArray;
}
/**
* @param View $view
* @param string $name
* @param string $dir
* @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header
* @throws \OC\ForbiddenException
*/
private static function getSingleFile($view, $dir, $name, $params) {
$filename = $dir . '/' . $name;
$file = null;
try {
$userFolder = \OC::$server->getRootFolder()->get(\OC\Files\Filesystem::getRoot());
$file = $userFolder->get($filename);
if (!$file instanceof \OC\Files\Node\File || !$file->isReadable()) {
http_response_code(403);
die('403 Forbidden');
}
$fileSize = $file->getSize();
} catch (\OCP\Files\NotPermittedException $e) {
http_response_code(403);
die('403 Forbidden');
} catch (\OCP\Files\InvalidPathException $e) {
http_response_code(403);
die('403 Forbidden');
} catch (\OCP\Files\NotFoundException $e) {
http_response_code(404);
$tmpl = new OC_Template('', '404', 'guest');
$tmpl->printPage();
exit();
}
OC_Util::obEnd();
$view->lockFile($filename, ILockingProvider::LOCK_SHARED);
$rangeArray = [];
if (isset($params['range']) && substr($params['range'], 0, 6) === 'bytes=') {
$rangeArray = self::parseHttpRangeHeader(substr($params['range'], 6), $fileSize);
}
$dispatcher = \OCP\Server::get(IEventDispatcher::class);
$event = new BeforeDirectFileDownloadEvent($filename);
$dispatcher->dispatchTyped($event);
if (!\OC\Files\Filesystem::isReadable($filename) || $event->getErrorMessage()) {
if ($event->getErrorMessage()) {
$msg = $event->getErrorMessage();
} else {
$msg = 'Access denied';
}
throw new \OC\ForbiddenException($msg);
}
self::sendHeaders($filename, $name, $rangeArray);
if (isset($params['head']) && $params['head']) {
return;
}
if (!empty($rangeArray)) {
try {
if (count($rangeArray) == 1) {
$view->readfilePart($filename, $rangeArray[0]['from'], $rangeArray[0]['to']);
} else {
// check if file is seekable (if not throw UnseekableException)
// we have to check it before body contents
$view->readfilePart($filename, $rangeArray[0]['size'], $rangeArray[0]['size']);
$type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename));
foreach ($rangeArray as $range) {
echo "\r\n--".self::getBoundary()."\r\n".
"Content-type: ".$type."\r\n".
"Content-range: bytes ".$range['from']."-".$range['to']."/".$range['size']."\r\n\r\n";
$view->readfilePart($filename, $range['from'], $range['to']);
}
echo "\r\n--".self::getBoundary()."--\r\n";
}
} catch (\OCP\Files\UnseekableException $ex) {
// file is unseekable
header_remove('Accept-Ranges');
header_remove('Content-Range');
http_response_code(200);
self::sendHeaders($filename, $name, []);
$view->readfile($filename);
}
} else {
$view->readfile($filename);
}
}
/**
* Returns the total (recursive) number of files and folders in the given
* FileInfos.
*
* @param \OCP\Files\FileInfo[] $fileInfos the FileInfos to count
* @return int the total number of files and folders
*/
private static function getNumberOfFiles($fileInfos) {
$numberOfFiles = 0;
$view = new View();
while ($fileInfo = array_pop($fileInfos)) {
$numberOfFiles++;
if ($fileInfo->getType() === \OCP\Files\FileInfo::TYPE_FOLDER) {
$fileInfos = array_merge($fileInfos, $view->getDirectoryContent($fileInfo->getPath()));
}
}
return $numberOfFiles;
}
/**
* @param View $view
* @param string $dir
* @param string[]|string $files
*/
public static function lockFiles($view, $dir, $files) {
if (!is_array($files)) {
$file = $dir . '/' . $files;
$files = [$file];
}
foreach ($files as $file) {
$file = $dir . '/' . $file;
$view->lockFile($file, ILockingProvider::LOCK_SHARED);
if ($view->is_dir($file)) {
$contents = $view->getDirectoryContent($file);
$contents = array_map(function ($fileInfo) use ($file) {
/** @var \OCP\Files\FileInfo $fileInfo */
return $file . '/' . $fileInfo->getName();
}, $contents);
self::lockFiles($view, $dir, $contents);
}
}
}
/**
* @param string $dir
* @param $files
* @param integer $getType
* @param View $view
* @param string $filename
*/
private static function unlockAllTheFiles($dir, $files, $getType, $view, $filename) {
if ($getType === self::FILE) {
$view->unlockFile($filename, ILockingProvider::LOCK_SHARED);
}
if ($getType === self::ZIP_FILES) {
foreach ($files as $file) {
$file = $dir . '/' . $file;
$view->unlockFile($file, ILockingProvider::LOCK_SHARED);
}
}
if ($getType === self::ZIP_DIR) {
$file = $dir . '/' . $files;
$view->unlockFile($file, ILockingProvider::LOCK_SHARED);
}
}
}
@@ -0,0 +1,667 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Ardinis <Ardinis@users.noreply.github.com>
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bart Visscher <bartv@thisnet.nl>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Felix Moeller <mail@felixmoeller.de>
* @author J0WI <J0WI@users.noreply.github.com>
* @author Jakob Sack <mail@jakobsack.de>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Olivier Paroz <github@oparoz.com>
* @author Pellaeon Lin <nfsmwlin@gmail.com>
* @author RealRancor <fisch.666@gmx.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Simon Könnecke <simonkoennecke@gmail.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Thomas Tanghus <thomas@tanghus.net>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Files\Filesystem;
use OCP\Files\Mount\IMountPoint;
use OCP\IBinaryFinder;
use OCP\ICacheFactory;
use OCP\IUser;
use OCP\Util;
use Psr\Log\LoggerInterface;
/**
* Collection of useful functions
*
* @psalm-type StorageInfo = array{
* free: float|int,
* mountPoint: string,
* mountType: string,
* owner: string,
* ownerDisplayName: string,
* quota: float|int,
* relative: float|int,
* total: float|int,
* used: float|int,
* }
*/
class OC_Helper {
private static $templateManager;
private static ?ICacheFactory $cacheFactory = null;
private static ?bool $quotaIncludeExternalStorage = null;
/**
* Make a human file size
* @param int|float $bytes file size in bytes
* @return string a human readable file size
*
* Makes 2048 to 2 kB.
*/
public static function humanFileSize(int|float $bytes): string {
if ($bytes < 0) {
return "?";
}
if ($bytes < 1024) {
return "$bytes B";
}
$bytes = round($bytes / 1024, 0);
if ($bytes < 1024) {
return "$bytes KB";
}
$bytes = round($bytes / 1024, 1);
if ($bytes < 1024) {
return "$bytes MB";
}
$bytes = round($bytes / 1024, 1);
if ($bytes < 1024) {
return "$bytes GB";
}
$bytes = round($bytes / 1024, 1);
if ($bytes < 1024) {
return "$bytes TB";
}
$bytes = round($bytes / 1024, 1);
return "$bytes PB";
}
/**
* Make a computer file size
* @param string $str file size in human readable format
* @return false|int|float a file size in bytes
*
* Makes 2kB to 2048.
*
* Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
*/
public static function computerFileSize(string $str): false|int|float {
$str = strtolower($str);
if (is_numeric($str)) {
return Util::numericToNumber($str);
}
$bytes_array = [
'b' => 1,
'k' => 1024,
'kb' => 1024,
'mb' => 1024 * 1024,
'm' => 1024 * 1024,
'gb' => 1024 * 1024 * 1024,
'g' => 1024 * 1024 * 1024,
'tb' => 1024 * 1024 * 1024 * 1024,
't' => 1024 * 1024 * 1024 * 1024,
'pb' => 1024 * 1024 * 1024 * 1024 * 1024,
'p' => 1024 * 1024 * 1024 * 1024 * 1024,
];
$bytes = (float)$str;
if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && !empty($bytes_array[$matches[1]])) {
$bytes *= $bytes_array[$matches[1]];
} else {
return false;
}
return Util::numericToNumber(round($bytes));
}
/**
* Recursive copying of folders
* @param string $src source folder
* @param string $dest target folder
* @return void
*/
public static function copyr($src, $dest) {
if (is_dir($src)) {
if (!is_dir($dest)) {
mkdir($dest);
}
$files = scandir($src);
foreach ($files as $file) {
if ($file != "." && $file != "..") {
self::copyr("$src/$file", "$dest/$file");
}
}
} elseif (file_exists($src) && !\OC\Files\Filesystem::isFileBlacklisted($src)) {
copy($src, $dest);
}
}
/**
* Recursive deletion of folders
* @param string $dir path to the folder
* @param bool $deleteSelf if set to false only the content of the folder will be deleted
* @return bool
*/
public static function rmdirr($dir, $deleteSelf = true) {
if (is_dir($dir)) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileInfo) {
/** @var SplFileInfo $fileInfo */
if ($fileInfo->isLink()) {
unlink($fileInfo->getPathname());
} elseif ($fileInfo->isDir()) {
rmdir($fileInfo->getRealPath());
} else {
unlink($fileInfo->getRealPath());
}
}
if ($deleteSelf) {
rmdir($dir);
}
} elseif (file_exists($dir)) {
if ($deleteSelf) {
unlink($dir);
}
}
if (!$deleteSelf) {
return true;
}
return !file_exists($dir);
}
/**
* @deprecated 18.0.0
* @return \OC\Files\Type\TemplateManager
*/
public static function getFileTemplateManager() {
if (!self::$templateManager) {
self::$templateManager = new \OC\Files\Type\TemplateManager();
}
return self::$templateManager;
}
/**
* detect if a given program is found in the search PATH
*
* @param string $name
* @param bool $path
* @internal param string $program name
* @internal param string $optional search path, defaults to $PATH
* @return bool true if executable program found in path
*/
public static function canExecute($name, $path = false) {
// path defaults to PATH from environment if not set
if ($path === false) {
$path = getenv("PATH");
}
// we look for an executable file of that name
$exts = [""];
$check_fn = "is_executable";
// Default check will be done with $path directories :
$dirs = explode(PATH_SEPARATOR, $path);
// WARNING : We have to check if open_basedir is enabled :
$obd = OC::$server->get(IniGetWrapper::class)->getString('open_basedir');
if ($obd != "none") {
$obd_values = explode(PATH_SEPARATOR, $obd);
if (count($obd_values) > 0 and $obd_values[0]) {
// open_basedir is in effect !
// We need to check if the program is in one of these dirs :
$dirs = $obd_values;
}
}
foreach ($dirs as $dir) {
foreach ($exts as $ext) {
if ($check_fn("$dir/$name" . $ext)) {
return true;
}
}
}
return false;
}
/**
* copy the contents of one stream to another
*
* @param resource $source
* @param resource $target
* @return array the number of bytes copied and result
*/
public static function streamCopy($source, $target) {
if (!$source or !$target) {
return [0, false];
}
$bufSize = 8192;
$result = true;
$count = 0;
while (!feof($source)) {
$buf = fread($source, $bufSize);
$bytesWritten = fwrite($target, $buf);
if ($bytesWritten !== false) {
$count += $bytesWritten;
}
// note: strlen is expensive so only use it when necessary,
// on the last block
if ($bytesWritten === false
|| ($bytesWritten < $bufSize && $bytesWritten < strlen($buf))
) {
// write error, could be disk full ?
$result = false;
break;
}
}
return [$count, $result];
}
/**
* Adds a suffix to the name in case the file exists
*
* @param string $path
* @param string $filename
* @return string
*/
public static function buildNotExistingFileName($path, $filename) {
$view = \OC\Files\Filesystem::getView();
return self::buildNotExistingFileNameForView($path, $filename, $view);
}
/**
* Adds a suffix to the name in case the file exists
*
* @param string $path
* @param string $filename
* @return string
*/
public static function buildNotExistingFileNameForView($path, $filename, \OC\Files\View $view) {
if ($path === '/') {
$path = '';
}
if ($pos = strrpos($filename, '.')) {
$name = substr($filename, 0, $pos);
$ext = substr($filename, $pos);
} else {
$name = $filename;
$ext = '';
}
$newpath = $path . '/' . $filename;
if ($view->file_exists($newpath)) {
if (preg_match_all('/\((\d+)\)/', $name, $matches, PREG_OFFSET_CAPTURE)) {
//Replace the last "(number)" with "(number+1)"
$last_match = count($matches[0]) - 1;
$counter = $matches[1][$last_match][0] + 1;
$offset = $matches[0][$last_match][1];
$match_length = strlen($matches[0][$last_match][0]);
} else {
$counter = 2;
$match_length = 0;
$offset = false;
}
do {
if ($offset) {
//Replace the last "(number)" with "(number+1)"
$newname = substr_replace($name, '(' . $counter . ')', $offset, $match_length);
} else {
$newname = $name . ' (' . $counter . ')';
}
$newpath = $path . '/' . $newname . $ext;
$counter++;
} while ($view->file_exists($newpath));
}
return $newpath;
}
/**
* Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
*
* @param array $input The array to work on
* @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
* @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
* @return array
*
* Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
* based on https://www.php.net/manual/en/function.array-change-key-case.php#107715
*
*/
public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
$case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER;
$ret = [];
foreach ($input as $k => $v) {
$ret[mb_convert_case($k, $case, $encoding)] = $v;
}
return $ret;
}
/**
* performs a search in a nested array
* @param array $haystack the array to be searched
* @param string $needle the search string
* @param mixed $index optional, only search this key name
* @return mixed the key of the matching field, otherwise false
*
* performs a search in a nested array
*
* taken from https://www.php.net/manual/en/function.array-search.php#97645
*/
public static function recursiveArraySearch($haystack, $needle, $index = null) {
$aIt = new RecursiveArrayIterator($haystack);
$it = new RecursiveIteratorIterator($aIt);
while ($it->valid()) {
if (((isset($index) and ($it->key() == $index)) or !isset($index)) and ($it->current() == $needle)) {
return $aIt->key();
}
$it->next();
}
return false;
}
/**
* calculates the maximum upload size respecting system settings, free space and user quota
*
* @param string $dir the current folder where the user currently operates
* @param int|float $freeSpace the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
* @return int|float number of bytes representing
*/
public static function maxUploadFilesize($dir, $freeSpace = null) {
if (is_null($freeSpace) || $freeSpace < 0) {
$freeSpace = self::freeSpace($dir);
}
return min($freeSpace, self::uploadLimit());
}
/**
* Calculate free space left within user quota
*
* @param string $dir the current folder where the user currently operates
* @return int|float number of bytes representing
*/
public static function freeSpace($dir) {
$freeSpace = \OC\Files\Filesystem::free_space($dir);
if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
$freeSpace = max($freeSpace, 0);
return $freeSpace;
} else {
return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
}
}
/**
* Calculate PHP upload limit
*
* @return int|float PHP upload file size limit
*/
public static function uploadLimit() {
$ini = \OC::$server->get(IniGetWrapper::class);
$upload_max_filesize = Util::computerFileSize($ini->get('upload_max_filesize')) ?: 0;
$post_max_size = Util::computerFileSize($ini->get('post_max_size')) ?: 0;
if ($upload_max_filesize === 0 && $post_max_size === 0) {
return INF;
} elseif ($upload_max_filesize === 0 || $post_max_size === 0) {
return max($upload_max_filesize, $post_max_size); //only the non 0 value counts
} else {
return min($upload_max_filesize, $post_max_size);
}
}
/**
* Checks if a function is available
*
* @deprecated Since 25.0.0 use \OCP\Util::isFunctionEnabled instead
*/
public static function is_function_enabled(string $function_name): bool {
return \OCP\Util::isFunctionEnabled($function_name);
}
/**
* Try to find a program
* @deprecated Since 25.0.0 Use \OC\BinaryFinder directly
*/
public static function findBinaryPath(string $program): ?string {
$result = \OCP\Server::get(IBinaryFinder::class)->findBinaryPath($program);
return $result !== false ? $result : null;
}
/**
* Calculate the disc space for the given path
*
* BEWARE: this requires that Util::setupFS() was called
* already !
*
* @param string $path
* @param \OCP\Files\FileInfo $rootInfo (optional)
* @param bool $includeMountPoints whether to include mount points in the size calculation
* @param bool $useCache whether to use the cached quota values
* @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
* @return StorageInfo
* @throws \OCP\Files\NotFoundException
*/
public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) {
if (!self::$cacheFactory) {
self::$cacheFactory = \OC::$server->get(ICacheFactory::class);
}
$memcache = self::$cacheFactory->createLocal('storage_info');
// return storage info without adding mount points
if (self::$quotaIncludeExternalStorage === null) {
self::$quotaIncludeExternalStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false);
}
$view = Filesystem::getView();
if (!$view) {
throw new \OCP\Files\NotFoundException();
}
$fullPath = Filesystem::normalizePath($view->getAbsolutePath($path));
$cacheKey = $fullPath. '::' . ($includeMountPoints ? 'include' : 'exclude');
if ($useCache) {
$cached = $memcache->get($cacheKey);
if ($cached) {
return $cached;
}
}
if (!$rootInfo) {
$rootInfo = \OC\Files\Filesystem::getFileInfo($path, self::$quotaIncludeExternalStorage ? 'ext' : false);
}
if (!$rootInfo instanceof \OCP\Files\FileInfo) {
throw new \OCP\Files\NotFoundException('The root directory of the user\'s files is missing');
}
$used = $rootInfo->getSize($includeMountPoints);
if ($used < 0) {
$used = 0.0;
}
/** @var int|float $quota */
$quota = \OCP\Files\FileInfo::SPACE_UNLIMITED;
$mount = $rootInfo->getMountPoint();
$storage = $mount->getStorage();
$sourceStorage = $storage;
if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
self::$quotaIncludeExternalStorage = false;
}
if (self::$quotaIncludeExternalStorage) {
if ($storage->instanceOfStorage('\OC\Files\Storage\Home')
|| $storage->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')
) {
/** @var \OC\Files\Storage\Home $storage */
$user = $storage->getUser();
} else {
$user = \OC::$server->getUserSession()->getUser();
}
$quota = OC_Util::getUserQuota($user);
if ($quota !== \OCP\Files\FileInfo::SPACE_UNLIMITED) {
// always get free space / total space from root + mount points
return self::getGlobalStorageInfo($quota, $user, $mount);
}
}
// TODO: need a better way to get total space from storage
if ($sourceStorage->instanceOfStorage('\OC\Files\Storage\Wrapper\Quota')) {
/** @var \OC\Files\Storage\Wrapper\Quota $storage */
$quota = $sourceStorage->getQuota();
}
try {
$free = $sourceStorage->free_space($rootInfo->getInternalPath());
if (is_bool($free)) {
$free = 0.0;
}
} catch (\Exception $e) {
if ($path === "") {
throw $e;
}
/** @var LoggerInterface $logger */
$logger = \OC::$server->get(LoggerInterface::class);
$logger->warning("Error while getting quota info, using root quota", ['exception' => $e]);
$rootInfo = self::getStorageInfo("");
$memcache->set($cacheKey, $rootInfo, 5 * 60);
return $rootInfo;
}
if ($free >= 0) {
$total = $free + $used;
} else {
$total = $free; //either unknown or unlimited
}
if ($total > 0) {
if ($quota > 0 && $total > $quota) {
$total = $quota;
}
// prevent division by zero or error codes (negative values)
$relative = round(($used / $total) * 10000) / 100;
} else {
$relative = 0;
}
/** @var string $ownerId */
$ownerId = $storage->getOwner($path);
$ownerDisplayName = '';
if ($ownerId) {
$ownerDisplayName = \OC::$server->getUserManager()->getDisplayName($ownerId) ?? '';
}
if (substr_count($mount->getMountPoint(), '/') < 3) {
$mountPoint = '';
} else {
[,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
}
$info = [
'free' => $free,
'used' => $used,
'quota' => $quota,
'total' => $total,
'relative' => $relative,
'owner' => $ownerId,
'ownerDisplayName' => $ownerDisplayName,
'mountType' => $mount->getMountType(),
'mountPoint' => trim($mountPoint, '/'),
];
$memcache->set($cacheKey, $info, 5 * 60);
return $info;
}
/**
* Get storage info including all mount points and quota
*
* @psalm-suppress LessSpecificReturnStatement Legacy code outputs weird types - manually validated that they are correct
* @return StorageInfo
*/
private static function getGlobalStorageInfo(int|float $quota, IUser $user, IMountPoint $mount): array {
$rootInfo = \OC\Files\Filesystem::getFileInfo('', 'ext');
/** @var int|float $used */
$used = $rootInfo['size'];
if ($used < 0) {
$used = 0.0;
}
$total = $quota;
/** @var int|float $free */
$free = $quota - $used;
if ($total > 0) {
if ($quota > 0 && $total > $quota) {
$total = $quota;
}
// prevent division by zero or error codes (negative values)
$relative = round(($used / $total) * 10000) / 100;
} else {
$relative = 0.0;
}
if (substr_count($mount->getMountPoint(), '/') < 3) {
$mountPoint = '';
} else {
[,,,$mountPoint] = explode('/', $mount->getMountPoint(), 4);
}
return [
'free' => $free,
'used' => $used,
'total' => $total,
'relative' => $relative,
'quota' => $quota,
'owner' => $user->getUID(),
'ownerDisplayName' => $user->getDisplayName(),
'mountType' => $mount->getMountType(),
'mountPoint' => trim($mountPoint, '/'),
];
}
public static function clearStorageInfo(string $absolutePath): void {
/** @var ICacheFactory $cacheFactory */
$cacheFactory = \OC::$server->get(ICacheFactory::class);
$memcache = $cacheFactory->createLocal('storage_info');
$cacheKeyPrefix = Filesystem::normalizePath($absolutePath) . '::';
$memcache->remove($cacheKeyPrefix . 'include');
$memcache->remove($cacheKeyPrefix . 'exclude');
}
/**
* Returns whether the config file is set manually to read-only
* @return bool
*/
public static function isReadOnlyConfigEnabled() {
return \OC::$server->getConfig()->getSystemValueBool('config_is_read_only', false);
}
}
+145
View File
@@ -0,0 +1,145 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Jakob Sack <mail@jakobsack.de>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Sam Tuke <mail@samtuke.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
class OC_Hook {
public static $thrownExceptions = [];
private static $registered = [];
/**
* connects a function to a hook
*
* @param string $signalClass class name of emitter
* @param string $signalName name of signal
* @param string|object $slotClass class name of slot
* @param string $slotName name of slot
* @return bool
*
* This function makes it very easy to connect to use hooks.
*
* TODO: write example
*/
public static function connect($signalClass, $signalName, $slotClass, $slotName) {
// If we're trying to connect to an emitting class that isn't
// yet registered, register it
if (!array_key_exists($signalClass, self::$registered)) {
self::$registered[$signalClass] = [];
}
// If we're trying to connect to an emitting method that isn't
// yet registered, register it with the emitting class
if (!array_key_exists($signalName, self::$registered[$signalClass])) {
self::$registered[$signalClass][$signalName] = [];
}
// don't connect hooks twice
foreach (self::$registered[$signalClass][$signalName] as $hook) {
if ($hook['class'] === $slotClass and $hook['name'] === $slotName) {
return false;
}
}
// Connect the hook handler to the requested emitter
self::$registered[$signalClass][$signalName][] = [
"class" => $slotClass,
"name" => $slotName
];
// No chance for failure ;-)
return true;
}
/**
* emits a signal
*
* @param string $signalClass class name of emitter
* @param string $signalName name of signal
* @param mixed $params default: array() array with additional data
* @return bool true if slots exists or false if not
* @throws \OCP\HintException
* @throws \OC\ServerNotAvailableException Emits a signal. To get data from the slot use references!
*
* TODO: write example
*/
public static function emit($signalClass, $signalName, $params = []) {
// Return false if no hook handlers are listening to this
// emitting class
if (!array_key_exists($signalClass, self::$registered)) {
return false;
}
// Return false if no hook handlers are listening to this
// emitting method
if (!array_key_exists($signalName, self::$registered[$signalClass])) {
return false;
}
// Call all slots
foreach (self::$registered[$signalClass][$signalName] as $i) {
try {
call_user_func([ $i["class"], $i["name"] ], $params);
} catch (Exception $e) {
self::$thrownExceptions[] = $e;
\OC::$server->getLogger()->logException($e);
if ($e instanceof \OCP\HintException) {
throw $e;
}
if ($e instanceof \OC\ServerNotAvailableException) {
throw $e;
}
}
}
return true;
}
/**
* clear hooks
* @param string $signalClass
* @param string $signalName
*/
public static function clear($signalClass = '', $signalName = '') {
if ($signalClass) {
if ($signalName) {
self::$registered[$signalClass][$signalName] = [];
} else {
self::$registered[$signalClass] = [];
}
} else {
self::$registered = [];
}
}
/**
* DO NOT USE!
* For unit tests ONLY!
*/
public static function getHooks() {
return self::$registered;
}
}
File diff suppressed because it is too large Load Diff
+135
View File
@@ -0,0 +1,135 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Thomas Tanghus <thomas@tanghus.net>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
class OC_JSON {
/**
* Check if the app is enabled, send json error msg if not
* @param string $app
* @deprecated Use the AppFramework instead. It will automatically check if the app is enabled.
* @suppress PhanDeprecatedFunction
*/
public static function checkAppEnabled($app) {
if (!\OC::$server->getAppManager()->isEnabledForUser($app)) {
$l = \OC::$server->getL10N('lib');
self::error([ 'data' => [ 'message' => $l->t('Application is not enabled'), 'error' => 'application_not_enabled' ]]);
exit();
}
}
/**
* Check if the user is logged in, send json error msg if not
* @deprecated Use annotation based ACLs from the AppFramework instead
* @suppress PhanDeprecatedFunction
*/
public static function checkLoggedIn() {
$twoFactorAuthManger = \OC::$server->getTwoFactorAuthManager();
if (!\OC::$server->getUserSession()->isLoggedIn()
|| $twoFactorAuthManger->needsSecondFactor(\OC::$server->getUserSession()->getUser())) {
$l = \OC::$server->getL10N('lib');
http_response_code(\OCP\AppFramework\Http::STATUS_UNAUTHORIZED);
self::error([ 'data' => [ 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ]]);
exit();
}
}
/**
* Check an ajax get/post call if the request token is valid, send json error msg if not.
* @deprecated Use annotation based CSRF checks from the AppFramework instead
* @suppress PhanDeprecatedFunction
*/
public static function callCheck() {
if (!\OC::$server->getRequest()->passesStrictCookieCheck()) {
header('Location: '.\OC::$WEBROOT);
exit();
}
if (!\OC::$server->getRequest()->passesCSRFCheck()) {
$l = \OC::$server->getL10N('lib');
self::error([ 'data' => [ 'message' => $l->t('Token expired. Please reload page.'), 'error' => 'token_expired' ]]);
exit();
}
}
/**
* Check if the user is a admin, send json error msg if not.
* @deprecated Use annotation based ACLs from the AppFramework instead
* @suppress PhanDeprecatedFunction
*/
public static function checkAdminUser() {
if (!OC_User::isAdminUser(OC_User::getUser())) {
$l = \OC::$server->getL10N('lib');
self::error([ 'data' => [ 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ]]);
exit();
}
}
/**
* Send json error msg
* @deprecated Use a AppFramework JSONResponse instead
* @suppress PhanDeprecatedFunction
* @psalm-taint-escape html
*/
public static function error($data = []) {
$data['status'] = 'error';
header('Content-Type: application/json; charset=utf-8');
echo self::encode($data);
}
/**
* Send json success msg
* @deprecated Use a AppFramework JSONResponse instead
* @suppress PhanDeprecatedFunction
* @psalm-taint-escape html
*/
public static function success($data = []) {
$data['status'] = 'success';
header('Content-Type: application/json; charset=utf-8');
echo self::encode($data);
}
/**
* Convert OC_L10N_String to string, for use in json encodings
*/
protected static function to_string(&$value) {
if ($value instanceof \OC\L10N\L10NString) {
$value = (string)$value;
}
}
/**
* Encode JSON
* @deprecated Use a AppFramework JSONResponse instead
*/
public static function encode($data) {
if (is_array($data)) {
array_walk_recursive($data, ['OC_JSON', 'to_string']);
}
return json_encode($data, JSON_HEX_TAG);
}
}
@@ -0,0 +1,106 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Andreas Fischer <bantu@owncloud.com>
* @author Bart Visscher <bartv@thisnet.nl>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author J0WI <J0WI@users.noreply.github.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
class OC_Response {
/**
* Sets the content disposition header (with possible workarounds)
* @param string $filename file name
* @param string $type disposition type, either 'attachment' or 'inline'
*/
public static function setContentDispositionHeader($filename, $type = 'attachment') {
if (\OC::$server->getRequest()->isUserAgent(
[
\OC\AppFramework\Http\Request::USER_AGENT_IE,
\OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME,
\OC\AppFramework\Http\Request::USER_AGENT_FREEBOX,
])) {
header('Content-Disposition: ' . rawurlencode($type) . '; filename="' . rawurlencode($filename) . '"');
} else {
header('Content-Disposition: ' . rawurlencode($type) . '; filename*=UTF-8\'\'' . rawurlencode($filename)
. '; filename="' . rawurlencode($filename) . '"');
}
}
/**
* Sets the content length header (with possible workarounds)
* @param string|int|float $length Length to be sent
*/
public static function setContentLengthHeader($length) {
if (PHP_INT_SIZE === 4) {
if ($length > PHP_INT_MAX && stripos(PHP_SAPI, 'apache') === 0) {
// Apache PHP SAPI casts Content-Length headers to PHP integers.
// This enforces a limit of PHP_INT_MAX (2147483647 on 32-bit
// platforms). So, if the length is greater than PHP_INT_MAX,
// we just do not send a Content-Length header to prevent
// bodies from being received incompletely.
return;
}
// Convert signed integer or float to unsigned base-10 string.
$lfh = new \OC\LargeFileHelper;
$length = $lfh->formatUnsignedInteger($length);
}
header('Content-Length: '.$length);
}
/**
* This function adds some security related headers to all requests served via base.php
* The implementation of this function has to happen here to ensure that all third-party
* components (e.g. SabreDAV) also benefit from this headers.
*/
public static function addSecurityHeaders() {
/**
* FIXME: Content Security Policy for legacy ownCloud components. This
* can be removed once \OCP\AppFramework\Http\Response from the AppFramework
* is used everywhere.
* @see \OCP\AppFramework\Http\Response::getHeaders
*/
$policy = 'default-src \'self\'; '
. 'script-src \'self\' \'nonce-'.\OC::$server->getContentSecurityPolicyNonceManager()->getNonce().'\'; '
. 'style-src \'self\' \'unsafe-inline\'; '
. 'frame-src *; '
. 'img-src * data: blob:; '
. 'font-src \'self\' data:; '
. 'media-src *; '
. 'connect-src *; '
. 'object-src \'none\'; '
. 'base-uri \'self\'; ';
header('Content-Security-Policy:' . $policy);
// Send fallback headers for installations that don't have the possibility to send
// custom headers on the webserver side
if (getenv('modHeadersAvailable') !== 'true') {
header('Referrer-Policy: no-referrer'); // https://www.w3.org/TR/referrer-policy/
header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE
header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains
header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
header('X-Robots-Tag: noindex, nofollow'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters
}
}
}
@@ -0,0 +1,317 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Brice Maron <brice@bmaron.net>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Frank Karlitschek <frank@karlitschek.de>
* @author Individual IT Services <info@individual-it.net>
* @author Jakob Sack <mail@jakobsack.de>
* @author Jan-Christoph Borchardt <hey@jancborchardt.net>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Marin Treselj <marin@pixelipo.com>
* @author Michael Letzgus <www@chronos.michael-letzgus.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use OC\TemplateLayout;
use OCP\AppFramework\Http\TemplateResponse;
require_once __DIR__.'/template/functions.php';
/**
* This class provides the templates for ownCloud.
*/
class OC_Template extends \OC\Template\Base {
/** @var string */
private $renderAs; // Create a full page?
/** @var string */
private $path; // The path to the template
/** @var array */
private $headers = []; //custom headers
/** @var string */
protected $app; // app id
/**
* Constructor
*
* @param string $app app providing the template
* @param string $name of the template file (without suffix)
* @param string $renderAs If $renderAs is set, OC_Template will try to
* produce a full page in the according layout. For
* now, $renderAs can be set to "guest", "user" or
* "admin".
* @param bool $registerCall = true
*/
public function __construct($app, $name, $renderAs = TemplateResponse::RENDER_AS_BLANK, $registerCall = true) {
$theme = OC_Util::getTheme();
$requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : '';
$parts = explode('/', $app); // fix translation when app is something like core/lostpassword
$l10n = \OC::$server->getL10N($parts[0]);
/** @var \OCP\Defaults $themeDefaults */
$themeDefaults = \OCP\Server::get(\OCP\Defaults::class);
[$path, $template] = $this->findTemplate($theme, $app, $name);
// Set the private data
$this->renderAs = $renderAs;
$this->path = $path;
$this->app = $app;
parent::__construct($template, $requestToken, $l10n, $themeDefaults);
}
/**
* find the template with the given name
* @param string $name of the template file (without suffix)
*
* Will select the template file for the selected theme.
* Checking all the possible locations.
* @param string $theme
* @param string $app
* @return string[]
*/
protected function findTemplate($theme, $app, $name) {
// Check if it is a app template or not.
if ($app !== '') {
$dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app));
} else {
$dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT);
}
$locator = new \OC\Template\TemplateFileLocator($dirs);
$template = $locator->find($name);
$path = $locator->getPath();
return [$path, $template];
}
/**
* Add a custom element to the header
* @param string $tag tag name of the element
* @param array $attributes array of attributes for the element
* @param string $text the text content for the element. If $text is null then the
* element will be written as empty element. So use "" to get a closing tag.
*/
public function addHeader($tag, $attributes, $text = null) {
$this->headers[] = [
'tag' => $tag,
'attributes' => $attributes,
'text' => $text
];
}
/**
* Process the template
* @return string
*
* This function process the template. If $this->renderAs is set, it
* will produce a full page.
*/
public function fetchPage($additionalParams = null) {
$data = parent::fetchPage($additionalParams);
if ($this->renderAs) {
$page = new TemplateLayout($this->renderAs, $this->app);
if (is_array($additionalParams)) {
foreach ($additionalParams as $key => $value) {
$page->assign($key, $value);
}
}
// Add custom headers
$headers = '';
foreach (OC_Util::$headers as $header) {
$headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']);
if (strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes'])))) {
$headers .= ' defer';
}
foreach ($header['attributes'] as $name => $value) {
$headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"';
}
if ($header['text'] !== null) {
$headers .= '>'.\OCP\Util::sanitizeHTML($header['text']).'</'.\OCP\Util::sanitizeHTML($header['tag']).'>';
} else {
$headers .= '/>';
}
}
$page->assign('headers', $headers);
$page->assign('content', $data);
return $page->fetchPage($additionalParams);
}
return $data;
}
/**
* Include template
*
* @param string $file
* @param array|null $additionalParams
* @return string returns content of included template
*
* Includes another template. use <?php echo $this->inc('template'); ?> to
* do this.
*/
public function inc($file, $additionalParams = null) {
return $this->load($this->path.$file.'.php', $additionalParams);
}
/**
* Shortcut to print a simple page for users
* @param string $application The application we render the template for
* @param string $name Name of the template
* @param array $parameters Parameters for the template
* @return boolean|null
*/
public static function printUserPage($application, $name, $parameters = []) {
$content = new OC_Template($application, $name, "user");
foreach ($parameters as $key => $value) {
$content->assign($key, $value);
}
print $content->printPage();
}
/**
* Shortcut to print a simple page for admins
* @param string $application The application we render the template for
* @param string $name Name of the template
* @param array $parameters Parameters for the template
* @return bool
*/
public static function printAdminPage($application, $name, $parameters = []) {
$content = new OC_Template($application, $name, "admin");
foreach ($parameters as $key => $value) {
$content->assign($key, $value);
}
return $content->printPage();
}
/**
* Shortcut to print a simple page for guests
* @param string $application The application we render the template for
* @param string $name Name of the template
* @param array|string $parameters Parameters for the template
* @return bool
*/
public static function printGuestPage($application, $name, $parameters = []) {
$content = new OC_Template($application, $name, $name === 'error' ? $name : 'guest');
foreach ($parameters as $key => $value) {
$content->assign($key, $value);
}
return $content->printPage();
}
/**
* Print a fatal error page and terminates the script
* @param string $error_msg The error message to show
* @param string $hint An optional hint message - needs to be properly escape
* @param int $statusCode
* @suppress PhanAccessMethodInternal
*/
public static function printErrorPage($error_msg, $hint = '', $statusCode = 500) {
if (\OC::$server->getAppManager()->isEnabledForUser('theming') && !\OC_App::isAppLoaded('theming')) {
\OC_App::loadApp('theming');
}
if ($error_msg === $hint) {
// If the hint is the same as the message there is no need to display it twice.
$hint = '';
}
http_response_code($statusCode);
try {
$content = new \OC_Template('', 'error', 'error', false);
$errors = [['error' => $error_msg, 'hint' => $hint]];
$content->assign('errors', $errors);
$content->printPage();
} catch (\Exception $e) {
$logger = \OC::$server->getLogger();
$logger->error("$error_msg $hint", ['app' => 'core']);
$logger->logException($e, ['app' => 'core']);
header('Content-Type: text/plain; charset=utf-8');
print("$error_msg $hint");
}
die();
}
/**
* print error page using Exception details
* @param Exception|Throwable $exception
* @param int $statusCode
* @return bool|string
* @suppress PhanAccessMethodInternal
*/
public static function printExceptionErrorPage($exception, $statusCode = 503) {
http_response_code($statusCode);
try {
$request = \OC::$server->getRequest();
$content = new \OC_Template('', 'exception', 'error', false);
$content->assign('errorClass', get_class($exception));
$content->assign('errorMsg', $exception->getMessage());
$content->assign('errorCode', $exception->getCode());
$content->assign('file', $exception->getFile());
$content->assign('line', $exception->getLine());
$content->assign('exception', $exception);
$content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false));
$content->assign('remoteAddr', $request->getRemoteAddress());
$content->assign('requestID', $request->getId());
$content->printPage();
} catch (\Exception $e) {
try {
$logger = \OC::$server->getLogger();
$logger->logException($exception, ['app' => 'core']);
$logger->logException($e, ['app' => 'core']);
} catch (Throwable $e) {
// no way to log it properly - but to avoid a white page of death we send some output
header('Content-Type: text/plain; charset=utf-8');
print("Internal Server Error\n\n");
print("The server encountered an internal error and was unable to complete your request.\n");
print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
print("More details can be found in the server log.\n");
// and then throw it again to log it at least to the web server error log
throw $e;
}
header('Content-Type: text/plain; charset=utf-8');
print("Internal Server Error\n\n");
print("The server encountered an internal error and was unable to complete your request.\n");
print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
print("More details can be found in the server log.\n");
}
die();
}
}
+420
View File
@@ -0,0 +1,420 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Aldo "xoen" Giambelluca <xoen@xoen.org>
* @author Andreas Fischer <bantu@owncloud.com>
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bartek Przybylski <bart.p.pl@gmail.com>
* @author Björn Schießle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Jakob Sack <mail@jakobsack.de>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author shkdee <louis.traynard@m4x.org>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use OC\User\LoginException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUserManager;
use OCP\User\Events\BeforeUserLoggedInEvent;
use OCP\User\Events\UserLoggedInEvent;
use Psr\Log\LoggerInterface;
/**
* This class provides wrapper methods for user management. Multiple backends are
* supported. User management operations are delegated to the configured backend for
* execution.
*
* Note that &run is deprecated and won't work anymore.
*
* Hooks provided:
* pre_createUser(&run, uid, password)
* post_createUser(uid, password)
* pre_deleteUser(&run, uid)
* post_deleteUser(uid)
* pre_setPassword(&run, uid, password, recoveryPassword)
* post_setPassword(uid, password, recoveryPassword)
* pre_login(&run, uid, password)
* post_login(uid)
* logout()
*/
class OC_User {
private static $_usedBackends = [];
private static $_setupedBackends = [];
// bool, stores if a user want to access a resource anonymously, e.g if they open a public link
private static $incognitoMode = false;
/**
* Adds the backend to the list of used backends
*
* @param string|\OCP\UserInterface $backend default: database The backend to use for user management
* @return bool
*
* Set the User Authentication Module
* @suppress PhanDeprecatedFunction
*/
public static function useBackend($backend = 'database') {
if ($backend instanceof \OCP\UserInterface) {
self::$_usedBackends[get_class($backend)] = $backend;
\OC::$server->getUserManager()->registerBackend($backend);
} else {
// You'll never know what happens
if (null === $backend or !is_string($backend)) {
$backend = 'database';
}
// Load backend
switch ($backend) {
case 'database':
case 'mysql':
case 'sqlite':
\OCP\Server::get(LoggerInterface::class)->debug('Adding user backend ' . $backend . '.', ['app' => 'core']);
self::$_usedBackends[$backend] = new \OC\User\Database();
\OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]);
break;
case 'dummy':
self::$_usedBackends[$backend] = new \Test\Util\User\Dummy();
\OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]);
break;
default:
\OCP\Server::get(LoggerInterface::class)->debug('Adding default user backend ' . $backend . '.', ['app' => 'core']);
$className = 'OC_USER_' . strtoupper($backend);
self::$_usedBackends[$backend] = new $className();
\OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]);
break;
}
}
return true;
}
/**
* remove all used backends
*/
public static function clearBackends() {
self::$_usedBackends = [];
\OC::$server->getUserManager()->clearBackends();
}
/**
* setup the configured backends in config.php
* @suppress PhanDeprecatedFunction
*/
public static function setupBackends() {
OC_App::loadApps(['prelogin']);
$backends = \OC::$server->getSystemConfig()->getValue('user_backends', []);
if (isset($backends['default']) && !$backends['default']) {
// clear default backends
self::clearBackends();
}
foreach ($backends as $i => $config) {
if (!is_array($config)) {
continue;
}
$class = $config['class'];
$arguments = $config['arguments'];
if (class_exists($class)) {
if (!in_array($i, self::$_setupedBackends)) {
// make a reflection object
$reflectionObj = new ReflectionClass($class);
// use Reflection to create a new instance, using the $args
$backend = $reflectionObj->newInstanceArgs($arguments);
self::useBackend($backend);
self::$_setupedBackends[] = $i;
} else {
\OCP\Server::get(LoggerInterface::class)->debug('User backend ' . $class . ' already initialized.', ['app' => 'core']);
}
} else {
\OCP\Server::get(LoggerInterface::class)->error('User backend ' . $class . ' not found.', ['app' => 'core']);
}
}
}
/**
* Try to login a user, assuming authentication
* has already happened (e.g. via Single Sign On).
*
* Log in a user and regenerate a new session.
*
* @param \OCP\Authentication\IApacheBackend $backend
* @return bool
*/
public static function loginWithApache(\OCP\Authentication\IApacheBackend $backend) {
$uid = $backend->getCurrentUserId();
$run = true;
OC_Hook::emit("OC_User", "pre_login", ["run" => &$run, "uid" => $uid, 'backend' => $backend]);
if ($uid) {
if (self::getUser() !== $uid) {
self::setUserId($uid);
$userSession = \OC::$server->getUserSession();
/** @var IEventDispatcher $dispatcher */
$dispatcher = \OC::$server->get(IEventDispatcher::class);
if ($userSession->getUser() && !$userSession->getUser()->isEnabled()) {
$message = \OC::$server->getL10N('lib')->t('User disabled');
throw new LoginException($message);
}
$userSession->setLoginName($uid);
$request = OC::$server->getRequest();
$password = null;
if ($backend instanceof \OCP\Authentication\IProvideUserSecretBackend) {
$password = $backend->getCurrentUserSecret();
}
/** @var IEventDispatcher $dispatcher */
$dispatcher->dispatchTyped(new BeforeUserLoggedInEvent($uid, $password, $backend));
$userSession->createSessionToken($request, $uid, $uid, $password);
$userSession->createRememberMeToken($userSession->getUser());
// setup the filesystem
OC_Util::setupFS($uid);
// first call the post_login hooks, the login-process needs to be
// completed before we can safely create the users folder.
// For example encryption needs to initialize the users keys first
// before we can create the user folder with the skeleton files
OC_Hook::emit(
'OC_User',
'post_login',
[
'uid' => $uid,
'password' => $password,
'isTokenLogin' => false,
]
);
$dispatcher->dispatchTyped(new UserLoggedInEvent(
\OC::$server->get(IUserManager::class)->get($uid),
$uid,
null,
false)
);
//trigger creation of user home and /files folder
\OC::$server->getUserFolder($uid);
}
return true;
}
return false;
}
/**
* Verify with Apache whether user is authenticated.
*
* @return boolean|null
* true: authenticated
* false: not authenticated
* null: not handled / no backend available
*/
public static function handleApacheAuth() {
$backend = self::findFirstActiveUsedBackend();
if ($backend) {
OC_App::loadApps();
//setup extra user backends
self::setupBackends();
\OC::$server->getUserSession()->unsetMagicInCookie();
return self::loginWithApache($backend);
}
return null;
}
/**
* Sets user id for session and triggers emit
*
* @param string $uid
*/
public static function setUserId($uid) {
$userSession = \OC::$server->getUserSession();
$userManager = \OC::$server->getUserManager();
if ($user = $userManager->get($uid)) {
$userSession->setUser($user);
} else {
\OC::$server->getSession()->set('user_id', $uid);
}
}
/**
* Check if the user is logged in, considers also the HTTP basic credentials
*
* @deprecated use \OC::$server->getUserSession()->isLoggedIn()
* @return bool
*/
public static function isLoggedIn() {
return \OC::$server->getUserSession()->isLoggedIn();
}
/**
* set incognito mode, e.g. if a user wants to open a public link
*
* @param bool $status
*/
public static function setIncognitoMode($status) {
self::$incognitoMode = $status;
}
/**
* get incognito mode status
*
* @return bool
*/
public static function isIncognitoMode() {
return self::$incognitoMode;
}
/**
* Returns the current logout URL valid for the currently logged-in user
*
* @param \OCP\IURLGenerator $urlGenerator
* @return string
*/
public static function getLogoutUrl(\OCP\IURLGenerator $urlGenerator) {
$backend = self::findFirstActiveUsedBackend();
if ($backend) {
return $backend->getLogoutUrl();
}
$user = \OC::$server->getUserSession()->getUser();
if ($user instanceof \OCP\IUser) {
$backend = $user->getBackend();
if ($backend instanceof \OCP\User\Backend\ICustomLogout) {
return $backend->getLogoutUrl();
}
}
$logoutUrl = $urlGenerator->linkToRoute('core.login.logout');
$logoutUrl .= '?requesttoken=' . urlencode(\OCP\Util::callRegister());
return $logoutUrl;
}
/**
* Check if the user is an admin user
*
* @param string $uid uid of the admin
* @return bool
*/
public static function isAdminUser($uid) {
$group = \OC::$server->getGroupManager()->get('admin');
$user = \OC::$server->getUserManager()->get($uid);
if ($group && $user && $group->inGroup($user) && self::$incognitoMode === false) {
return true;
}
return false;
}
/**
* get the user id of the user currently logged in.
*
* @return string|false uid or false
*/
public static function getUser() {
$uid = \OC::$server->getSession() ? \OC::$server->getSession()->get('user_id') : null;
if (!is_null($uid) && self::$incognitoMode === false) {
return $uid;
} else {
return false;
}
}
/**
* Set password
*
* @param string $uid The username
* @param string $password The new password
* @param string $recoveryPassword for the encryption app to reset encryption keys
* @return bool
*
* Change the password of a user
*/
public static function setPassword($uid, $password, $recoveryPassword = null) {
$user = \OC::$server->getUserManager()->get($uid);
if ($user) {
return $user->setPassword($password, $recoveryPassword);
} else {
return false;
}
}
/**
* @param string $uid The username
* @return string
*
* returns the path to the users home directory
* @deprecated Use \OC::$server->getUserManager->getHome()
*/
public static function getHome($uid) {
$user = \OC::$server->getUserManager()->get($uid);
if ($user) {
return $user->getHome();
} else {
return \OC::$server->getSystemConfig()->getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid;
}
}
/**
* Get a list of all users display name
*
* @param string $search
* @param int $limit
* @param int $offset
* @return array associative array with all display names (value) and corresponding uids (key)
*
* Get a list of all display names and user ids.
* @deprecated Use \OC::$server->getUserManager->searchDisplayName($search, $limit, $offset) instead.
*/
public static function getDisplayNames($search = '', $limit = null, $offset = null) {
$displayNames = [];
$users = \OC::$server->getUserManager()->searchDisplayName($search, $limit, $offset);
foreach ($users as $user) {
$displayNames[$user->getUID()] = $user->getDisplayName();
}
return $displayNames;
}
/**
* Returns the first active backend from self::$_usedBackends.
*
* @return OCP\Authentication\IApacheBackend|null if no backend active, otherwise OCP\Authentication\IApacheBackend
*/
private static function findFirstActiveUsedBackend() {
foreach (self::$_usedBackends as $backend) {
if ($backend instanceof OCP\Authentication\IApacheBackend) {
if ($backend->isSessionActive()) {
return $backend;
}
}
}
return null;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,346 @@
<?php
use OCP\Util;
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bart Visscher <bartv@thisnet.nl>
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Michael Letzgus <www@chronos.michael-letzgus.de>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin McCorkell <robin@mccorkell.me.uk>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
function p($string) {
print(\OCP\Util::sanitizeHTML($string));
}
/**
* Prints a <link> tag for loading css
* @param string $href the source URL, ignored when empty
* @param string $opts, additional optional options
*/
function emit_css_tag($href, $opts = '') {
$s = '<link rel="stylesheet"';
if (!empty($href)) {
$s .= ' href="' . $href .'"';
}
if (!empty($opts)) {
$s .= ' '.$opts;
}
print_unescaped($s.">\n");
}
/**
* Prints all tags for CSS loading
* @param array $obj all the script information from template
*/
function emit_css_loading_tags($obj) {
foreach ($obj['cssfiles'] as $css) {
emit_css_tag($css);
}
foreach ($obj['printcssfiles'] as $css) {
emit_css_tag($css, 'media="print"');
}
}
/**
* Prints a <script> tag with nonce and defer depending on config
* @param string $src the source URL, ignored when empty
* @param string $script_content the inline script content, ignored when empty
* @param string $content_type the type of the source (e.g. 'module')
*/
function emit_script_tag(string $src, string $script_content = '', string $content_type = '') {
$nonceManager = \OC::$server->get(\OC\Security\CSP\ContentSecurityPolicyNonceManager::class);
$defer_str = ' defer';
$type = $content_type !== '' ? ' type="' . $content_type . '"' : '';
$s = '<script nonce="' . $nonceManager->getNonce() . '"';
if (!empty($src)) {
// emit script tag for deferred loading from $src
$s .= $defer_str.' src="' . $src .'"' . $type . '>';
} elseif ($script_content !== '') {
// emit script tag for inline script from $script_content without defer (see MDN)
$s .= ">\n".$script_content."\n";
} else {
// no $src nor $src_content, really useless empty tag
$s .= '>';
}
$s .= '</script>';
print_unescaped($s."\n");
}
/**
* Print all <script> tags for loading JS
* @param array $obj all the script information from template
*/
function emit_script_loading_tags($obj) {
foreach ($obj['jsfiles'] as $jsfile) {
$fileName = explode('?', $jsfile, 2)[0];
$type = str_ends_with($fileName, '.mjs') ? 'module' : '';
emit_script_tag($jsfile, '', $type);
}
if (!empty($obj['inline_ocjs'])) {
emit_script_tag('', $obj['inline_ocjs']);
}
}
/**
* Prints an unsanitized string - usage of this function may result into XSS.
* Consider using p() instead.
* @param string|array $string the string which will be printed as it is
*/
function print_unescaped($string) {
print($string);
}
/**
* Shortcut for adding scripts to a page
* All scripts are forced to be loaded after core since
* they are coming from a template registration.
* Please consider moving them into the relevant controller
*
* @deprecated 24.0.0 - Use \OCP\Util::addScript
*
* @param string $app the appname
* @param string|string[] $file the filename,
* if an array is given it will add all scripts
*/
function script($app, $file = null) {
if (is_array($file)) {
foreach ($file as $script) {
Util::addScript($app, $script, 'core');
}
} else {
Util::addScript($app, $file, 'core');
}
}
/**
* Shortcut for adding vendor scripts to a page
* @param string $app the appname
* @param string|string[] $file the filename,
* if an array is given it will add all scripts
*/
function vendor_script($app, $file = null) {
if (is_array($file)) {
foreach ($file as $f) {
OC_Util::addVendorScript($app, $f);
}
} else {
OC_Util::addVendorScript($app, $file);
}
}
/**
* Shortcut for adding styles to a page
* @param string $app the appname
* @param string|string[] $file the filename,
* if an array is given it will add all styles
*/
function style($app, $file = null) {
if (is_array($file)) {
foreach ($file as $f) {
OC_Util::addStyle($app, $f);
}
} else {
OC_Util::addStyle($app, $file);
}
}
/**
* Shortcut for adding vendor styles to a page
* @param string $app the appname
* @param string|string[] $file the filename,
* if an array is given it will add all styles
*/
function vendor_style($app, $file = null) {
if (is_array($file)) {
foreach ($file as $f) {
OC_Util::addVendorStyle($app, $f);
}
} else {
OC_Util::addVendorStyle($app, $file);
}
}
/**
* Shortcut for adding translations to a page
* @param string $app the appname
* if an array is given it will add all styles
*/
function translation($app) {
OC_Util::addTranslations($app);
}
/**
* Shortcut for HTML imports
* @param string $app the appname
* @param string|string[] $file the path relative to the app's component folder,
* if an array is given it will add all components
*/
function component($app, $file) {
if (is_array($file)) {
foreach ($file as $f) {
$url = link_to($app, 'component/' . $f . '.html');
OC_Util::addHeader('link', ['rel' => 'import', 'href' => $url]);
}
} else {
$url = link_to($app, 'component/' . $file . '.html');
OC_Util::addHeader('link', ['rel' => 'import', 'href' => $url]);
}
}
/**
* make \OCP\IURLGenerator::linkTo available as a simple function
* @param string $app app
* @param string $file file
* @param array $args array with param=>value, will be appended to the returned url
* @return string link to the file
*
* For further information have a look at \OCP\IURLGenerator::linkTo
*/
function link_to($app, $file, $args = []) {
return \OC::$server->getURLGenerator()->linkTo($app, $file, $args);
}
/**
* @param $key
* @return string url to the online documentation
*/
function link_to_docs($key) {
return \OC::$server->getURLGenerator()->linkToDocs($key);
}
/**
* make \OCP\IURLGenerator::imagePath available as a simple function
* @param string $app app
* @param string $image image
* @return string link to the image
*
* For further information have a look at \OCP\IURLGenerator::imagePath
*/
function image_path($app, $image) {
return \OC::$server->getURLGenerator()->imagePath($app, $image);
}
/**
* make OC_Helper::mimetypeIcon available as a simple function
* @param string $mimetype mimetype
* @return string link to the image
*/
function mimetype_icon($mimetype) {
return \OC::$server->getMimeTypeDetector()->mimeTypeIcon($mimetype);
}
/**
* make preview_icon available as a simple function
* Returns the path to the preview of the image.
* @param string $path path of file
* @return string link to the preview
*/
function preview_icon($path) {
return \OC::$server->getURLGenerator()->linkToRoute('core.Preview.getPreview', ['x' => 32, 'y' => 32, 'file' => $path]);
}
/**
* @param string $path
* @param string $token
* @return string
*/
function publicPreview_icon($path, $token) {
return \OC::$server->getURLGenerator()->linkToRoute('files_sharing.PublicPreview.getPreview', ['x' => 32, 'y' => 32, 'file' => $path, 'token' => $token]);
}
/**
* make OC_Helper::humanFileSize available as a simple function
* @param int $bytes size in bytes
* @return string size as string
*
* For further information have a look at OC_Helper::humanFileSize
*/
function human_file_size($bytes) {
return OC_Helper::humanFileSize($bytes);
}
/**
* Strips the timestamp of its time value
* @param int $timestamp UNIX timestamp to strip
* @return int timestamp without time value
*/
function strip_time($timestamp) {
$date = new \DateTime("@{$timestamp}");
$date->setTime(0, 0, 0);
return (int)$date->format('U');
}
/**
* Formats timestamp relatively to the current time using
* a human-friendly format like "x minutes ago" or "yesterday"
* @param int $timestamp timestamp to format
* @param int|null $fromTime timestamp to compare from, defaults to current time
* @param bool|null $dateOnly whether to strip time information
* @return string timestamp
*/
function relative_modified_date($timestamp, $fromTime = null, $dateOnly = false) {
/** @var \OC\DateTimeFormatter $formatter */
$formatter = \OCP\Server::get('DateTimeFormatter');
if ($dateOnly) {
return $formatter->formatDateSpan($timestamp, $fromTime);
}
return $formatter->formatTimeSpan($timestamp, $fromTime);
}
function html_select_options($options, $selected, $params = []) {
if (!is_array($selected)) {
$selected = [$selected];
}
if (isset($params['combine']) && $params['combine']) {
$options = array_combine($options, $options);
}
$value_name = $label_name = false;
if (isset($params['value'])) {
$value_name = $params['value'];
}
if (isset($params['label'])) {
$label_name = $params['label'];
}
$html = '';
foreach ($options as $value => $label) {
if ($value_name && is_array($label)) {
$value = $label[$value_name];
}
if ($label_name && is_array($label)) {
$label = $label[$label_name];
}
$select = in_array($value, $selected) ? ' selected="selected"' : '';
$html .= '<option value="' . \OCP\Util::sanitizeHTML($value) . '"' . $select . '>' . \OCP\Util::sanitizeHTML($label) . '</option>'."\n";
}
return $html;
}