aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private')
-rw-r--r--lib/private/Accounts/AccountManager.php9
-rw-r--r--lib/private/App/PlatformRepository.php9
-rw-r--r--lib/private/AppConfig.php9
-rw-r--r--lib/private/AppFramework/DependencyInjection/DIContainer.php37
-rw-r--r--lib/private/AppFramework/Http/Dispatcher.php2
-rw-r--r--lib/private/AppFramework/Http/Request.php3
-rw-r--r--lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php22
-rw-r--r--lib/private/AppFramework/Utility/SimpleContainer.php2
-rw-r--r--lib/private/Authentication/Events/LoginFailed.php12
-rw-r--r--lib/private/Authentication/Listeners/LoginFailedListener.php3
-rw-r--r--lib/private/Authentication/Login/EmailLoginCommand.php12
-rw-r--r--lib/private/Authentication/Login/LoggedInCheckCommand.php3
-rw-r--r--lib/private/Calendar/Manager.php6
-rw-r--r--lib/private/Collaboration/Collaborators/MailPlugin.php6
-rw-r--r--lib/private/Comments/Manager.php2
-rw-r--r--lib/private/DB/Connection.php13
-rw-r--r--lib/private/DB/Migrator.php2
-rw-r--r--lib/private/Dashboard/Manager.php16
-rw-r--r--lib/private/Federation/CloudIdManager.php3
-rw-r--r--lib/private/Files/Cache/Storage.php2
-rw-r--r--lib/private/Files/Node/Folder.php15
-rw-r--r--lib/private/Files/Node/Node.php6
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php22
-rw-r--r--lib/private/Files/ObjectStore/S3ObjectTrait.php4
-rw-r--r--lib/private/Files/SetupManager.php26
-rw-r--r--lib/private/Files/SimpleFS/NewSimpleFile.php4
-rw-r--r--lib/private/Files/Storage/Local.php15
-rw-r--r--lib/private/Files/Stream/Encryption.php3
-rw-r--r--lib/private/Files/Stream/SeekableHttpStream.php11
-rw-r--r--lib/private/Files/View.php6
-rw-r--r--lib/private/Group/DisplayNameCache.php87
-rw-r--r--lib/private/Group/Group.php2
-rw-r--r--lib/private/Group/Manager.php17
-rw-r--r--lib/private/Http/Client/Client.php18
-rw-r--r--lib/private/Http/Client/ClientService.php10
-rw-r--r--lib/private/Http/Client/DnsPinMiddleware.php14
-rw-r--r--lib/private/Http/Client/LocalAddressChecker.php102
-rw-r--r--lib/private/L10N/Factory.php13
-rw-r--r--lib/private/Log/ErrorHandler.php84
-rw-r--r--lib/private/Log/Errorlog.php5
-rw-r--r--lib/private/Mail/Message.php18
-rw-r--r--lib/private/Memcache/Redis.php46
-rw-r--r--lib/private/Metadata/FileMetadata.php2
-rw-r--r--lib/private/NavigationManager.php2
-rw-r--r--lib/private/Net/HostnameClassifier.php74
-rw-r--r--lib/private/Net/IpAddressClassifier.php81
-rw-r--r--lib/private/Notification/Manager.php2
-rw-r--r--lib/private/Preview/BackgroundCleanupJob.php8
-rw-r--r--lib/private/Preview/Generator.php166
-rw-r--r--lib/private/Preview/HEIC.php3
-rw-r--r--lib/private/Preview/Imaginary.php21
-rw-r--r--lib/private/PreviewManager.php35
-rw-r--r--lib/private/Profile/Actions/FediverseAction.php90
-rw-r--r--lib/private/Profile/ProfileManager.php2
-rw-r--r--lib/private/Repair.php2
-rw-r--r--lib/private/Repair/NC25/AddMissingSecretJob.php63
-rw-r--r--lib/private/Repair/RepairMimeTypes.php13
-rw-r--r--lib/private/Security/RemoteHostValidator.php76
-rw-r--r--lib/private/Server.php39
-rw-r--r--lib/private/ServerContainer.php11
-rw-r--r--lib/private/Session/CryptoSessionData.php7
-rw-r--r--lib/private/Setup.php7
-rw-r--r--lib/private/Setup/PostgreSQL.php11
-rw-r--r--lib/private/Share20/DefaultShareProvider.php4
-rw-r--r--lib/private/StreamImage.php34
-rw-r--r--lib/private/Streamer.php2
-rw-r--r--lib/private/Support/Subscription/Assertion.php2
-rw-r--r--lib/private/SystemConfig.php4
-rw-r--r--lib/private/Talk/Broker.php8
-rw-r--r--lib/private/Template/CSSResourceLocator.php13
-rw-r--r--lib/private/Template/JSCombiner.php2
-rw-r--r--lib/private/Template/JSResourceLocator.php8
-rwxr-xr-xlib/private/Template/ResourceLocator.php19
-rw-r--r--lib/private/TemplateLayout.php54
-rw-r--r--lib/private/Updater.php3
-rw-r--r--lib/private/User/Manager.php70
-rw-r--r--lib/private/User/Session.php9
-rw-r--r--lib/private/legacy/OC_Helper.php12
-rw-r--r--lib/private/legacy/OC_Image.php393
-rw-r--r--lib/private/legacy/OC_Response.php13
-rw-r--r--lib/private/legacy/OC_Util.php2
81 files changed, 1248 insertions, 820 deletions
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php
index 20e0add1ccb..8fc16d5ce1a 100644
--- a/lib/private/Accounts/AccountManager.php
+++ b/lib/private/Accounts/AccountManager.php
@@ -131,6 +131,7 @@ class AccountManager implements IAccountManager {
self::PROPERTY_AVATAR => self::SCOPE_FEDERATED,
self::PROPERTY_PHONE => self::SCOPE_LOCAL,
self::PROPERTY_TWITTER => self::SCOPE_LOCAL,
+ self::PROPERTY_FEDIVERSE => self::SCOPE_LOCAL,
self::PROPERTY_ORGANISATION => self::SCOPE_LOCAL,
self::PROPERTY_ROLE => self::SCOPE_LOCAL,
self::PROPERTY_HEADLINE => self::SCOPE_LOCAL,
@@ -525,6 +526,7 @@ class AccountManager implements IAccountManager {
protected function updateVerificationStatus(IAccount $updatedAccount, array $oldData): void {
static $propertiesVerifiableByLookupServer = [
self::PROPERTY_TWITTER,
+ self::PROPERTY_FEDIVERSE,
self::PROPERTY_WEBSITE,
self::PROPERTY_EMAIL,
];
@@ -724,6 +726,13 @@ class AccountManager implements IAccountManager {
],
[
+ 'name' => self::PROPERTY_FEDIVERSE,
+ 'value' => '',
+ 'scope' => $scopes[self::PROPERTY_FEDIVERSE],
+ 'verified' => self::NOT_VERIFIED,
+ ],
+
+ [
'name' => self::PROPERTY_ORGANISATION,
'value' => '',
'scope' => $scopes[self::PROPERTY_ORGANISATION],
diff --git a/lib/private/App/PlatformRepository.php b/lib/private/App/PlatformRepository.php
index 4166c2ead03..9b94c0b07bc 100644
--- a/lib/private/App/PlatformRepository.php
+++ b/lib/private/App/PlatformRepository.php
@@ -50,7 +50,11 @@ class PlatformRepository {
$ext = new \ReflectionExtension($name);
try {
$prettyVersion = $ext->getVersion();
- $prettyVersion = $this->normalizeVersion($prettyVersion);
+ /** @psalm-suppress TypeDoesNotContainNull
+ * @psalm-suppress RedundantCondition
+ * TODO Remove these annotations once psalm fixes the method signature ( https://github.com/vimeo/psalm/pull/8655 )
+ */
+ $prettyVersion = $this->normalizeVersion($prettyVersion ?? '0');
} catch (\UnexpectedValueException $e) {
$prettyVersion = '0';
$prettyVersion = $this->normalizeVersion($prettyVersion);
@@ -111,6 +115,9 @@ class PlatformRepository {
continue 2;
}
+ if ($prettyVersion === null) {
+ continue;
+ }
try {
$prettyVersion = $this->normalizeVersion($prettyVersion);
} catch (\UnexpectedValueException $e) {
diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php
index 00e5dddc1f9..efaa2209eca 100644
--- a/lib/private/AppConfig.php
+++ b/lib/private/AppConfig.php
@@ -46,6 +46,7 @@ class AppConfig implements IAppConfig {
/** @var array[] */
protected $sensitiveValues = [
'circles' => [
+ '/^key_pairs$/',
'/^local_gskey$/',
],
'external' => [
@@ -109,9 +110,12 @@ class AppConfig implements IAppConfig {
'/^cookie$/',
],
'spreed' => [
- '/^bridge_bot_password/',
+ '/^bridge_bot_password$/',
+ '/^hosted-signaling-server-(.*)$/',
'/^signaling_servers$/',
'/^signaling_ticket_secret$/',
+ '/^signaling_token_privkey_(.*)$/',
+ '/^signaling_token_pubkey_(.*)$/',
'/^sip_bridge_dialin_info$/',
'/^sip_bridge_shared_secret$/',
'/^stun_servers$/',
@@ -132,6 +136,9 @@ class AppConfig implements IAppConfig {
'user_ldap' => [
'/^(s..)?ldap_agent_password$/',
],
+ 'user_saml' => [
+ '/^idp-x509cert$/',
+ ],
];
/** @var Connection */
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index e06d5226a28..462f9b978fd 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -79,6 +79,7 @@ use Psr\Log\LoggerInterface;
* @deprecated 20.0.0
*/
class DIContainer extends SimpleContainer implements IAppContainer {
+ private string $appName;
/**
* @var array
@@ -94,9 +95,10 @@ class DIContainer extends SimpleContainer implements IAppContainer {
* @param array $urlParams
* @param ServerContainer|null $server
*/
- public function __construct($appName, $urlParams = [], ServerContainer $server = null) {
+ public function __construct(string $appName, array $urlParams = [], ServerContainer $server = null) {
parent::__construct();
- $this['AppName'] = $appName;
+ $this->appName = $appName;
+ $this['appName'] = $appName;
$this['urlParams'] = $urlParams;
$this->registerAlias('Request', IRequest::class);
@@ -109,9 +111,12 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$this->server->registerAppContainer($appName, $this);
// aliases
- $this->registerAlias('appName', 'AppName');
- $this->registerAlias('webRoot', 'WebRoot');
- $this->registerAlias('userId', 'UserId');
+ /** @deprecated inject $appName */
+ $this->registerAlias('AppName', 'appName');
+ /** @deprecated inject $webRoot*/
+ $this->registerAlias('WebRoot', 'webRoot');
+ /** @deprecated inject $userId */
+ $this->registerAlias('UserId', 'userId');
/**
* Core services
@@ -158,11 +163,11 @@ class DIContainer extends SimpleContainer implements IAppContainer {
$this->registerAlias(IAppContainer::class, ContainerInterface::class);
// commonly used attributes
- $this->registerService('UserId', function (ContainerInterface $c) {
+ $this->registerService('userId', function (ContainerInterface $c) {
return $c->get(IUserSession::class)->getSession()->get('user_id');
});
- $this->registerService('WebRoot', function (ContainerInterface $c) {
+ $this->registerService('webRoot', function (ContainerInterface $c) {
return $c->get(IServerContainer::class)->getWebRoot();
});
@@ -301,7 +306,8 @@ class DIContainer extends SimpleContainer implements IAppContainer {
new OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware(
$c->get(IRequest::class),
$c->get(ISession::class),
- $c->get(\OCP\IConfig::class)
+ $c->get(\OCP\IConfig::class),
+ $c->get(OC\Security\Bruteforce\Throttler::class)
)
);
$dispatcher->registerMiddleware(
@@ -433,6 +439,15 @@ class DIContainer extends SimpleContainer implements IAppContainer {
}
public function query(string $name, bool $autoload = true) {
+ if ($name === 'AppName' || $name === 'appName') {
+ return $this->appName;
+ }
+
+ $isServerClass = str_starts_with($name, 'OCP\\') || str_starts_with($name, 'OC\\');
+ if ($isServerClass && !$this->has($name)) {
+ return $this->getServer()->query($name, $autoload);
+ }
+
try {
return $this->queryNoFallback($name);
} catch (QueryException $firstException) {
@@ -457,11 +472,11 @@ class DIContainer extends SimpleContainer implements IAppContainer {
if ($this->offsetExists($name)) {
return parent::query($name);
- } elseif ($this['AppName'] === 'settings' && strpos($name, 'OC\\Settings\\') === 0) {
+ } elseif ($this->appName === 'settings' && str_starts_with($name, 'OC\\Settings\\')) {
return parent::query($name);
- } elseif ($this['AppName'] === 'core' && strpos($name, 'OC\\Core\\') === 0) {
+ } elseif ($this->appName === 'core' && str_starts_with($name, 'OC\\Core\\')) {
return parent::query($name);
- } elseif (strpos($name, \OC\AppFramework\App::buildAppNamespace($this['AppName']) . '\\') === 0) {
+ } elseif (str_starts_with($name, \OC\AppFramework\App::buildAppNamespace($this->appName) . '\\')) {
return parent::query($name);
}
diff --git a/lib/private/AppFramework/Http/Dispatcher.php b/lib/private/AppFramework/Http/Dispatcher.php
index c1a203a7165..aaaf2ba6560 100644
--- a/lib/private/AppFramework/Http/Dispatcher.php
+++ b/lib/private/AppFramework/Http/Dispatcher.php
@@ -194,7 +194,7 @@ class Dispatcher {
$arguments = [];
// valid types that will be casted
- $types = ['int', 'integer', 'bool', 'boolean', 'float'];
+ $types = ['int', 'integer', 'bool', 'boolean', 'float', 'double'];
foreach ($this->reflector->getParameters() as $param => $default) {
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php
index 496a845dd4a..286187c696c 100644
--- a/lib/private/AppFramework/Http/Request.php
+++ b/lib/private/AppFramework/Http/Request.php
@@ -431,13 +431,12 @@ class Request implements \ArrayAccess, \Countable, IRequest {
// 'application/json' must be decoded manually.
if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) {
$params = json_decode(file_get_contents($this->inputStream), true);
- if ($params !== null && \count($params) > 0) {
+ if (\is_array($params) && \count($params) > 0) {
$this->items['params'] = $params;
if ($this->method === 'POST') {
$this->items['post'] = $params;
}
}
-
// Handle application/x-www-form-urlencoded for methods other than GET
// or post correctly
} elseif ($this->method !== 'GET'
diff --git a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
index d3beb4fd3a8..d956ce58912 100644
--- a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
+++ b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
@@ -24,6 +24,7 @@
namespace OC\AppFramework\Middleware\PublicShare;
use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException;
+use OC\Security\Bruteforce\Throttler;
use OCP\AppFramework\AuthPublicShareController;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Middleware;
@@ -34,6 +35,7 @@ use OCP\IRequest;
use OCP\ISession;
class PublicShareMiddleware extends Middleware {
+
/** @var IRequest */
private $request;
@@ -43,10 +45,14 @@ class PublicShareMiddleware extends Middleware {
/** @var IConfig */
private $config;
- public function __construct(IRequest $request, ISession $session, IConfig $config) {
+ /** @var Throttler */
+ private $throttler;
+
+ public function __construct(IRequest $request, ISession $session, IConfig $config, Throttler $throttler) {
$this->request = $request;
$this->session = $session;
$this->config = $config;
+ $this->throttler = $throttler;
}
public function beforeController($controller, $methodName) {
@@ -54,6 +60,11 @@ class PublicShareMiddleware extends Middleware {
return;
}
+ $controllerClassPath = explode('\\', get_class($controller));
+ $controllerShortClass = end($controllerClassPath);
+ $bruteforceProtectionAction = $controllerShortClass . '::' . $methodName;
+ $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $bruteforceProtectionAction);
+
if (!$this->isLinkSharingEnabled()) {
throw new NotFoundException('Link sharing is disabled');
}
@@ -68,6 +79,8 @@ class PublicShareMiddleware extends Middleware {
$controller->setToken($token);
if (!$controller->isValidToken()) {
+ $this->throttle($bruteforceProtectionAction, $token);
+
$controller->shareNotFound();
throw new NotFoundException();
}
@@ -88,6 +101,7 @@ class PublicShareMiddleware extends Middleware {
throw new NeedAuthenticationException();
}
+ $this->throttle($bruteforceProtectionAction, $token);
throw new NotFoundException();
}
@@ -128,4 +142,10 @@ class PublicShareMiddleware extends Middleware {
return true;
}
+
+ private function throttle($bruteforceProtectionAction, $token): void {
+ $ip = $this->request->getRemoteAddress();
+ $this->throttler->sleepDelay($ip, $bruteforceProtectionAction);
+ $this->throttler->registerAttempt($bruteforceProtectionAction, $ip, ['token' => $token]);
+ }
}
diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php
index da1efdec826..925ef67de64 100644
--- a/lib/private/AppFramework/Utility/SimpleContainer.php
+++ b/lib/private/AppFramework/Utility/SimpleContainer.php
@@ -108,7 +108,7 @@ class SimpleContainer implements ArrayAccess, ContainerInterface, IContainer {
return $this->query($resolveName);
} catch (QueryException $e2) {
// don't lose the error we got while trying to query by type
- throw new QueryException($e2->getMessage(), (int) $e2->getCode(), $e);
+ throw new QueryException($e->getMessage(), (int) $e->getCode(), $e);
}
}
diff --git a/lib/private/Authentication/Events/LoginFailed.php b/lib/private/Authentication/Events/LoginFailed.php
index 138e567139a..ef702e40a59 100644
--- a/lib/private/Authentication/Events/LoginFailed.php
+++ b/lib/private/Authentication/Events/LoginFailed.php
@@ -28,17 +28,21 @@ namespace OC\Authentication\Events;
use OCP\EventDispatcher\Event;
class LoginFailed extends Event {
+ private string $loginName;
+ private ?string $password;
- /** @var string */
- private $loginName;
-
- public function __construct(string $loginName) {
+ public function __construct(string $loginName, ?string $password) {
parent::__construct();
$this->loginName = $loginName;
+ $this->password = $password;
}
public function getLoginName(): string {
return $this->loginName;
}
+
+ public function getPassword(): ?string {
+ return $this->password;
+ }
}
diff --git a/lib/private/Authentication/Listeners/LoginFailedListener.php b/lib/private/Authentication/Listeners/LoginFailedListener.php
index 12d5dd4c17e..f75ee51b287 100644
--- a/lib/private/Authentication/Listeners/LoginFailedListener.php
+++ b/lib/private/Authentication/Listeners/LoginFailedListener.php
@@ -27,6 +27,7 @@ declare(strict_types=1);
namespace OC\Authentication\Listeners;
use OC\Authentication\Events\LoginFailed;
+use OCP\Authentication\Events\AnyLoginFailedEvent;
use OCP\Authentication\Events\LoginFailedEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventDispatcher;
@@ -55,6 +56,8 @@ class LoginFailedListener implements IEventListener {
return;
}
+ $this->dispatcher->dispatchTyped(new AnyLoginFailedEvent($event->getLoginName(), $event->getPassword()));
+
$uid = $event->getLoginName();
Util::emitHook(
'\OCA\Files_Sharing\API\Server2Server',
diff --git a/lib/private/Authentication/Login/EmailLoginCommand.php b/lib/private/Authentication/Login/EmailLoginCommand.php
index e2e55cc12c8..7145ab9e14f 100644
--- a/lib/private/Authentication/Login/EmailLoginCommand.php
+++ b/lib/private/Authentication/Login/EmailLoginCommand.php
@@ -38,9 +38,21 @@ class EmailLoginCommand extends ALoginCommand {
public function process(LoginData $loginData): LoginResult {
if ($loginData->getUser() === false) {
+ if (!filter_var($loginData->getUsername(), FILTER_VALIDATE_EMAIL)) {
+ return $this->processNextOrFinishSuccessfully($loginData);
+ }
+
$users = $this->userManager->getByEmail($loginData->getUsername());
// we only allow login by email if unique
if (count($users) === 1) {
+
+ // FIXME: This is a workaround to still stick to configured LDAP login filters
+ // this can be removed once the email login is properly implemented in the local user backend
+ // as described in https://github.com/nextcloud/server/issues/5221
+ if ($users[0]->getBackendClassName() === 'LDAP') {
+ return $this->processNextOrFinishSuccessfully($loginData);
+ }
+
$username = $users[0]->getUID();
if ($username !== $loginData->getUsername()) {
$user = $this->userManager->checkPassword(
diff --git a/lib/private/Authentication/Login/LoggedInCheckCommand.php b/lib/private/Authentication/Login/LoggedInCheckCommand.php
index 0efd84adbf5..9f80d47a3d4 100644
--- a/lib/private/Authentication/Login/LoggedInCheckCommand.php
+++ b/lib/private/Authentication/Login/LoggedInCheckCommand.php
@@ -48,11 +48,12 @@ class LoggedInCheckCommand extends ALoginCommand {
public function process(LoginData $loginData): LoginResult {
if ($loginData->getUser() === false) {
$loginName = $loginData->getUsername();
+ $password = $loginData->getPassword();
$ip = $loginData->getRequest()->getRemoteAddress();
$this->logger->warning("Login failed: $loginName (Remote IP: $ip)");
- $this->dispatcher->dispatchTyped(new LoginFailed($loginName));
+ $this->dispatcher->dispatchTyped(new LoginFailed($loginName, $password));
return LoginResult::failure($loginData, LoginController::LOGIN_MSG_INVALIDPASSWORD);
}
diff --git a/lib/private/Calendar/Manager.php b/lib/private/Calendar/Manager.php
index 550ba36dd6b..e85c2d2e377 100644
--- a/lib/private/Calendar/Manager.php
+++ b/lib/private/Calendar/Manager.php
@@ -7,6 +7,7 @@ declare(strict_types=1);
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com>
+ * @author Anna Larch <anna.larch@gmx.net>
*
* @license GNU AGPL version 3 or any later version
*
@@ -33,6 +34,7 @@ use OCP\Calendar\ICalendar;
use OCP\Calendar\ICalendarProvider;
use OCP\Calendar\ICalendarQuery;
use OCP\Calendar\ICreateFromString;
+use OCP\Calendar\IHandleImipMessage;
use OCP\Calendar\IManager;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
@@ -280,7 +282,7 @@ class Manager implements IManager {
// Drawback: attendees that have been deleted will still be able to update their partstat
foreach ($calendars as $calendar) {
// We should not search in writable calendars
- if ($calendar instanceof ICreateFromString) {
+ if ($calendar instanceof IHandleImipMessage) {
$o = $calendar->search($sender, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
if (!empty($o)) {
$found = $calendar;
@@ -358,7 +360,7 @@ class Manager implements IManager {
// Drawback: attendees that have been deleted will still be able to update their partstat
foreach ($calendars as $calendar) {
// We should not search in writable calendars
- if ($calendar instanceof ICreateFromString) {
+ if ($calendar instanceof IHandleImipMessage) {
$o = $calendar->search($recipient, ['ATTENDEE'], ['uid' => $vEvent->{'UID'}->getValue()]);
if (!empty($o)) {
$found = $calendar;
diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php
index d34d9fb0087..aa317ec1720 100644
--- a/lib/private/Collaboration/Collaborators/MailPlugin.php
+++ b/lib/private/Collaboration/Collaborators/MailPlugin.php
@@ -101,6 +101,12 @@ class MailPlugin implements ISearchPlugin {
return false;
}
+ // Extract the email address from "Foo Bar <foo.bar@example.tld>" and then search with "foo.bar@example.tld" instead
+ $result = preg_match('/<([^@]+@.+)>$/', $search, $matches);
+ if ($result && filter_var($matches[1], FILTER_VALIDATE_EMAIL)) {
+ return $this->search($matches[1], $limit, $offset, $searchResult);
+ }
+
$currentUserId = $this->userSession->getUser()->getUID();
$result = $userResults = ['wide' => [], 'exact' => []];
diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php
index 53603e51e56..3eb9aab6817 100644
--- a/lib/private/Comments/Manager.php
+++ b/lib/private/Comments/Manager.php
@@ -605,7 +605,7 @@ class Manager implements ICommentsManager {
public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array {
$objectIds = [];
if ($objectId) {
- $objectIds[] = $objectIds;
+ $objectIds[] = $objectId;
}
return $this->searchForObjects($search, $objectType, $objectIds, $verb, $offset, $limit);
}
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index 73e0f4b4ac2..ceaffbcfa9a 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -50,12 +50,13 @@ use Doctrine\DBAL\Result;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Statement;
use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Diagnostics\IEventLogger;
use OCP\IRequestId;
use OCP\PreConditionNotMetException;
+use OCP\Profiler\IProfiler;
use OC\DB\QueryBuilder\QueryBuilder;
use OC\SystemConfig;
use Psr\Log\LoggerInterface;
-use OCP\Profiler\IProfiler;
class Connection extends \Doctrine\DBAL\Connection {
/** @var string */
@@ -124,12 +125,14 @@ class Connection extends \Doctrine\DBAL\Connection {
public function connect() {
try {
if ($this->_conn) {
+ /** @psalm-suppress InternalMethod */
return parent::connect();
}
// Only trigger the event logger for the initial connect call
- $eventLogger = \OC::$server->getEventLogger();
+ $eventLogger = \OC::$server->get(IEventLogger::class);
$eventLogger->start('connect:db', 'db connection opened');
+ /** @psalm-suppress InternalMethod */
$status = parent::connect();
$eventLogger->end('connect:db');
@@ -291,7 +294,7 @@ class Connection extends \Doctrine\DBAL\Connection {
$sql = $this->adapter->fixupStatement($sql);
$this->queriesExecuted++;
$this->logQueryToFile($sql);
- return parent::executeStatement($sql, $params, $types);
+ return (int)parent::executeStatement($sql, $params, $types);
}
protected function logQueryToFile(string $sql): void {
@@ -390,7 +393,7 @@ class Connection extends \Doctrine\DBAL\Connection {
return $insertQb->createNamedParameter($value, $this->getType($value));
}, array_merge($keys, $values))
);
- return $insertQb->execute();
+ return $insertQb->executeStatement();
} catch (NotNullConstraintViolationException $e) {
throw $e;
} catch (ConstraintViolationException $e) {
@@ -416,7 +419,7 @@ class Connection extends \Doctrine\DBAL\Connection {
}
}
$updateQb->where($where);
- $affected = $updateQb->execute();
+ $affected = $updateQb->executeStatement();
if ($affected === 0 && !empty($updatePreconditionValues)) {
throw new PreConditionNotMetException();
diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php
index 5dc07be1d2b..97d91e1c100 100644
--- a/lib/private/DB/Migrator.php
+++ b/lib/private/DB/Migrator.php
@@ -41,7 +41,6 @@ use function preg_match;
use OCP\EventDispatcher\IEventDispatcher;
class Migrator {
-
/** @var Connection */
protected $connection;
@@ -138,6 +137,7 @@ class Migrator {
}
}
+ /** @psalm-suppress InternalMethod */
$comparator = new Comparator();
return $comparator->compare($sourceSchema, $targetSchema);
}
diff --git a/lib/private/Dashboard/Manager.php b/lib/private/Dashboard/Manager.php
index 2aeedf3174e..ba34219a615 100644
--- a/lib/private/Dashboard/Manager.php
+++ b/lib/private/Dashboard/Manager.php
@@ -28,6 +28,7 @@ namespace OC\Dashboard;
use InvalidArgumentException;
use OCP\App\IAppManager;
+use OCP\Dashboard\IConditionalWidget;
use OCP\Dashboard\IManager;
use OCP\Dashboard\IWidget;
use Psr\Container\ContainerExceptionInterface;
@@ -82,15 +83,20 @@ class Manager implements IManager {
* we can not inject it. Thus the static call.
*/
\OC::$server->get(LoggerInterface::class)->critical(
- 'Could not load lazy dashboard widget: ' . $e->getMessage(),
- ['excepiton' => $e]
+ 'Could not load lazy dashboard widget: ' . $service['class'],
+ ['exception' => $e]
);
+ continue;
}
/**
* Try to register the loaded reporter. Theoretically it could be of a wrong
* type, so we might get a TypeError here that we should catch.
*/
try {
+ if ($widget instanceof IConditionalWidget && !$widget->isEnabled()) {
+ continue;
+ }
+
$this->registerWidget($widget);
} catch (Throwable $e) {
/*
@@ -98,9 +104,10 @@ class Manager implements IManager {
* we can not inject it. Thus the static call.
*/
\OC::$server->get(LoggerInterface::class)->critical(
- 'Could not register lazy dashboard widget: ' . $e->getMessage(),
+ 'Could not register lazy dashboard widget: ' . $service['class'],
['exception' => $e]
);
+ continue;
}
try {
@@ -119,9 +126,10 @@ class Manager implements IManager {
}
} catch (Throwable $e) {
\OC::$server->get(LoggerInterface::class)->critical(
- 'Error during dashboard widget loading: ' . $e->getMessage(),
+ 'Error during dashboard widget loading: ' . $service['class'],
['exception' => $e]
);
+ continue;
}
}
$this->lazyWidgets = [];
diff --git a/lib/private/Federation/CloudIdManager.php b/lib/private/Federation/CloudIdManager.php
index e4e42cb1293..85aae8e5ec5 100644
--- a/lib/private/Federation/CloudIdManager.php
+++ b/lib/private/Federation/CloudIdManager.php
@@ -125,6 +125,9 @@ class CloudIdManager implements ICloudIdManager {
if ($lastValidAtPos !== false) {
$user = substr($id, 0, $lastValidAtPos);
$remote = substr($id, $lastValidAtPos + 1);
+
+ $this->userManager->validateUserId($user);
+
if (!empty($user) && !empty($remote)) {
return new CloudId($id, $user, $remote, $this->getDisplayNameFromContact($id));
}
diff --git a/lib/private/Files/Cache/Storage.php b/lib/private/Files/Cache/Storage.php
index f77c9b71dd7..01fc638cef8 100644
--- a/lib/private/Files/Cache/Storage.php
+++ b/lib/private/Files/Cache/Storage.php
@@ -239,7 +239,7 @@ class Storage {
$query = $db->getQueryBuilder();
$query->delete('storages')
- ->where($query->expr()->eq('numeric_id', $query->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)));
+ ->where($query->expr()->in('numeric_id', $query->createNamedParameter($storageIds, IQueryBuilder::PARAM_INT_ARRAY)));
$query->executeStatement();
$query = $db->getQueryBuilder();
diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php
index 268c1d8dd06..bf9ae3c148d 100644
--- a/lib/private/Files/Node/Folder.php
+++ b/lib/private/Files/Node/Folder.php
@@ -66,10 +66,11 @@ class Folder extends Node implements \OCP\Files\Folder {
* @throws \OCP\Files\NotPermittedException
*/
public function getFullPath($path) {
+ $path = $this->normalizePath($path);
if (!$this->isValidPath($path)) {
throw new NotPermittedException('Invalid path');
}
- return $this->path . $this->normalizePath($path);
+ return $this->path . $path;
}
/**
@@ -371,12 +372,12 @@ class Folder extends Node implements \OCP\Files\Folder {
return [$this->root->createNode(
$absolutePath, new \OC\Files\FileInfo(
- $absolutePath,
- $mount->getStorage(),
- $cacheEntry->getPath(),
- $cacheEntry,
- $mount
- ))];
+ $absolutePath,
+ $mount->getStorage(),
+ $cacheEntry->getPath(),
+ $cacheEntry,
+ $mount
+ ))];
}
public function getFreeSpace() {
diff --git a/lib/private/Files/Node/Node.php b/lib/private/Files/Node/Node.php
index bfa4d6861ea..8a752ff281d 100644
--- a/lib/private/Files/Node/Node.php
+++ b/lib/private/Files/Node/Node.php
@@ -99,10 +99,10 @@ class Node implements \OCP\Files\Node {
* @throws NotFoundException
*/
public function getFileInfo() {
- if (!Filesystem::isValidPath($this->path)) {
- throw new InvalidPathException();
- }
if (!$this->fileInfo) {
+ if (!Filesystem::isValidPath($this->path)) {
+ throw new InvalidPathException();
+ }
$fileInfo = $this->view->getFileInfo($this->path);
if ($fileInfo instanceof FileInfo) {
$this->fileInfo = $fileInfo;
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index b7044c2d894..02b65bf37f7 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -61,6 +61,9 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
private $logger;
+ /** @var bool */
+ protected $validateWrites = true;
+
public function __construct($params) {
if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) {
$this->objectStore = $params['objectstore'];
@@ -75,6 +78,9 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
if (isset($params['objectPrefix'])) {
$this->objectPrefix = $params['objectPrefix'];
}
+ if (isset($params['validateWrites'])) {
+ $this->validateWrites = (bool)$params['validateWrites'];
+ }
//initialize cache with root directory in cache
if (!$this->is_dir('/')) {
$this->mkdir('/');
@@ -303,13 +309,23 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
case 'rb':
$stat = $this->stat($path);
if (is_array($stat)) {
+ $filesize = $stat['size'] ?? 0;
// Reading 0 sized files is a waste of time
- if (isset($stat['size']) && $stat['size'] === 0) {
+ if ($filesize === 0) {
return fopen('php://memory', $mode);
}
try {
- return $this->objectStore->readObject($this->getURN($stat['fileid']));
+ $handle = $this->objectStore->readObject($this->getURN($stat['fileid']));
+ if ($handle === false) {
+ return false; // keep backward compatibility
+ }
+ $streamStat = fstat($handle);
+ $actualSize = $streamStat['size'] ?? -1;
+ if ($actualSize > -1 && $actualSize !== $filesize) {
+ $this->getCache()->update((int)$stat['fileid'], ['size' => $actualSize]);
+ }
+ return $handle;
} catch (NotFoundException $e) {
$this->logger->logException($e, [
'app' => 'objectstore',
@@ -522,7 +538,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
if ($exists) {
$this->getCache()->update($fileId, $stat);
} else {
- if ($this->objectStore->objectExists($urn)) {
+ if (!$this->validateWrites || $this->objectStore->objectExists($urn)) {
$this->getCache()->move($uploadPath, $path);
} else {
$this->getCache()->remove($uploadPath);
diff --git a/lib/private/Files/ObjectStore/S3ObjectTrait.php b/lib/private/Files/ObjectStore/S3ObjectTrait.php
index a88ebbeda82..e6a2cf21cd0 100644
--- a/lib/private/Files/ObjectStore/S3ObjectTrait.php
+++ b/lib/private/Files/ObjectStore/S3ObjectTrait.php
@@ -29,9 +29,9 @@ namespace OC\Files\ObjectStore;
use Aws\S3\Exception\S3MultipartUploadException;
use Aws\S3\MultipartUploader;
use Aws\S3\S3Client;
+use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Utils;
use OC\Files\Stream\SeekableHttpStream;
-use GuzzleHttp\Psr7;
use Psr\Http\Message\StreamInterface;
trait S3ObjectTrait {
@@ -47,6 +47,7 @@ trait S3ObjectTrait {
/**
* @param string $urn the unified resource name used to identify the object
+ *
* @return resource stream with the read data
* @throws \Exception when something goes wrong, message will be logged
* @since 7.0.0
@@ -88,6 +89,7 @@ trait S3ObjectTrait {
});
}
+
/**
* Single object put helper
*
diff --git a/lib/private/Files/SetupManager.php b/lib/private/Files/SetupManager.php
index d52a291cd99..ec5cd59d960 100644
--- a/lib/private/Files/SetupManager.php
+++ b/lib/private/Files/SetupManager.php
@@ -40,6 +40,7 @@ use OC_Util;
use OCP\Constants;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IHomeMountProvider;
use OCP\Files\Config\IMountProvider;
use OCP\Files\Config\IUserMountCache;
@@ -414,9 +415,9 @@ class SetupManager {
$mounts = [];
if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
- $setupProviders[] = $cachedMount->getMountProvider();
$currentProviders[] = $cachedMount->getMountProvider();
if ($cachedMount->getMountProvider()) {
+ $setupProviders[] = $cachedMount->getMountProvider();
$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]);
} else {
$this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup");
@@ -427,16 +428,21 @@ class SetupManager {
if ($includeChildren) {
$subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
- foreach ($subCachedMounts as $cachedMount) {
- if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
- $setupProviders[] = $cachedMount->getMountProvider();
- $currentProviders[] = $cachedMount->getMountProvider();
- if ($cachedMount->getMountProvider()) {
+
+ $needsFullSetup = array_reduce($subCachedMounts, function (bool $needsFullSetup, ICachedMountInfo $cachedMountInfo) {
+ return $needsFullSetup || $cachedMountInfo->getMountProvider() === '';
+ }, false);
+
+ if ($needsFullSetup) {
+ $this->logger->debug("mount has no provider set, performing full setup");
+ $this->setupForUser($user);
+ return;
+ } else {
+ foreach ($subCachedMounts as $cachedMount) {
+ if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
+ $currentProviders[] = $cachedMount->getMountProvider();
+ $setupProviders[] = $cachedMount->getMountProvider();
$mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]));
- } else {
- $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup");
- $this->setupForUser($user);
- return;
}
}
}
diff --git a/lib/private/Files/SimpleFS/NewSimpleFile.php b/lib/private/Files/SimpleFS/NewSimpleFile.php
index b2a183b7d29..e2d1ae274b1 100644
--- a/lib/private/Files/SimpleFS/NewSimpleFile.php
+++ b/lib/private/Files/SimpleFS/NewSimpleFile.php
@@ -136,6 +136,10 @@ class NewSimpleFile implements ISimpleFile {
* @throws NotFoundException
*/
private function checkFile(): void {
+ if (!$this->file) {
+ throw new NotFoundException('File not set');
+ }
+
$cur = $this->file;
while ($cur->stat() === false) {
diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php
index ff157bfe7f6..9daa3517825 100644
--- a/lib/private/Files/Storage/Local.php
+++ b/lib/private/Files/Storage/Local.php
@@ -170,11 +170,6 @@ class Local extends \OC\Files\Storage\Common {
return false;
}
$statResult = @stat($fullPath);
- if (PHP_INT_SIZE === 4 && $statResult && !$this->is_dir($path)) {
- $filesize = $this->filesize($path);
- $statResult['size'] = $filesize;
- $statResult[7] = $filesize;
- }
if (is_array($statResult)) {
$statResult['full_path'] = $fullPath;
}
@@ -246,10 +241,6 @@ class Local extends \OC\Files\Storage\Common {
return 0;
}
$fullPath = $this->getSourcePath($path);
- if (PHP_INT_SIZE === 4) {
- $helper = new \OC\LargeFileHelper;
- return $helper->getFileSize($fullPath);
- }
return filesize($fullPath);
}
@@ -271,10 +262,6 @@ class Local extends \OC\Files\Storage\Common {
if (!$this->file_exists($path)) {
return false;
}
- if (PHP_INT_SIZE === 4) {
- $helper = new \OC\LargeFileHelper();
- return $helper->getFileMtime($fullPath);
- }
return filemtime($fullPath);
}
@@ -421,7 +408,7 @@ class Local extends \OC\Files\Storage\Common {
if ($space === false || is_null($space)) {
return \OCP\Files\FileInfo::SPACE_UNKNOWN;
}
- return $space;
+ return (int)$space;
}
public function search($query) {
diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php
index 0f1838c97c8..9cc8b238ee1 100644
--- a/lib/private/Files/Stream/Encryption.php
+++ b/lib/private/Files/Stream/Encryption.php
@@ -465,7 +465,7 @@ class Encryption extends Wrapper {
$cacheEntry = $cache->get($this->internalPath);
if ($cacheEntry) {
$version = $cacheEntry['encryptedVersion'] + 1;
- $cache->update($cacheEntry->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]);
+ $cache->update($cacheEntry->getId(), ['encrypted' => $version, 'encryptedVersion' => $version, 'unencrypted_size' => $this->unencryptedSize]);
}
}
@@ -528,6 +528,7 @@ class Encryption extends Wrapper {
*/
protected function writeHeader() {
$header = $this->util->createHeader($this->newHeader, $this->encryptionModule);
+ $this->fileUpdated = true;
return parent::stream_write($header);
}
diff --git a/lib/private/Files/Stream/SeekableHttpStream.php b/lib/private/Files/Stream/SeekableHttpStream.php
index df37fd29f42..51ccaeba998 100644
--- a/lib/private/Files/Stream/SeekableHttpStream.php
+++ b/lib/private/Files/Stream/SeekableHttpStream.php
@@ -75,8 +75,12 @@ class SeekableHttpStream implements File {
/** @var ?resource|closed-resource */
private $current;
+ /** @var int $offset offset of the current chunk */
private int $offset = 0;
+ /** @var int $length length of the current chunk */
private int $length = 0;
+ /** @var int $totalSize size of the full stream */
+ private int $totalSize = 0;
private bool $needReconnect = false;
private function reconnect(int $start): bool {
@@ -128,6 +132,9 @@ class SeekableHttpStream implements File {
$this->offset = $begin;
$this->length = $length;
+ if ($start === 0) {
+ $this->totalSize = $length;
+ }
return true;
}
@@ -211,7 +218,9 @@ class SeekableHttpStream implements File {
public function stream_stat() {
if ($this->getCurrent()) {
- return fstat($this->getCurrent());
+ $stat = fstat($this->getCurrent());
+ $stat['size'] = $this->totalSize;
+ return $stat;
} else {
return false;
}
diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php
index 0abc870dc6f..16a9381768b 100644
--- a/lib/private/Files/View.php
+++ b/lib/private/Files/View.php
@@ -1191,13 +1191,13 @@ class View {
throw $e;
}
- if ($result && in_array('delete', $hooks)) {
+ if ($result !== false && in_array('delete', $hooks)) {
$this->removeUpdate($storage, $internalPath);
}
- if ($result && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') {
+ if ($result !== false && in_array('write', $hooks, true) && $operation !== 'fopen' && $operation !== 'touch') {
$this->writeUpdate($storage, $internalPath);
}
- if ($result && in_array('touch', $hooks)) {
+ if ($result !== false && in_array('touch', $hooks)) {
$this->writeUpdate($storage, $internalPath, $extraParam);
}
diff --git a/lib/private/Group/DisplayNameCache.php b/lib/private/Group/DisplayNameCache.php
new file mode 100644
index 00000000000..d724b6caf0e
--- /dev/null
+++ b/lib/private/Group/DisplayNameCache.php
@@ -0,0 +1,87 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2022 Anna Larch <anna.larch@gmx.net>
+ * @author Anna Larch <anna.larch@gmx.net>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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/>
+ *
+ */
+
+
+namespace OC\Group;
+
+use OCP\Cache\CappedMemoryCache;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\Group\Events\GroupChangedEvent;
+use OCP\ICache;
+use OCP\ICacheFactory;
+use OCP\IGroupManager;
+
+/**
+ * Class that cache the relation Group ID -> Display name
+ *
+ * This saves fetching the group from the backend for "just" the display name
+ */
+class DisplayNameCache implements IEventListener {
+ private CappedMemoryCache $cache;
+ private ICache $memCache;
+ private IGroupManager $groupManager;
+
+ public function __construct(ICacheFactory $cacheFactory, IGroupManager $groupManager) {
+ $this->cache = new CappedMemoryCache();
+ $this->memCache = $cacheFactory->createDistributed('groupDisplayNameMappingCache');
+ $this->groupManager = $groupManager;
+ }
+
+ public function getDisplayName(string $groupId): ?string {
+ if (isset($this->cache[$groupId])) {
+ return $this->cache[$groupId];
+ }
+ $displayName = $this->memCache->get($groupId);
+ if ($displayName) {
+ $this->cache[$groupId] = $displayName;
+ return $displayName;
+ }
+
+ $group = $this->groupManager->get($groupId);
+ if ($group) {
+ $displayName = $group->getDisplayName();
+ } else {
+ $displayName = null;
+ }
+ $this->cache[$groupId] = $displayName;
+ $this->memCache->set($groupId, $displayName, 60 * 10); // 10 minutes
+
+ return $displayName;
+ }
+
+ public function clear(): void {
+ $this->cache = new CappedMemoryCache();
+ $this->memCache->clear();
+ }
+
+ public function handle(Event $event): void {
+ if ($event instanceof GroupChangedEvent && $event->getFeature() === 'displayName') {
+ $groupId = $event->getGroup()->getGID();
+ $newDisplayName = $event->getValue();
+ $this->cache[$groupId] = $newDisplayName;
+ $this->memCache->set($groupId, $newDisplayName, 60 * 10); // 10 minutes
+ }
+ }
+}
diff --git a/lib/private/Group/Group.php b/lib/private/Group/Group.php
index 2ef4d2ee23f..ae70a611e4e 100644
--- a/lib/private/Group/Group.php
+++ b/lib/private/Group/Group.php
@@ -38,6 +38,7 @@ use OCP\Group\Backend\IGetDisplayNameBackend;
use OCP\Group\Backend\IHideFromCollaborationBackend;
use OCP\Group\Backend\INamedBackend;
use OCP\Group\Backend\ISetDisplayNameBackend;
+use OCP\Group\Events\GroupChangedEvent;
use OCP\GroupInterface;
use OCP\IGroup;
use OCP\IUser;
@@ -112,6 +113,7 @@ class Group implements IGroup {
if (($backend instanceof ISetDisplayNameBackend)
&& $backend->setDisplayName($this->gid, $displayName)) {
$this->displayName = $displayName;
+ $this->dispatcher->dispatch(new GroupChangedEvent($this, 'displayName', $displayName, ''));
return true;
}
}
diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php
index 28f7a400b41..b718afa5168 100644
--- a/lib/private/Group/Manager.php
+++ b/lib/private/Group/Manager.php
@@ -42,6 +42,7 @@ namespace OC\Group;
use OC\Hooks\PublicEmitter;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\GroupInterface;
+use OCP\ICacheFactory;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
@@ -82,12 +83,16 @@ class Manager extends PublicEmitter implements IGroupManager {
/** @var \OC\SubAdmin */
private $subAdmin = null;
+ private DisplayNameCache $displayNameCache;
+
public function __construct(\OC\User\Manager $userManager,
EventDispatcherInterface $dispatcher,
- LoggerInterface $logger) {
+ LoggerInterface $logger,
+ ICacheFactory $cacheFactory) {
$this->userManager = $userManager;
$this->dispatcher = $dispatcher;
$this->logger = $logger;
+ $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);
$cachedGroups = &$this->cachedGroups;
$cachedUserGroups = &$this->cachedUserGroups;
@@ -339,6 +344,14 @@ class Manager extends PublicEmitter implements IGroupManager {
}
/**
+ * @param string $groupId
+ * @return ?string
+ */
+ public function getDisplayName(string $groupId): ?string {
+ return $this->displayNameCache->getDisplayName($groupId);
+ }
+
+ /**
* get an array of groupid and displayName for a user
*
* @param IUser $user
@@ -346,7 +359,7 @@ class Manager extends PublicEmitter implements IGroupManager {
*/
public function getUserGroupNames(IUser $user) {
return array_map(function ($group) {
- return ['displayName' => $group->getDisplayName()];
+ return ['displayName' => $this->displayNameCache->getDisplayName($group->getGID())];
}, $this->getUserGroups($user));
}
diff --git a/lib/private/Http/Client/Client.php b/lib/private/Http/Client/Client.php
index 4bf7fd02400..2e370395132 100644
--- a/lib/private/Http/Client/Client.php
+++ b/lib/private/Http/Client/Client.php
@@ -37,8 +37,11 @@ use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\RequestOptions;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IResponse;
+use OCP\Http\Client\LocalServerException;
use OCP\ICertificateManager;
use OCP\IConfig;
+use OCP\Security\IRemoteHostValidator;
+use function parse_url;
/**
* Class Client
@@ -52,19 +55,18 @@ class Client implements IClient {
private $config;
/** @var ICertificateManager */
private $certificateManager;
- /** @var LocalAddressChecker */
- private $localAddressChecker;
+ private IRemoteHostValidator $remoteHostValidator;
public function __construct(
IConfig $config,
ICertificateManager $certificateManager,
GuzzleClient $client,
- LocalAddressChecker $localAddressChecker
+ IRemoteHostValidator $remoteHostValidator
) {
$this->config = $config;
$this->client = $client;
$this->certificateManager = $certificateManager;
- $this->localAddressChecker = $localAddressChecker;
+ $this->remoteHostValidator = $remoteHostValidator;
}
private function buildRequestOptions(array $options): array {
@@ -181,7 +183,13 @@ class Client implements IClient {
return;
}
- $this->localAddressChecker->ThrowIfLocalAddress($uri);
+ $host = parse_url($uri, PHP_URL_HOST);
+ if ($host === false || $host === null) {
+ throw new LocalServerException('Could not detect any host');
+ }
+ if (!$this->remoteHostValidator->isValid($host)) {
+ throw new LocalServerException('Host violates local access rules');
+ }
}
/**
diff --git a/lib/private/Http/Client/ClientService.php b/lib/private/Http/Client/ClientService.php
index e868d4af7a5..bbc2330176f 100644
--- a/lib/private/Http/Client/ClientService.php
+++ b/lib/private/Http/Client/ClientService.php
@@ -33,6 +33,7 @@ use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\ICertificateManager;
use OCP\IConfig;
+use OCP\Security\IRemoteHostValidator;
/**
* Class ClientService
@@ -46,17 +47,16 @@ class ClientService implements IClientService {
private $certificateManager;
/** @var DnsPinMiddleware */
private $dnsPinMiddleware;
- /** @var LocalAddressChecker */
- private $localAddressChecker;
+ private IRemoteHostValidator $remoteHostValidator;
public function __construct(IConfig $config,
ICertificateManager $certificateManager,
DnsPinMiddleware $dnsPinMiddleware,
- LocalAddressChecker $localAddressChecker) {
+ IRemoteHostValidator $remoteHostValidator) {
$this->config = $config;
$this->certificateManager = $certificateManager;
$this->dnsPinMiddleware = $dnsPinMiddleware;
- $this->localAddressChecker = $localAddressChecker;
+ $this->remoteHostValidator = $remoteHostValidator;
}
/**
@@ -73,7 +73,7 @@ class ClientService implements IClientService {
$this->config,
$this->certificateManager,
$client,
- $this->localAddressChecker
+ $this->remoteHostValidator,
);
}
}
diff --git a/lib/private/Http/Client/DnsPinMiddleware.php b/lib/private/Http/Client/DnsPinMiddleware.php
index f5e6214a4ab..c6a58972fdd 100644
--- a/lib/private/Http/Client/DnsPinMiddleware.php
+++ b/lib/private/Http/Client/DnsPinMiddleware.php
@@ -25,20 +25,21 @@ declare(strict_types=1);
*/
namespace OC\Http\Client;
+use OC\Net\IpAddressClassifier;
+use OCP\Http\Client\LocalServerException;
use Psr\Http\Message\RequestInterface;
class DnsPinMiddleware {
/** @var NegativeDnsCache */
private $negativeDnsCache;
- /** @var LocalAddressChecker */
- private $localAddressChecker;
+ private IpAddressClassifier $ipAddressClassifier;
public function __construct(
NegativeDnsCache $negativeDnsCache,
- LocalAddressChecker $localAddressChecker
+ IpAddressClassifier $ipAddressClassifier
) {
$this->negativeDnsCache = $negativeDnsCache;
- $this->localAddressChecker = $localAddressChecker;
+ $this->ipAddressClassifier = $ipAddressClassifier;
}
/**
@@ -133,7 +134,10 @@ class DnsPinMiddleware {
$curlResolves["$hostName:$port"] = [];
foreach ($targetIps as $ip) {
- $this->localAddressChecker->ThrowIfLocalIp($ip);
+ if ($this->ipAddressClassifier->isLocalAddress($ip)) {
+ // TODO: continue with all non-local IPs?
+ throw new LocalServerException('Host violates local access rules');
+ }
$curlResolves["$hostName:$port"][] = $ip;
}
}
diff --git a/lib/private/Http/Client/LocalAddressChecker.php b/lib/private/Http/Client/LocalAddressChecker.php
deleted file mode 100644
index 13a7d062de3..00000000000
--- a/lib/private/Http/Client/LocalAddressChecker.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2021, Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * 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
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-namespace OC\Http\Client;
-
-use IPLib\Address\IPv6;
-use IPLib\Factory;
-use IPLib\ParseStringFlag;
-use OCP\Http\Client\LocalServerException;
-use Psr\Log\LoggerInterface;
-use Symfony\Component\HttpFoundation\IpUtils;
-
-class LocalAddressChecker {
- private LoggerInterface $logger;
-
- public function __construct(LoggerInterface $logger) {
- $this->logger = $logger;
- }
-
- public function ThrowIfLocalIp(string $ip) : void {
- $parsedIp = Factory::parseAddressString(
- $ip,
- ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
- );
- if ($parsedIp === null) {
- /* Not an IP */
- return;
- }
- /* Replace by normalized form */
- if ($parsedIp instanceof IPv6) {
- $ip = (string)($parsedIp->toIPv4() ?? $parsedIp);
- } else {
- $ip = (string)$parsedIp;
- }
-
- $localRanges = [
- '100.64.0.0/10', // See RFC 6598
- '192.0.0.0/24', // See RFC 6890
- ];
- if (
- (bool)filter_var($ip, FILTER_VALIDATE_IP) &&
- (
- !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) ||
- IpUtils::checkIp($ip, $localRanges)
- )) {
- $this->logger->warning("Host $ip was not connected to because it violates local access rules");
- throw new LocalServerException('Host violates local access rules');
- }
- }
-
- public function ThrowIfLocalAddress(string $uri) : void {
- $host = parse_url($uri, PHP_URL_HOST);
- if ($host === false || $host === null) {
- $this->logger->warning("Could not detect any host in $uri");
- throw new LocalServerException('Could not detect any host');
- }
-
- $host = idn_to_utf8(strtolower(urldecode($host)));
- // Remove brackets from IPv6 addresses
- if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
- $host = substr($host, 1, -1);
- }
-
- // Disallow local network top-level domains from RFC 6762
- $localTopLevelDomains = ['local','localhost','intranet','internal','private','corp','home','lan'];
- $topLevelDomain = substr((strrchr($host, '.') ?: ''), 1);
- if (in_array($topLevelDomain, $localTopLevelDomains)) {
- $this->logger->warning("Host $host was not connected to because it violates local access rules");
- throw new LocalServerException('Host violates local access rules');
- }
-
- // Disallow hostname only
- if (substr_count($host, '.') === 0 && !(bool)filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
- $this->logger->warning("Host $host was not connected to because it violates local access rules");
- throw new LocalServerException('Host violates local access rules');
- }
-
- $this->ThrowIfLocalIp($host);
- }
-}
diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php
index 7fcfab37fa6..71910873db7 100644
--- a/lib/private/L10N/Factory.php
+++ b/lib/private/L10N/Factory.php
@@ -40,6 +40,8 @@ declare(strict_types=1);
namespace OC\L10N;
+use OCP\ICache;
+use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUser;
@@ -94,7 +96,9 @@ class Factory implements IFactory {
protected $request;
/** @var IUserSession */
- protected $userSession;
+ protected IUserSession $userSession;
+
+ private ICache $cache;
/** @var string */
protected $serverRoot;
@@ -109,11 +113,13 @@ class Factory implements IFactory {
IConfig $config,
IRequest $request,
IUserSession $userSession,
+ ICacheFactory $cacheFactory,
$serverRoot
) {
$this->config = $config;
$this->request = $request;
$this->userSession = $userSession;
+ $this->cache = $cacheFactory->createLocal('L10NFactory');
$this->serverRoot = $serverRoot;
}
@@ -338,6 +344,10 @@ class Factory implements IFactory {
$key = 'null';
}
+ if ($availableLanguages = $this->cache->get($key)) {
+ $this->availableLanguages[$key] = $availableLanguages;
+ }
+
// also works with null as key
if (!empty($this->availableLanguages[$key])) {
return $this->availableLanguages[$key];
@@ -374,6 +384,7 @@ class Factory implements IFactory {
}
$this->availableLanguages[$key] = $available;
+ $this->cache->set($key, $available, 60);
return $available;
}
diff --git a/lib/private/Log/ErrorHandler.php b/lib/private/Log/ErrorHandler.php
index d56fecb1ecb..c4b9631e75a 100644
--- a/lib/private/Log/ErrorHandler.php
+++ b/lib/private/Log/ErrorHandler.php
@@ -1,4 +1,7 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
@@ -27,84 +30,75 @@
*/
namespace OC\Log;
+use Error;
use OCP\ILogger;
+use Psr\Log\LoggerInterface;
+use Throwable;
class ErrorHandler {
- /** @var ILogger */
- private static $logger;
+ private LoggerInterface $logger;
+
+ public function __construct(LoggerInterface $logger) {
+ $this->logger = $logger;
+ }
/**
- * remove password in URLs
- * @param string $msg
- * @return string
+ * Remove password in URLs
*/
- protected static function removePassword($msg) {
+ private static function removePassword(string $msg): string {
return preg_replace('#//(.*):(.*)@#', '//xxx:xxx@', $msg);
}
- public static function register($debug = false) {
- $handler = new ErrorHandler();
-
- if ($debug) {
- set_error_handler([$handler, 'onAll'], E_ALL);
- if (\OC::$CLI) {
- set_exception_handler(['OC_Template', 'printExceptionErrorPage']);
- }
- } else {
- set_error_handler([$handler, 'onError']);
- }
- register_shutdown_function([$handler, 'onShutdown']);
- set_exception_handler([$handler, 'onException']);
- }
-
- public static function setLogger(ILogger $logger) {
- self::$logger = $logger;
- }
-
- //Fatal errors handler
- public static function onShutdown() {
+ /**
+ * Fatal errors handler
+ */
+ public function onShutdown(): void {
$error = error_get_last();
- if ($error && self::$logger) {
- //ob_end_clean();
+ if ($error) {
$msg = $error['message'] . ' at ' . $error['file'] . '#' . $error['line'];
- self::$logger->critical(self::removePassword($msg), ['app' => 'PHP']);
+ $this->logger->critical(self::removePassword($msg), ['app' => 'PHP']);
}
}
/**
- * Uncaught exception handler
- *
- * @param \Exception $exception
+ * Uncaught exception handler
*/
- public static function onException($exception) {
+ public function onException(Throwable $exception): void {
$class = get_class($exception);
$msg = $exception->getMessage();
$msg = "$class: $msg at " . $exception->getFile() . '#' . $exception->getLine();
- self::$logger->critical(self::removePassword($msg), ['app' => 'PHP']);
+ $this->logger->critical(self::removePassword($msg), ['app' => 'PHP']);
}
- //Recoverable errors handler
- public static function onError($number, $message, $file, $line) {
+ /**
+ * Recoverable errors handler
+ */
+ public function onError(int $number, string $message, string $file, int $line): bool {
if (!(error_reporting() & $number)) {
- return;
+ return true;
}
$msg = $message . ' at ' . $file . '#' . $line;
- $e = new \Error(self::removePassword($msg));
- self::$logger->logException($e, ['app' => 'PHP', 'level' => self::errnoToLogLevel($number)]);
+ $e = new Error(self::removePassword($msg));
+ $this->logger->log(self::errnoToLogLevel($number), $e->getMessage(), ['app' => 'PHP']);
+ return true;
}
- //Recoverable handler which catch all errors, warnings and notices
- public static function onAll($number, $message, $file, $line) {
+ /**
+ * Recoverable handler which catch all errors, warnings and notices
+ */
+ public function onAll(int $number, string $message, string $file, int $line): bool {
$msg = $message . ' at ' . $file . '#' . $line;
- $e = new \Error(self::removePassword($msg));
- self::$logger->logException($e, ['app' => 'PHP', 'level' => self::errnoToLogLevel($number)]);
+ $e = new Error(self::removePassword($msg));
+ $this->logger->log(self::errnoToLogLevel($number), $e->getMessage(), ['app' => 'PHP']);
+ return true;
}
- public static function errnoToLogLevel(int $errno): int {
+ private static function errnoToLogLevel(int $errno): int {
switch ($errno) {
case E_USER_WARNING:
return ILogger::WARN;
+ case E_DEPRECATED:
case E_USER_DEPRECATED:
return ILogger::DEBUG;
diff --git a/lib/private/Log/Errorlog.php b/lib/private/Log/Errorlog.php
index d27759d7050..7fca04d8b34 100644
--- a/lib/private/Log/Errorlog.php
+++ b/lib/private/Log/Errorlog.php
@@ -1,4 +1,7 @@
<?php
+
+declare(strict_types=1);
+
/**
* The MIT License (MIT)
*
@@ -32,7 +35,7 @@ class Errorlog implements IWriter {
/** @var string */
protected $tag;
- public function __construct(string $tag = 'owncloud') {
+ public function __construct(string $tag = 'nextcloud') {
$this->tag = $tag;
}
diff --git a/lib/private/Mail/Message.php b/lib/private/Mail/Message.php
index c1b08b4e231..8b94f44ddba 100644
--- a/lib/private/Mail/Message.php
+++ b/lib/private/Mail/Message.php
@@ -78,14 +78,18 @@ class Message implements IMessage {
$convertedAddresses = [];
foreach ($addresses as $email => $readableName) {
- if (!is_numeric($email)) {
- [$name, $domain] = explode('@', $email, 2);
- $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
- $convertedAddresses[$name.'@'.$domain] = $readableName;
+ $parsableEmail = is_numeric($email) ? $readableName : $email;
+ if (strpos($parsableEmail, '@') === false) {
+ $convertedAddresses[$parsableEmail] = $readableName;
+ continue;
+ }
+
+ [$name, $domain] = explode('@', $parsableEmail, 2);
+ $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
+ if (is_numeric($email)) {
+ $convertedAddresses[] = $name . '@' . $domain;
} else {
- [$name, $domain] = explode('@', $readableName, 2);
- $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
- $convertedAddresses[$email] = $name.'@'.$domain;
+ $convertedAddresses[$name . '@' . $domain] = $readableName;
}
}
diff --git a/lib/private/Memcache/Redis.php b/lib/private/Memcache/Redis.php
index de38033ca32..f4094e0bef6 100644
--- a/lib/private/Memcache/Redis.php
+++ b/lib/private/Memcache/Redis.php
@@ -33,20 +33,28 @@ use OCP\IMemcacheTTL;
class Redis extends Cache implements IMemcacheTTL {
/**
- * @var \Redis $cache
+ * @var \Redis|\RedisCluster $cache
*/
private static $cache = null;
public function __construct($prefix = '', string $logFile = '') {
parent::__construct($prefix);
+ }
+
+ /**
+ * @return \Redis|\RedisCluster|null
+ * @throws \Exception
+ */
+ public function getCache() {
if (is_null(self::$cache)) {
self::$cache = \OC::$server->getGetRedisFactory()->getInstance();
}
+ return self::$cache;
}
public function get($key) {
- $result = self::$cache->get($this->getPrefix() . $key);
- if ($result === false && !self::$cache->exists($this->getPrefix() . $key)) {
+ $result = $this->getCache()->get($this->getPrefix() . $key);
+ if ($result === false && !$this->getCache()->exists($this->getPrefix() . $key)) {
return null;
} else {
return json_decode($result, true);
@@ -55,18 +63,18 @@ class Redis extends Cache implements IMemcacheTTL {
public function set($key, $value, $ttl = 0) {
if ($ttl > 0) {
- return self::$cache->setex($this->getPrefix() . $key, $ttl, json_encode($value));
+ return $this->getCache()->setex($this->getPrefix() . $key, $ttl, json_encode($value));
} else {
- return self::$cache->set($this->getPrefix() . $key, json_encode($value));
+ return $this->getCache()->set($this->getPrefix() . $key, json_encode($value));
}
}
public function hasKey($key) {
- return (bool)self::$cache->exists($this->getPrefix() . $key);
+ return (bool)$this->getCache()->exists($this->getPrefix() . $key);
}
public function remove($key) {
- if (self::$cache->del($this->getPrefix() . $key)) {
+ if ($this->getCache()->del($this->getPrefix() . $key)) {
return true;
} else {
return false;
@@ -75,8 +83,8 @@ class Redis extends Cache implements IMemcacheTTL {
public function clear($prefix = '') {
$prefix = $this->getPrefix() . $prefix . '*';
- $keys = self::$cache->keys($prefix);
- $deleted = self::$cache->del($keys);
+ $keys = $this->getCache()->keys($prefix);
+ $deleted = $this->getCache()->del($keys);
return (is_array($keys) && (count($keys) === $deleted));
}
@@ -100,7 +108,7 @@ class Redis extends Cache implements IMemcacheTTL {
$args['ex'] = $ttl;
}
- return self::$cache->set($this->getPrefix() . $key, $value, $args);
+ return $this->getCache()->set($this->getPrefix() . $key, (string)$value, $args);
}
/**
@@ -111,7 +119,7 @@ class Redis extends Cache implements IMemcacheTTL {
* @return int | bool
*/
public function inc($key, $step = 1) {
- return self::$cache->incrBy($this->getPrefix() . $key, $step);
+ return $this->getCache()->incrBy($this->getPrefix() . $key, $step);
}
/**
@@ -125,7 +133,7 @@ class Redis extends Cache implements IMemcacheTTL {
if (!$this->hasKey($key)) {
return false;
}
- return self::$cache->decrBy($this->getPrefix() . $key, $step);
+ return $this->getCache()->decrBy($this->getPrefix() . $key, $step);
}
/**
@@ -140,14 +148,14 @@ class Redis extends Cache implements IMemcacheTTL {
if (!is_int($new)) {
$new = json_encode($new);
}
- self::$cache->watch($this->getPrefix() . $key);
+ $this->getCache()->watch($this->getPrefix() . $key);
if ($this->get($key) === $old) {
- $result = self::$cache->multi()
+ $result = $this->getCache()->multi()
->set($this->getPrefix() . $key, $new)
->exec();
return $result !== false;
}
- self::$cache->unwatch();
+ $this->getCache()->unwatch();
return false;
}
@@ -159,19 +167,19 @@ class Redis extends Cache implements IMemcacheTTL {
* @return bool
*/
public function cad($key, $old) {
- self::$cache->watch($this->getPrefix() . $key);
+ $this->getCache()->watch($this->getPrefix() . $key);
if ($this->get($key) === $old) {
- $result = self::$cache->multi()
+ $result = $this->getCache()->multi()
->del($this->getPrefix() . $key)
->exec();
return $result !== false;
}
- self::$cache->unwatch();
+ $this->getCache()->unwatch();
return false;
}
public function setTTL($key, $ttl) {
- self::$cache->expire($this->getPrefix() . $key, $ttl);
+ $this->getCache()->expire($this->getPrefix() . $key, $ttl);
}
public static function isAvailable(): bool {
diff --git a/lib/private/Metadata/FileMetadata.php b/lib/private/Metadata/FileMetadata.php
index 7d1db21cf39..9ad0f9d35c6 100644
--- a/lib/private/Metadata/FileMetadata.php
+++ b/lib/private/Metadata/FileMetadata.php
@@ -28,7 +28,7 @@ use OCP\DB\Types;
/**
* @method string getGroupName()
* @method void setGroupName(string $groupName)
- * @method string getMetadata()
+ * @method array getMetadata()
* @method void setMetadata(array $metadata)
* @see \OC\Core\Migrations\Version240000Date20220404230027
*/
diff --git a/lib/private/NavigationManager.php b/lib/private/NavigationManager.php
index 7e162e65a5d..b78d9fa1ed8 100644
--- a/lib/private/NavigationManager.php
+++ b/lib/private/NavigationManager.php
@@ -193,7 +193,7 @@ class NavigationManager implements INavigationManager {
$this->add([
'type' => 'settings',
'id' => 'help',
- 'order' => 6,
+ 'order' => 99998,
'href' => $this->urlGenerator->linkToRoute('settings.Help.help'),
'name' => $l->t('Help'),
'icon' => $this->urlGenerator->imagePath('settings', 'help.svg'),
diff --git a/lib/private/Net/HostnameClassifier.php b/lib/private/Net/HostnameClassifier.php
new file mode 100644
index 00000000000..626aa47083e
--- /dev/null
+++ b/lib/private/Net/HostnameClassifier.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\Net;
+
+use function filter_var;
+use function in_array;
+use function strrchr;
+use function substr;
+use function substr_count;
+
+/**
+ * Classifier for network hostnames
+ *
+ * @internal
+ */
+class HostnameClassifier {
+ private const LOCAL_TOPLEVEL_DOMAINS = [
+ 'local',
+ 'localhost',
+ 'intranet',
+ 'internal',
+ 'private',
+ 'corp',
+ 'home',
+ 'lan',
+ ];
+
+ /**
+ * Check host identifier for local hostname
+ *
+ * IP addresses are not considered local. Use the IpAddressClassifier for those.
+ *
+ * @param string $hostname
+ *
+ * @return bool
+ */
+ public function isLocalHostname(string $hostname): bool {
+ // Disallow local network top-level domains from RFC 6762
+ $topLevelDomain = substr((strrchr($hostname, '.') ?: ''), 1);
+ if (in_array($topLevelDomain, self::LOCAL_TOPLEVEL_DOMAINS)) {
+ return true;
+ }
+
+ // Disallow hostname only
+ if (substr_count($hostname, '.') === 0 && !filter_var($hostname, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/lib/private/Net/IpAddressClassifier.php b/lib/private/Net/IpAddressClassifier.php
new file mode 100644
index 00000000000..d4698864ec8
--- /dev/null
+++ b/lib/private/Net/IpAddressClassifier.php
@@ -0,0 +1,81 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\Net;
+
+use IPLib\Address\IPv6;
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+use Symfony\Component\HttpFoundation\IpUtils;
+use function filter_var;
+
+/**
+ * Classifier for IP addresses
+ *
+ * @internal
+ */
+class IpAddressClassifier {
+ private const LOCAL_ADDRESS_RANGES = [
+ '100.64.0.0/10', // See RFC 6598
+ '192.0.0.0/24', // See RFC 6890
+ ];
+
+ /**
+ * Check host identifier for local IPv4 and IPv6 address ranges
+ *
+ * Hostnames are not considered local. Use the HostnameClassifier for those.
+ *
+ * @param string $ip
+ *
+ * @return bool
+ */
+ public function isLocalAddress(string $ip): bool {
+ $parsedIp = Factory::parseAddressString(
+ $ip,
+ ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
+ );
+ if ($parsedIp === null) {
+ /* Not an IP */
+ return false;
+ }
+ /* Replace by normalized form */
+ if ($parsedIp instanceof IPv6) {
+ $ip = (string)($parsedIp->toIPv4() ?? $parsedIp);
+ } else {
+ $ip = (string)$parsedIp;
+ }
+
+ if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
+ /* Range address */
+ return true;
+ }
+ if (IpUtils::checkIp($ip, self::LOCAL_ADDRESS_RANGES)) {
+ /* Within local range */
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/lib/private/Notification/Manager.php b/lib/private/Notification/Manager.php
index d758cae428f..3d77f643d93 100644
--- a/lib/private/Notification/Manager.php
+++ b/lib/private/Notification/Manager.php
@@ -305,7 +305,7 @@ class Manager implements IManager {
* users overload our infrastructure. For this reason we have to rate-limit the
* use of push notifications. If you need this feature, consider using Nextcloud Enterprise.
*/
- $isFairUse = $this->subscription->delegateHasValidSubscription() || $this->userManager->countSeenUsers() < 500;
+ $isFairUse = $this->subscription->delegateHasValidSubscription() || $this->userManager->countSeenUsers() < 1000;
$pushAllowed = $isFairUse ? 'yes' : 'no';
$this->cache->set('push_fair_use', $pushAllowed, 3600);
}
diff --git a/lib/private/Preview/BackgroundCleanupJob.php b/lib/private/Preview/BackgroundCleanupJob.php
index ab40aeaaa79..9a493384f11 100644
--- a/lib/private/Preview/BackgroundCleanupJob.php
+++ b/lib/private/Preview/BackgroundCleanupJob.php
@@ -25,8 +25,9 @@ declare(strict_types=1);
*/
namespace OC\Preview;
-use OC\BackgroundJob\TimedJob;
use OC\Preview\Storage\Root;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\BackgroundJob\TimedJob;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\IMimeTypeLoader;
use OCP\Files\NotFoundException;
@@ -34,7 +35,6 @@ use OCP\Files\NotPermittedException;
use OCP\IDBConnection;
class BackgroundCleanupJob extends TimedJob {
-
/** @var IDBConnection */
private $connection;
@@ -47,10 +47,12 @@ class BackgroundCleanupJob extends TimedJob {
/** @var IMimeTypeLoader */
private $mimeTypeLoader;
- public function __construct(IDBConnection $connection,
+ public function __construct(ITimeFactory $timeFactory,
+ IDBConnection $connection,
Root $previewFolder,
IMimeTypeLoader $mimeTypeLoader,
bool $isCLI) {
+ parent::__construct($timeFactory);
// Run at most once an hour
$this->setInterval(3600);
diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php
index ef44188da74..7d2408d683f 100644
--- a/lib/private/Preview/Generator.php
+++ b/lib/private/Preview/Generator.php
@@ -29,6 +29,7 @@
*/
namespace OC\Preview;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\Files\IAppData;
use OCP\Files\InvalidPathException;
@@ -40,12 +41,15 @@ use OCP\IConfig;
use OCP\IImage;
use OCP\IPreview;
use OCP\IStreamImage;
+use OCP\Preview\BeforePreviewFetchedEvent;
use OCP\Preview\IProviderV2;
use OCP\Preview\IVersionedPreviewFile;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
class Generator {
+ public const SEMAPHORE_ID_ALL = 0x0a11;
+ public const SEMAPHORE_ID_NEW = 0x07ea;
/** @var IPreview */
private $previewManager;
@@ -56,26 +60,23 @@ class Generator {
/** @var GeneratorHelper */
private $helper;
/** @var EventDispatcherInterface */
+ private $legacyEventDispatcher;
+ /** @var IEventDispatcher */
private $eventDispatcher;
- /**
- * @param IConfig $config
- * @param IPreview $previewManager
- * @param IAppData $appData
- * @param GeneratorHelper $helper
- * @param EventDispatcherInterface $eventDispatcher
- */
public function __construct(
IConfig $config,
IPreview $previewManager,
IAppData $appData,
GeneratorHelper $helper,
- EventDispatcherInterface $eventDispatcher
+ EventDispatcherInterface $legacyEventDispatcher,
+ IEventDispatcher $eventDispatcher
) {
$this->config = $config;
$this->previewManager = $previewManager;
$this->appData = $appData;
$this->helper = $helper;
+ $this->legacyEventDispatcher = $legacyEventDispatcher;
$this->eventDispatcher = $eventDispatcher;
}
@@ -102,10 +103,14 @@ class Generator {
'crop' => $crop,
'mode' => $mode,
];
- $this->eventDispatcher->dispatch(
+
+ $this->legacyEventDispatcher->dispatch(
IPreview::EVENT,
new GenericEvent($file, $specification)
);
+ $this->eventDispatcher->dispatchTyped(new BeforePreviewFetchedEvent(
+ $file
+ ));
// since we only ask for one preview, and the generate method return the last one it created, it returns the one we want
return $this->generatePreviews($file, [$specification], $mimeType);
@@ -300,6 +305,98 @@ class Generator {
}
/**
+ * Acquire a semaphore of the specified id and concurrency, blocking if necessary.
+ * Return an identifier of the semaphore on success, which can be used to release it via
+ * {@see Generator::unguardWithSemaphore()}.
+ *
+ * @param int $semId
+ * @param int $concurrency
+ * @return false|resource the semaphore on success or false on failure
+ */
+ public static function guardWithSemaphore(int $semId, int $concurrency) {
+ if (!extension_loaded('sysvsem')) {
+ return false;
+ }
+ $sem = sem_get($semId, $concurrency);
+ if ($sem === false) {
+ return false;
+ }
+ if (!sem_acquire($sem)) {
+ return false;
+ }
+ return $sem;
+ }
+
+ /**
+ * Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
+ *
+ * @param resource|bool $semId the semaphore identifier returned by guardWithSemaphore
+ * @return bool
+ */
+ public static function unguardWithSemaphore($semId): bool {
+ if (!is_resource($semId) || !extension_loaded('sysvsem')) {
+ return false;
+ }
+ return sem_release($semId);
+ }
+
+ /**
+ * Get the number of concurrent threads supported by the host.
+ *
+ * @return int number of concurrent threads, or 0 if it cannot be determined
+ */
+ public static function getHardwareConcurrency(): int {
+ static $width;
+ if (!isset($width)) {
+ if (is_file("/proc/cpuinfo")) {
+ $width = substr_count(file_get_contents("/proc/cpuinfo"), "processor");
+ } else {
+ $width = 0;
+ }
+ }
+ return $width;
+ }
+
+ /**
+ * Get number of concurrent preview generations from system config
+ *
+ * Two config entries, `preview_concurrency_new` and `preview_concurrency_all`,
+ * are available. If not set, the default values are determined with the hardware concurrency
+ * of the host. In case the hardware concurrency cannot be determined, or the user sets an
+ * invalid value, fallback values are:
+ * For new images whose previews do not exist and need to be generated, 4;
+ * For all preview generation requests, 8.
+ * Value of `preview_concurrency_all` should be greater than or equal to that of
+ * `preview_concurrency_new`, otherwise, the latter is returned.
+ *
+ * @param string $type either `preview_concurrency_new` or `preview_concurrency_all`
+ * @return int number of concurrent preview generations, or -1 if $type is invalid
+ */
+ public function getNumConcurrentPreviews(string $type): int {
+ static $cached = array();
+ if (array_key_exists($type, $cached)) {
+ return $cached[$type];
+ }
+
+ $hardwareConcurrency = self::getHardwareConcurrency();
+ switch ($type) {
+ case "preview_concurrency_all":
+ $fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency * 2 : 8;
+ $concurrency_all = $this->config->getSystemValueInt($type, $fallback);
+ $concurrency_new = $this->getNumConcurrentPreviews("preview_concurrency_new");
+ $cached[$type] = max($concurrency_all, $concurrency_new);
+ break;
+ case "preview_concurrency_new":
+ $fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency : 4;
+ $cached[$type] = $this->config->getSystemValueInt($type, $fallback);
+ break;
+ default:
+ return -1;
+ }
+ return $cached[$type];
+ }
+
+ /**
* @param ISimpleFolder $previewFolder
* @param File $file
* @param string $mimeType
@@ -337,7 +434,13 @@ class Generator {
$maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
$maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
- $preview = $this->helper->getThumbnail($provider, $file, $maxWidth, $maxHeight);
+ $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
+ $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
+ try {
+ $preview = $this->helper->getThumbnail($provider, $file, $maxWidth, $maxHeight);
+ } finally {
+ self::unguardWithSemaphore($sem);
+ }
if (!($preview instanceof IImage)) {
continue;
@@ -507,29 +610,34 @@ class Generator {
throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
}
- if ($crop) {
- if ($height !== $preview->height() && $width !== $preview->width()) {
- //Resize
- $widthR = $preview->width() / $width;
- $heightR = $preview->height() / $height;
-
- if ($widthR > $heightR) {
- $scaleH = $height;
- $scaleW = $maxWidth / $heightR;
- } else {
- $scaleH = $maxHeight / $widthR;
- $scaleW = $width;
+ $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
+ $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
+ try {
+ if ($crop) {
+ if ($height !== $preview->height() && $width !== $preview->width()) {
+ //Resize
+ $widthR = $preview->width() / $width;
+ $heightR = $preview->height() / $height;
+
+ if ($widthR > $heightR) {
+ $scaleH = $height;
+ $scaleW = $maxWidth / $heightR;
+ } else {
+ $scaleH = $maxHeight / $widthR;
+ $scaleW = $width;
+ }
+ $preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
}
- $preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
+ $cropX = (int)floor(abs($width - $preview->width()) * 0.5);
+ $cropY = (int)floor(abs($height - $preview->height()) * 0.5);
+ $preview = $preview->cropCopy($cropX, $cropY, $width, $height);
+ } else {
+ $preview = $maxPreview->resizeCopy(max($width, $height));
}
- $cropX = (int)floor(abs($width - $preview->width()) * 0.5);
- $cropY = (int)floor(abs($height - $preview->height()) * 0.5);
- $preview = $preview->cropCopy($cropX, $cropY, $width, $height);
- } else {
- $preview = $maxPreview->resizeCopy(max($width, $height));
+ } finally {
+ self::unguardWithSemaphore($sem);
}
-
$path = $this->generatePath($width, $height, $crop, $preview->dataMimeType(), $prefix);
try {
$file = $previewFolder->newFile($path);
diff --git a/lib/private/Preview/HEIC.php b/lib/private/Preview/HEIC.php
index ec200defce8..71df98f9ac6 100644
--- a/lib/private/Preview/HEIC.php
+++ b/lib/private/Preview/HEIC.php
@@ -115,6 +115,9 @@ class HEIC extends ProviderV2 {
// Layer 0 contains either the bitmap or a flat representation of all vector layers
$bp->readImage($tmpPath . '[0]');
+ // Fix orientation from EXIF
+ $bp->autoOrient();
+
$bp->setImageFormat('jpg');
$bp = $this->resize($bp, $maxX, $maxY);
diff --git a/lib/private/Preview/Imaginary.php b/lib/private/Preview/Imaginary.php
index 4da88f1ab26..e78b9b441f6 100644
--- a/lib/private/Preview/Imaginary.php
+++ b/lib/private/Preview/Imaginary.php
@@ -27,6 +27,7 @@ use OCP\Files\File;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IImage;
+use OCP\Image;
use OC\StreamImage;
use Psr\Log\LoggerInterface;
@@ -89,6 +90,8 @@ class Imaginary extends ProviderV2 {
$mimeType = 'jpeg';
}
+ $quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
+
$operations = [
[
'operation' => 'autorotate',
@@ -101,6 +104,7 @@ class Imaginary extends ProviderV2 {
'stripmeta' => 'true',
'type' => $mimeType,
'norotation' => 'true',
+ 'quality' => $quality,
]
]
];
@@ -126,12 +130,21 @@ class Imaginary extends ProviderV2 {
return null;
}
- if ($response->getHeader('X-Image-Width') && $response->getHeader('X-Image-Height')) {
- $maxX = (int)$response->getHeader('X-Image-Width');
- $maxY = (int)$response->getHeader('X-Image-Height');
+ // This is not optimal but previews are distorted if the wrong width and height values are
+ // used. Both dimension headers are only sent when passing the option "-return-size" to
+ // Imaginary.
+ if ($response->getHeader('Image-Width') && $response->getHeader('Image-Height')) {
+ $image = new StreamImage(
+ $response->getBody(),
+ $response->getHeader('Content-Type'),
+ (int)$response->getHeader('Image-Width'),
+ (int)$response->getHeader('Image-Height'),
+ );
+ } else {
+ $image = new Image();
+ $image->loadFromFileHandle($response->getBody());
}
- $image = new StreamImage($response->getBody(), $response->getHeader('Content-Type'), $maxX, $maxY);
return $image->valid() ? $image : null;
}
diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php
index a2d34892c68..87e709e9bcc 100644
--- a/lib/private/PreviewManager.php
+++ b/lib/private/PreviewManager.php
@@ -34,6 +34,7 @@ use OC\AppFramework\Bootstrap\Coordinator;
use OC\Preview\Generator;
use OC\Preview\GeneratorHelper;
use OCP\AppFramework\QueryException;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
@@ -51,7 +52,8 @@ class PreviewManager implements IPreview {
protected IConfig $config;
protected IRootFolder $rootFolder;
protected IAppData $appData;
- protected EventDispatcherInterface $eventDispatcher;
+ protected IEventDispatcher $eventDispatcher;
+ protected EventDispatcherInterface $legacyEventDispatcher;
private ?Generator $generator = null;
private GeneratorHelper $helper;
protected bool $providerListDirty = false;
@@ -73,20 +75,22 @@ class PreviewManager implements IPreview {
private IBinaryFinder $binaryFinder;
public function __construct(
- IConfig $config,
- IRootFolder $rootFolder,
- IAppData $appData,
- EventDispatcherInterface $eventDispatcher,
- GeneratorHelper $helper,
- ?string $userId,
- Coordinator $bootstrapCoordinator,
- IServerContainer $container,
- IBinaryFinder $binaryFinder
+ IConfig $config,
+ IRootFolder $rootFolder,
+ IAppData $appData,
+ IEventDispatcher $eventDispatcher,
+ EventDispatcherInterface $legacyEventDispatcher,
+ GeneratorHelper $helper,
+ ?string $userId,
+ Coordinator $bootstrapCoordinator,
+ IServerContainer $container,
+ IBinaryFinder $binaryFinder
) {
$this->config = $config;
$this->rootFolder = $rootFolder;
$this->appData = $appData;
$this->eventDispatcher = $eventDispatcher;
+ $this->legacyEventDispatcher = $legacyEventDispatcher;
$this->helper = $helper;
$this->userId = $userId;
$this->bootstrapCoordinator = $bootstrapCoordinator;
@@ -153,6 +157,7 @@ class PreviewManager implements IPreview {
$this->rootFolder,
$this->config
),
+ $this->legacyEventDispatcher,
$this->eventDispatcher
);
}
@@ -177,7 +182,15 @@ class PreviewManager implements IPreview {
* @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
*/
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
- return $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType);
+ $previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all');
+ $sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency);
+ try {
+ $preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType);
+ } finally {
+ Generator::unguardWithSemaphore($sem);
+ }
+
+ return $preview;
}
/**
diff --git a/lib/private/Profile/Actions/FediverseAction.php b/lib/private/Profile/Actions/FediverseAction.php
new file mode 100644
index 00000000000..ed3fcd80b52
--- /dev/null
+++ b/lib/private/Profile/Actions/FediverseAction.php
@@ -0,0 +1,90 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2021 Christopher Ng <chrng8@gmail.com>
+ *
+ * @author Christopher Ng <chrng8@gmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Profile\Actions;
+
+use function Safe\substr;
+use OCP\Accounts\IAccountManager;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\L10N\IFactory;
+use OCP\Profile\ILinkAction;
+
+class FediverseAction implements ILinkAction {
+ private ?string $value = null;
+ private IAccountManager $accountManager;
+ private IFactory $l10nFactory;
+ private IURLGenerator $urlGenerator;
+
+ public function __construct(
+ IAccountManager $accountManager,
+ IFactory $l10nFactory,
+ IURLGenerator $urlGenerator
+ ) {
+ $this->accountManager = $accountManager;
+ $this->l10nFactory = $l10nFactory;
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ public function preload(IUser $targetUser): void {
+ $account = $this->accountManager->getAccount($targetUser);
+ $this->value = $account->getProperty(IAccountManager::PROPERTY_FEDIVERSE)->getValue();
+ }
+
+ public function getAppId(): string {
+ return 'core';
+ }
+
+ public function getId(): string {
+ return IAccountManager::PROPERTY_FEDIVERSE;
+ }
+
+ public function getDisplayId(): string {
+ return $this->l10nFactory->get('lib')->t('Fediverse');
+ }
+
+ public function getTitle(): string {
+ $displayUsername = $this->value[0] === '@' ? $this->value : '@' . $this->value;
+ return $this->l10nFactory->get('lib')->t('View %s on the fediverse', [$displayUsername]);
+ }
+
+ public function getPriority(): int {
+ return 50;
+ }
+
+ public function getIcon(): string {
+ return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/mastodon.svg'));
+ }
+
+ public function getTarget(): ?string {
+ if (empty($this->value)) {
+ return null;
+ }
+ $username = $this->value[0] === '@' ? substr($this->value, 1) : $this->value;
+ [$username, $instance] = explode('@', $username);
+ return 'https://' . $instance . '/@' . $username;
+ }
+}
diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php
index f038986cf6d..ab1af1c1c16 100644
--- a/lib/private/Profile/ProfileManager.php
+++ b/lib/private/Profile/ProfileManager.php
@@ -35,6 +35,7 @@ use OC\KnownUser\KnownUserService;
use OC\Profile\Actions\EmailAction;
use OC\Profile\Actions\PhoneAction;
use OC\Profile\Actions\TwitterAction;
+use OC\Profile\Actions\FediverseAction;
use OC\Profile\Actions\WebsiteAction;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\PropertyDoesNotExistException;
@@ -95,6 +96,7 @@ class ProfileManager {
PhoneAction::class,
WebsiteAction::class,
TwitterAction::class,
+ FediverseAction::class,
];
/**
diff --git a/lib/private/Repair.php b/lib/private/Repair.php
index 5f4747721ca..9ca3ece6dd4 100644
--- a/lib/private/Repair.php
+++ b/lib/private/Repair.php
@@ -71,6 +71,7 @@ use OC\Repair\NC21\AddCheckForUserCertificatesJob;
use OC\Repair\NC21\ValidatePhoneNumber;
use OC\Repair\NC22\LookupServerSendCheck;
use OC\Repair\NC24\AddTokenCleanupJob;
+use OC\Repair\NC25\AddMissingSecretJob;
use OC\Repair\OldGroupMembershipShares;
use OC\Repair\Owncloud\CleanPreviews;
use OC\Repair\Owncloud\DropAccountTermsTable;
@@ -209,6 +210,7 @@ class Repair implements IOutput {
\OCP\Server::get(LookupServerSendCheck::class),
\OCP\Server::get(AddTokenCleanupJob::class),
\OCP\Server::get(CleanUpAbandonedApps::class),
+ \OCP\Server::get(AddMissingSecretJob::class),
];
}
diff --git a/lib/private/Repair/NC25/AddMissingSecretJob.php b/lib/private/Repair/NC25/AddMissingSecretJob.php
new file mode 100644
index 00000000000..1194fe3f6ab
--- /dev/null
+++ b/lib/private/Repair/NC25/AddMissingSecretJob.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2022 Carl Schwan <carl@carlschwan.eu>
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OC\Repair\NC25;
+
+use OCP\HintException;
+use OCP\IConfig;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+use OCP\Security\ISecureRandom;
+
+class AddMissingSecretJob implements IRepairStep {
+ private IConfig $config;
+ private ISecureRandom $random;
+
+ public function __construct(IConfig $config, ISecureRandom $random) {
+ $this->config = $config;
+ $this->random = $random;
+ }
+
+ public function getName(): string {
+ return 'Add possibly missing system config';
+ }
+
+ public function run(IOutput $output): void {
+ $passwordSalt = $this->config->getSystemValue('passwordsalt', null);
+ if ($passwordSalt === null || $passwordSalt === '') {
+ try {
+ $this->config->setSystemValue('passwordsalt', $this->random->generate(30));
+ } catch (HintException $e) {
+ $output->warning("passwordsalt is missing from your config.php and your config.php is read only. Please fix it manually.");
+ }
+ }
+
+ $secret = $this->config->getSystemValue('secret', null);
+ if ($secret === null || $secret === '') {
+ try {
+ $this->config->setSystemValue('secret', $this->random->generate(48));
+ } catch (HintException $e) {
+ $output->warning("secret is missing from your config.php and your config.php is read only. Please fix it manually.");
+ }
+ }
+ }
+}
diff --git a/lib/private/Repair/RepairMimeTypes.php b/lib/private/Repair/RepairMimeTypes.php
index 5b216331dc7..5f3531ea79c 100644
--- a/lib/private/Repair/RepairMimeTypes.php
+++ b/lib/private/Repair/RepairMimeTypes.php
@@ -106,6 +106,15 @@ class RepairMimeTypes implements IRepairStep {
return $count;
}
+ private function introduceAsciidocType() {
+ $updatedMimetypes = [
+ 'adoc' => 'text/asciidoc',
+ 'asciidoc' => 'text/asciidoc',
+ ];
+
+ return $this->updateMimetypes($updatedMimetypes);
+ }
+
private function introduceImageTypes() {
$updatedMimetypes = [
'jp2' => 'image/jp2',
@@ -273,5 +282,9 @@ class RepairMimeTypes implements IRepairStep {
if (version_compare($ocVersionFromBeforeUpdate, '25.0.0.2', '<') && $this->introduceOnlyofficeFormType()) {
$out->info('Fixed ONLYOFFICE Forms OpenXML mime types');
}
+
+ if (version_compare($ocVersionFromBeforeUpdate, '26.0.0.1', '<') && $this->introduceAsciidocType()) {
+ $out->info('Fixed AsciiDoc mime types');
+ }
}
}
diff --git a/lib/private/Security/RemoteHostValidator.php b/lib/private/Security/RemoteHostValidator.php
new file mode 100644
index 00000000000..e48bd862472
--- /dev/null
+++ b/lib/private/Security/RemoteHostValidator.php
@@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace OC\Security;
+
+use OC\Net\HostnameClassifier;
+use OC\Net\IpAddressClassifier;
+use OCP\IConfig;
+use OCP\Security\IRemoteHostValidator;
+use Psr\Log\LoggerInterface;
+use function strpos;
+use function strtolower;
+use function substr;
+use function urldecode;
+
+/**
+ * @internal
+ */
+final class RemoteHostValidator implements IRemoteHostValidator {
+ private IConfig $config;
+ private HostnameClassifier $hostnameClassifier;
+ private IpAddressClassifier $ipAddressClassifier;
+ private LoggerInterface $logger;
+
+ public function __construct(IConfig $config,
+ HostnameClassifier $hostnameClassifier,
+ IpAddressClassifier $ipAddressClassifier,
+ LoggerInterface $logger) {
+ $this->config = $config;
+ $this->hostnameClassifier = $hostnameClassifier;
+ $this->ipAddressClassifier = $ipAddressClassifier;
+ $this->logger = $logger;
+ }
+
+ public function isValid(string $host): bool {
+ if ($this->config->getSystemValueBool('allow_local_remote_servers', false)) {
+ return true;
+ }
+
+ $host = idn_to_utf8(strtolower(urldecode($host)));
+ // Remove brackets from IPv6 addresses
+ if (strpos($host, '[') === 0 && substr($host, -1) === ']') {
+ $host = substr($host, 1, -1);
+ }
+
+ if ($this->hostnameClassifier->isLocalHostname($host)
+ || $this->ipAddressClassifier->isLocalAddress($host)) {
+ $this->logger->warning("Host $host was not connected to because it violates local access rules");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index d804cca2086..d420b6fd11d 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -105,8 +105,6 @@ use OC\Files\Type\Loader;
use OC\Files\View;
use OC\FullTextSearch\FullTextSearchManager;
use OC\Http\Client\ClientService;
-use OC\Http\Client\DnsPinMiddleware;
-use OC\Http\Client\LocalAddressChecker;
use OC\Http\Client\NegativeDnsCache;
use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\AppLocator;
@@ -335,6 +333,7 @@ class Server extends ServerContainer implements IServerContainer {
$c->get(IRootFolder::class),
$c->get(SystemConfig::class)
),
+ $c->get(IEventDispatcher::class),
$c->get(SymfonyAdapter::class),
$c->get(GeneratorHelper::class),
$c->get(ISession::class)->get('user_id'),
@@ -488,7 +487,8 @@ class Server extends ServerContainer implements IServerContainer {
$groupManager = new \OC\Group\Manager(
$this->get(IUserManager::class),
$c->get(SymfonyAdapter::class),
- $this->get(LoggerInterface::class)
+ $this->get(LoggerInterface::class),
+ $this->get(ICacheFactory::class)
);
$groupManager->listen('\OC\Group', 'preCreate', function ($gid) {
/** @var IEventDispatcher $dispatcher */
@@ -688,6 +688,7 @@ class Server extends ServerContainer implements IServerContainer {
$c->get(\OCP\IConfig::class),
$c->getRequest(),
$c->get(IUserSession::class),
+ $c->get(ICacheFactory::class),
\OC::$SERVERROOT
);
});
@@ -857,7 +858,7 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(\OCP\Security\ISecureRandom::class, SecureRandom::class);
/** @deprecated 19.0.0 */
$this->registerDeprecatedAlias('SecureRandom', \OCP\Security\ISecureRandom::class);
-
+ $this->registerAlias(\OCP\Security\IRemoteHostValidator::class, \OC\Security\RemoteHostValidator::class);
$this->registerAlias(IVerificationToken::class, VerificationToken::class);
$this->registerAlias(ICrypto::class, Crypto::class);
@@ -889,22 +890,11 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerAlias(ICertificateManager::class, CertificateManager::class);
$this->registerAlias(IClientService::class, ClientService::class);
- $this->registerService(LocalAddressChecker::class, function (ContainerInterface $c) {
- return new LocalAddressChecker(
- $c->get(LoggerInterface::class),
- );
- });
$this->registerService(NegativeDnsCache::class, function (ContainerInterface $c) {
return new NegativeDnsCache(
$c->get(ICacheFactory::class),
);
});
- $this->registerService(DnsPinMiddleware::class, function (ContainerInterface $c) {
- return new DnsPinMiddleware(
- $c->get(NegativeDnsCache::class),
- $c->get(LocalAddressChecker::class)
- );
- });
$this->registerDeprecatedAlias('HttpClientService', IClientService::class);
$this->registerService(IEventLogger::class, function (ContainerInterface $c) {
return new EventLogger($c->get(SystemConfig::class), $c->get(LoggerInterface::class), $c->get(Log::class));
@@ -1221,21 +1211,22 @@ class Server extends ServerContainer implements IServerContainer {
}
if ($classExists && $c->get(\OCP\IConfig::class)->getSystemValue('installed', false) && $c->get(IAppManager::class)->isInstalled('theming') && $c->getTrustedDomainHelper()->isTrustedDomain($c->getRequest()->getInsecureServerHost())) {
+ $imageManager = new ImageManager(
+ $c->get(\OCP\IConfig::class),
+ $c->getAppDataDir('theming'),
+ $c->get(IURLGenerator::class),
+ $this->get(ICacheFactory::class),
+ $this->get(ILogger::class),
+ $this->get(ITempManager::class)
+ );
return new ThemingDefaults(
$c->get(\OCP\IConfig::class),
$c->getL10N('theming'),
$c->get(IUserSession::class),
$c->get(IURLGenerator::class),
$c->get(ICacheFactory::class),
- new Util($c->get(\OCP\IConfig::class), $this->get(IAppManager::class), $c->getAppDataDir('theming')),
- new ImageManager(
- $c->get(\OCP\IConfig::class),
- $c->getAppDataDir('theming'),
- $c->get(IURLGenerator::class),
- $this->get(ICacheFactory::class),
- $this->get(ILogger::class),
- $this->get(ITempManager::class)
- ),
+ new Util($c->get(\OCP\IConfig::class), $this->get(IAppManager::class), $c->getAppDataDir('theming'), $imageManager),
+ $imageManager,
$c->get(IAppManager::class),
$c->get(INavigationManager::class)
);
diff --git a/lib/private/ServerContainer.php b/lib/private/ServerContainer.php
index 0bc99f6c152..e53737990e8 100644
--- a/lib/private/ServerContainer.php
+++ b/lib/private/ServerContainer.php
@@ -139,10 +139,13 @@ class ServerContainer extends SimpleContainer {
public function query(string $name, bool $autoload = true) {
$name = $this->sanitizeName($name);
- try {
- return parent::query($name, false);
- } catch (QueryException $e) {
- // Continue with general autoloading then
+ if (str_starts_with($name, 'OCA\\')) {
+ // Skip server container query for app namespace classes
+ try {
+ return parent::query($name, false);
+ } catch (QueryException $e) {
+ // Continue with general autoloading then
+ }
}
// In case the service starts with OCA\ we try to find the service in
diff --git a/lib/private/Session/CryptoSessionData.php b/lib/private/Session/CryptoSessionData.php
index b01887e39e2..df810d5b30c 100644
--- a/lib/private/Session/CryptoSessionData.php
+++ b/lib/private/Session/CryptoSessionData.php
@@ -143,7 +143,6 @@ class CryptoSessionData implements \ArrayAccess, ISession {
$reopened = $this->reopen();
$this->isModified = true;
unset($this->sessionValues[$key]);
- $this->session->remove(self::encryptedSessionName);
if ($reopened) {
$this->close();
}
@@ -163,7 +162,11 @@ class CryptoSessionData implements \ArrayAccess, ISession {
}
public function reopen(): bool {
- return $this->session->reopen();
+ $reopened = $this->session->reopen();
+ if ($reopened) {
+ $this->initializeSession();
+ }
+ return $reopened;
}
/**
diff --git a/lib/private/Setup.php b/lib/private/Setup.php
index 3b79b31b849..7b08fb2f66f 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -247,14 +247,13 @@ class Setup {
];
}
- if ($this->iniWrapper->getString('open_basedir') !== '' && PHP_INT_SIZE === 4) {
+ if (PHP_INT_SIZE < 8) {
$errors[] = [
'error' => $this->l10n->t(
- 'It seems that this %s instance is running on a 32-bit PHP environment and the open_basedir has been configured in php.ini. ' .
- 'This will lead to problems with files over 4 GB and is highly discouraged.',
+ 'It seems that this %s instance is running on a 32-bit PHP environment. 64-bit is required for 26 and higher.',
[$this->defaults->getProductName()]
),
- 'hint' => $this->l10n->t('Please remove the open_basedir setting within your php.ini or switch to 64-bit PHP.'),
+ 'hint' => $this->l10n->t('Please switch to 64-bit PHP.'),
];
}
diff --git a/lib/private/Setup/PostgreSQL.php b/lib/private/Setup/PostgreSQL.php
index bc24909dc3d..af816c7ad04 100644
--- a/lib/private/Setup/PostgreSQL.php
+++ b/lib/private/Setup/PostgreSQL.php
@@ -62,6 +62,7 @@ class PostgreSQL extends AbstractDatabase {
}
if ($canCreateRoles) {
+ $connectionMainDatabase = $this->connect();
//use the admin login data for the new database user
//add prefix to the postgresql user name to prevent collisions
@@ -70,6 +71,16 @@ class PostgreSQL extends AbstractDatabase {
$this->dbPassword = \OC::$server->getSecureRandom()->generate(30, ISecureRandom::CHAR_ALPHANUMERIC);
$this->createDBUser($connection);
+
+ // Go to the main database and grant create on the public schema
+ // The code below is implemented to make installing possible with PostgreSQL version 15:
+ // https://www.postgresql.org/docs/release/15.0/
+ // From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases
+ // Therefore we assume that the database is only used by one user/service which is Nextcloud
+ // Additional services should get installed in a separate database in order to stay secure
+ // Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS
+ $connectionMainDatabase->executeQuery('GRANT CREATE ON SCHEMA public TO ' . addslashes($this->dbUser));
+ $connectionMainDatabase->close();
}
$this->config->setValues([
diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php
index c78cf62e069..a5a5568788d 100644
--- a/lib/private/Share20/DefaultShareProvider.php
+++ b/lib/private/Share20/DefaultShareProvider.php
@@ -1087,6 +1087,10 @@ class DefaultShareProvider implements IShareProvider {
}
} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
$share->setSharedWith($data['share_with']);
+ $group = $this->groupManager->get($data['share_with']);
+ if ($group !== null) {
+ $share->setSharedWithDisplayName($group->getDisplayName());
+ }
} elseif ($share->getShareType() === IShare::TYPE_LINK) {
$share->setPassword($data['password']);
$share->setSendPasswordByTalk((bool)$data['password_by_talk']);
diff --git a/lib/private/StreamImage.php b/lib/private/StreamImage.php
index 38fd114df17..33078310d27 100644
--- a/lib/private/StreamImage.php
+++ b/lib/private/StreamImage.php
@@ -37,7 +37,7 @@ class StreamImage implements IStreamImage {
/** @var resource The internal stream */
private $stream;
- /** @var string */
+ /** @var null|string */
private $mimeType;
/** @var int */
@@ -55,38 +55,38 @@ class StreamImage implements IStreamImage {
}
/** @inheritDoc */
- public function valid() {
+ public function valid(): bool {
return is_resource($this->stream);
}
/** @inheritDoc */
- public function mimeType() {
+ public function mimeType(): ?string {
return $this->mimeType;
}
/** @inheritDoc */
- public function width() {
+ public function width(): int {
return $this->width;
}
/** @inheritDoc */
- public function height() {
+ public function height(): int {
return $this->height;
}
- public function widthTopLeft() {
+ public function widthTopLeft(): int {
throw new \BadMethodCallException('Not implemented');
}
- public function heightTopLeft() {
+ public function heightTopLeft(): int {
throw new \BadMethodCallException('Not implemented');
}
- public function show($mimeType = null) {
+ public function show(?string $mimeType = null): bool {
throw new \BadMethodCallException('Not implemented');
}
- public function save($filePath = null, $mimeType = null) {
+ public function save(?string $filePath = null, ?string $mimeType = null): bool {
throw new \BadMethodCallException('Not implemented');
}
@@ -94,23 +94,23 @@ class StreamImage implements IStreamImage {
return $this->stream;
}
- public function dataMimeType() {
+ public function dataMimeType(): ?string {
return $this->mimeType;
}
- public function data() {
+ public function data(): ?string {
return '';
}
- public function getOrientation() {
+ public function getOrientation(): int {
throw new \BadMethodCallException('Not implemented');
}
- public function fixOrientation() {
+ public function fixOrientation(): bool {
throw new \BadMethodCallException('Not implemented');
}
- public function resize($maxSize) {
+ public function resize(int $maxSize): bool {
throw new \BadMethodCallException('Not implemented');
}
@@ -118,7 +118,7 @@ class StreamImage implements IStreamImage {
throw new \BadMethodCallException('Not implemented');
}
- public function centerCrop($size = 0) {
+ public function centerCrop(int $size = 0): bool {
throw new \BadMethodCallException('Not implemented');
}
@@ -126,11 +126,11 @@ class StreamImage implements IStreamImage {
throw new \BadMethodCallException('Not implemented');
}
- public function fitIn($maxWidth, $maxHeight) {
+ public function fitIn(int $maxWidth, int $maxHeight): bool {
throw new \BadMethodCallException('Not implemented');
}
- public function scaleDownToFit($maxWidth, $maxHeight) {
+ public function scaleDownToFit(int $maxWidth, int $maxHeight): bool {
throw new \BadMethodCallException('Not implemented');
}
diff --git a/lib/private/Streamer.php b/lib/private/Streamer.php
index 88204be9805..db7d0024b5a 100644
--- a/lib/private/Streamer.php
+++ b/lib/private/Streamer.php
@@ -86,7 +86,7 @@ class Streamer {
} elseif ($request->isUserAgent($this->preferTarFor)) {
$this->streamerInstance = new TarStreamer();
} else {
- $this->streamerInstance = new ZipStreamer(['zip64' => PHP_INT_SIZE !== 4]);
+ $this->streamerInstance = new ZipStreamer(['zip64' => true]);
}
}
diff --git a/lib/private/Support/Subscription/Assertion.php b/lib/private/Support/Subscription/Assertion.php
index 9b77e875944..451afe83bd3 100644
--- a/lib/private/Support/Subscription/Assertion.php
+++ b/lib/private/Support/Subscription/Assertion.php
@@ -49,7 +49,7 @@ class Assertion implements IAssertion {
public function createUserIsLegit(): void {
if ($this->registry->delegateIsHardUserLimitReached($this->notificationManager)) {
$l = $this->l10nFactory->get('lib');
- throw new HintException($l->t('The user limit has been reached and the user was not created. Check your notifications to learn more.'));
+ throw new HintException($l->t('The user was not created because the user limit has been reached. Check your notifications to learn more.'));
}
}
}
diff --git a/lib/private/SystemConfig.php b/lib/private/SystemConfig.php
index 0bc6154fbc4..6cd0e4376c5 100644
--- a/lib/private/SystemConfig.php
+++ b/lib/private/SystemConfig.php
@@ -72,6 +72,10 @@ class SystemConfig {
'host' => true,
'password' => true,
],
+ 'redis.cluster' => [
+ 'seeds' => true,
+ 'password' => true,
+ ],
'objectstore' => [
'arguments' => [
// Legacy Swift (https://github.com/nextcloud/server/pull/17696#discussion_r341302207)
diff --git a/lib/private/Talk/Broker.php b/lib/private/Talk/Broker.php
index a686adeed04..12e6c5fb34b 100644
--- a/lib/private/Talk/Broker.php
+++ b/lib/private/Talk/Broker.php
@@ -106,4 +106,12 @@ class Broker implements IBroker {
$options ?? ConversationOptions::default()
);
}
+
+ public function deleteConversation(string $id): void {
+ if (!$this->hasBackend()) {
+ throw new NoBackendException("The Talk broker has no registered backend");
+ }
+
+ $this->backend->deleteConversation($id);
+ }
}
diff --git a/lib/private/Template/CSSResourceLocator.php b/lib/private/Template/CSSResourceLocator.php
index 2cbf12ce65d..5047b3e906f 100644
--- a/lib/private/Template/CSSResourceLocator.php
+++ b/lib/private/Template/CSSResourceLocator.php
@@ -34,14 +34,8 @@ namespace OC\Template;
use Psr\Log\LoggerInterface;
class CSSResourceLocator extends ResourceLocator {
-
- /**
- * @param string $theme
- * @param array $core_map
- * @param array $party_map
- */
- public function __construct(LoggerInterface $logger, $theme, $core_map, $party_map) {
- parent::__construct($logger, $theme, $core_map, $party_map);
+ public function __construct(LoggerInterface $logger) {
+ parent::__construct($logger);
}
/**
@@ -50,8 +44,7 @@ class CSSResourceLocator extends ResourceLocator {
public function doFind($style) {
$app = substr($style, 0, strpos($style, '/'));
if (strpos($style, '3rdparty') === 0
- && $this->appendIfExist($this->thirdpartyroot, $style.'.css')
- || $this->appendIfExist($this->serverroot, $style.'.css')
+ && $this->appendIfExist($this->serverroot, $style.'.css')
|| $this->appendIfExist($this->serverroot, 'core/'.$style.'.css')
) {
return;
diff --git a/lib/private/Template/JSCombiner.php b/lib/private/Template/JSCombiner.php
index a6d9f0ee558..c075e65d76a 100644
--- a/lib/private/Template/JSCombiner.php
+++ b/lib/private/Template/JSCombiner.php
@@ -198,7 +198,7 @@ class JSCombiner {
$gzipFile->putContent(gzencode($res, 9));
$this->logger->debug('JSCombiner: successfully cached: ' . $fileName);
return true;
- } catch (NotPermittedException $e) {
+ } catch (NotPermittedException|NotFoundException $e) {
$this->logger->error('JSCombiner: unable to cache: ' . $fileName);
return false;
}
diff --git a/lib/private/Template/JSResourceLocator.php b/lib/private/Template/JSResourceLocator.php
index 9e76655aba2..88323af75de 100644
--- a/lib/private/Template/JSResourceLocator.php
+++ b/lib/private/Template/JSResourceLocator.php
@@ -34,8 +34,8 @@ class JSResourceLocator extends ResourceLocator {
/** @var JSCombiner */
protected $jsCombiner;
- public function __construct(LoggerInterface $logger, $theme, array $core_map, array $party_map, JSCombiner $JSCombiner) {
- parent::__construct($logger, $theme, $core_map, $party_map);
+ public function __construct(LoggerInterface $logger, JSCombiner $JSCombiner) {
+ parent::__construct($logger);
$this->jsCombiner = $JSCombiner;
}
@@ -45,10 +45,6 @@ class JSResourceLocator extends ResourceLocator {
*/
public function doFind($script) {
$theme_dir = 'themes/'.$this->theme.'/';
- if (strpos($script, '3rdparty') === 0
- && $this->appendIfExist($this->thirdpartyroot, $script.'.js')) {
- return;
- }
// Extracting the appId and the script file name
$app = substr($script, 0, strpos($script, '/'));
diff --git a/lib/private/Template/ResourceLocator.php b/lib/private/Template/ResourceLocator.php
index 5a50cc6fd1b..9e6e2056e6b 100755
--- a/lib/private/Template/ResourceLocator.php
+++ b/lib/private/Template/ResourceLocator.php
@@ -36,25 +36,20 @@ abstract class ResourceLocator {
protected $mapping;
protected $serverroot;
- protected $thirdpartyroot;
protected $webroot;
protected $resources = [];
protected LoggerInterface $logger;
- /**
- * @param string $theme
- * @param array $core_map
- * @param array $party_map
- */
- public function __construct(LoggerInterface $logger, $theme, $core_map, $party_map) {
+ public function __construct(LoggerInterface $logger) {
$this->logger = $logger;
- $this->theme = $theme;
- $this->mapping = $core_map + $party_map;
- $this->serverroot = key($core_map);
- $this->thirdpartyroot = key($party_map);
- $this->webroot = $this->mapping[$this->serverroot];
+ $this->mapping = [
+ \OC::$SERVERROOT => \OC::$WEBROOT
+ ];
+ $this->serverroot = \OC::$SERVERROOT;
+ $this->webroot = \OC::$WEBROOT;
+ $this->theme = \OC_Util::getTheme();
}
/**
diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php
index d041db2a7c2..178bec9c8dc 100644
--- a/lib/private/TemplateLayout.php
+++ b/lib/private/TemplateLayout.php
@@ -44,8 +44,9 @@ namespace OC;
use bantu\IniGetWrapper\IniGetWrapper;
use OC\Search\SearchQuery;
-use OC\Template\JSCombiner;
+use OC\Template\CSSResourceLocator;
use OC\Template\JSConfigHelper;
+use OC\Template\JSResourceLocator;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
use OCP\IConfig;
@@ -54,11 +55,16 @@ use OCP\INavigationManager;
use OCP\IUserSession;
use OCP\Support\Subscription\IRegistry;
use OCP\Util;
-use Psr\Log\LoggerInterface;
class TemplateLayout extends \OC_Template {
private static $versionHash = '';
+ /** @var CSSResourceLocator|null */
+ public static $cssLocator = null;
+
+ /** @var JSResourceLocator|null */
+ public static $jsLocator = null;
+
/** @var IConfig */
private $config;
@@ -102,7 +108,7 @@ class TemplateLayout extends \OC_Template {
$this->initialState->provideInitialState('core', 'active-app', $this->navigationManager->getActiveEntry());
$this->initialState->provideInitialState('core', 'apps', $this->navigationManager->getAll());
$this->initialState->provideInitialState('unified-search', 'limit-default', (int)$this->config->getAppValue('core', 'unified-search.limit-default', (string)SearchQuery::LIMIT_DEFAULT));
- $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)2));
+ $this->initialState->provideInitialState('unified-search', 'min-search-length', (int)$this->config->getAppValue('core', 'unified-search.min-search-length', (string)1));
$this->initialState->provideInitialState('unified-search', 'live-search', $this->config->getAppValue('core', 'unified-search.live-search', 'yes') === 'yes');
Util::addScript('core', 'unified-search', 'core');
@@ -114,10 +120,6 @@ class TemplateLayout extends \OC_Template {
$this->assign('enabledThemes', $themesService->getEnabledThemes());
}
- // set logo link target
- $logoUrl = $this->config->getSystemValueString('logo_url', '');
- $this->assign('logoUrl', $logoUrl);
-
// Add navigation entry
$this->assign('application', '');
$this->assign('appid', $appId);
@@ -188,6 +190,11 @@ class TemplateLayout extends \OC_Template {
} else {
parent::__construct('core', 'layout.base');
}
+
+ // set logo link target
+ $logoUrl = $this->config->getSystemValueString('logo_url', '');
+ $this->assign('logoUrl', $logoUrl);
+
// Send the language and the locale to our layouts
$lang = \OC::$server->getL10NFactory()->findLanguage();
$locale = \OC::$server->getL10NFactory()->findLocale($lang);
@@ -332,17 +339,11 @@ class TemplateLayout extends \OC_Template {
* @return array
*/
public static function findStylesheetFiles($styles, $compileScss = true) {
- // Read the selected theme from the config file
- $theme = \OC_Util::getTheme();
-
- $locator = new \OC\Template\CSSResourceLocator(
- \OC::$server->get(LoggerInterface::class),
- $theme,
- [ \OC::$SERVERROOT => \OC::$WEBROOT ],
- [ \OC::$SERVERROOT => \OC::$WEBROOT ],
- );
- $locator->find($styles);
- return $locator->getResources();
+ if (!self::$cssLocator) {
+ self::$cssLocator = \OCP\Server::get(CSSResourceLocator::class);
+ }
+ self::$cssLocator->find($styles);
+ return self::$cssLocator->getResources();
}
/**
@@ -366,18 +367,11 @@ class TemplateLayout extends \OC_Template {
* @return array
*/
public static function findJavascriptFiles($scripts) {
- // Read the selected theme from the config file
- $theme = \OC_Util::getTheme();
-
- $locator = new \OC\Template\JSResourceLocator(
- \OC::$server->get(LoggerInterface::class),
- $theme,
- [ \OC::$SERVERROOT => \OC::$WEBROOT ],
- [ \OC::$SERVERROOT => \OC::$WEBROOT ],
- \OC::$server->query(JSCombiner::class)
- );
- $locator->find($scripts);
- return $locator->getResources();
+ if (!self::$jsLocator) {
+ self::$jsLocator = \OCP\Server::get(JSResourceLocator::class);
+ }
+ self::$jsLocator->find($scripts);
+ return self::$jsLocator->getResources();
}
/**
diff --git a/lib/private/Updater.php b/lib/private/Updater.php
index 78613ddbb0c..53b07e25809 100644
--- a/lib/private/Updater.php
+++ b/lib/private/Updater.php
@@ -138,6 +138,9 @@ class Updater extends BasicEmitter {
$success = true;
try {
+ if (PHP_INT_SIZE < 8 && version_compare($currentVersion, '26.0.0.0', '>=')) {
+ throw new HintException('You are running a 32-bit PHP version. Cannot upgrade to Nextcloud 26 and higher. Please switch to 64-bit PHP.');
+ }
$this->doUpgrade($currentVersion, $installedVersion);
} catch (HintException $exception) {
$this->log->error($exception->getMessage(), [
diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php
index dc31eece414..82fc4d818ad 100644
--- a/lib/private/User/Manager.php
+++ b/lib/private/User/Manager.php
@@ -44,6 +44,8 @@ use OCP\IGroup;
use OCP\IUser;
use OCP\IUserBackend;
use OCP\IUserManager;
+use OCP\L10N\IFactory;
+use OCP\Server;
use OCP\Support\Subscription\IAssertion;
use OCP\User\Backend\IGetRealUIDBackend;
use OCP\User\Backend\ISearchKnownUsersBackend;
@@ -427,31 +429,7 @@ class Manager extends PublicEmitter implements IUserManager {
public function createUserFromBackend($uid, $password, UserInterface $backend) {
$l = \OC::$server->getL10N('lib');
- // Check the name for bad characters
- // Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
- if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
- throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
- . ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
- }
-
- // No empty username
- if (trim($uid) === '') {
- throw new \InvalidArgumentException($l->t('A valid username must be provided'));
- }
-
- // No whitespace at the beginning or at the end
- if (trim($uid) !== $uid) {
- throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
- }
-
- // Username only consists of 1 or 2 dots (directory traversal)
- if ($uid === '.' || $uid === '..') {
- throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
- }
-
- if (!$this->verifyUid($uid)) {
- throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
- }
+ $this->validateUserId($uid, true);
// No empty password
if (trim($password) === '') {
@@ -726,7 +704,43 @@ class Manager extends PublicEmitter implements IUserManager {
}));
}
- private function verifyUid(string $uid): bool {
+ /**
+ * @param string $uid
+ * @param bool $checkDataDirectory
+ * @throws \InvalidArgumentException Message is an already translated string with a reason why the id is not valid
+ * @since 26.0.0
+ */
+ public function validateUserId(string $uid, bool $checkDataDirectory = false): void {
+ $l = Server::get(IFactory::class)->get('lib');
+
+ // Check the name for bad characters
+ // Allowed are: "a-z", "A-Z", "0-9" and "_.@-'"
+ if (preg_match('/[^a-zA-Z0-9 _.@\-\']/', $uid)) {
+ throw new \InvalidArgumentException($l->t('Only the following characters are allowed in a username:'
+ . ' "a-z", "A-Z", "0-9", and "_.@-\'"'));
+ }
+
+ // No empty username
+ if (trim($uid) === '') {
+ throw new \InvalidArgumentException($l->t('A valid username must be provided'));
+ }
+
+ // No whitespace at the beginning or at the end
+ if (trim($uid) !== $uid) {
+ throw new \InvalidArgumentException($l->t('Username contains whitespace at the beginning or at the end'));
+ }
+
+ // Username only consists of 1 or 2 dots (directory traversal)
+ if ($uid === '.' || $uid === '..') {
+ throw new \InvalidArgumentException($l->t('Username must not consist of dots only'));
+ }
+
+ if (!$this->verifyUid($uid, $checkDataDirectory)) {
+ throw new \InvalidArgumentException($l->t('Username is invalid because files already exist for this user'));
+ }
+ }
+
+ private function verifyUid(string $uid, bool $checkDataDirectory = false): bool {
$appdata = 'appdata_' . $this->config->getSystemValueString('instanceid');
if (\in_array($uid, [
@@ -740,6 +754,10 @@ class Manager extends PublicEmitter implements IUserManager {
return false;
}
+ if (!$checkDataDirectory) {
+ return true;
+ }
+
$dataDirectory = $this->config->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data');
return !file_exists(rtrim($dataDirectory, '/') . '/' . $uid);
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index 5117812db31..c7b11e22504 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -89,7 +89,6 @@ use Symfony\Component\EventDispatcher\GenericEvent;
* @package OC\User
*/
class Session implements IUserSession, Emitter {
-
/** @var Manager $manager */
private $manager;
@@ -372,6 +371,7 @@ class Session implements IUserSession, Emitter {
if ($regenerateSessionId) {
$this->session->regenerateId();
+ $this->session->remove(Auth::DAV_AUTHENTICATED);
}
$this->setUser($user);
@@ -448,7 +448,6 @@ class Session implements IUserSession, Emitter {
// Try to login with this username and password
if (!$this->login($user, $password)) {
-
// Failed, maybe the user used their email address
if (!filter_var($user, FILTER_VALIDATE_EMAIL)) {
return false;
@@ -459,7 +458,7 @@ class Session implements IUserSession, Emitter {
$throttler->registerAttempt('login', $request->getRemoteAddress(), ['user' => $user]);
- $this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user));
+ $this->dispatcher->dispatchTyped(new OC\Authentication\Events\LoginFailed($user, $password));
if ($currentDelay === 0) {
$throttler->sleepDelay($request->getRemoteAddress(), 'login');
@@ -672,7 +671,7 @@ class Session implements IUserSession, Emitter {
// User does not exist
return false;
}
- $name = isset($request->server['HTTP_USER_AGENT']) ? utf8_encode($request->server['HTTP_USER_AGENT']) : 'unknown browser';
+ $name = isset($request->server['HTTP_USER_AGENT']) ? mb_convert_encoding($request->server['HTTP_USER_AGENT'], 'UTF-8', 'ISO-8859-1') : 'unknown browser';
try {
$sessionId = $this->session->getId();
$pwd = $this->getPassword($password);
@@ -868,7 +867,7 @@ class Session implements IUserSession, Emitter {
$tokens = $this->config->getUserKeys($uid, 'login_token');
// test cookies token against stored tokens
if (!in_array($currentToken, $tokens, true)) {
- $this->logger->error('Tried to log in {uid} but could not verify token', [
+ $this->logger->info('Tried to log in {uid} but could not verify token', [
'app' => 'core',
'uid' => $uid,
]);
diff --git a/lib/private/legacy/OC_Helper.php b/lib/private/legacy/OC_Helper.php
index b793029e1f1..3b409fd1d04 100644
--- a/lib/private/legacy/OC_Helper.php
+++ b/lib/private/legacy/OC_Helper.php
@@ -457,10 +457,12 @@ class OC_Helper {
*
* @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
* @return array
* @throws \OCP\Files\NotFoundException
*/
- public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true) {
+ public static function getStorageInfo($path, $rootInfo = null, $includeMountPoints = true, $useCache = true) {
/** @var ICacheFactory $cacheFactory */
$cacheFactory = \OC::$server->get(ICacheFactory::class);
$memcache = $cacheFactory->createLocal('storage_info');
@@ -470,9 +472,11 @@ class OC_Helper {
$fullPath = Filesystem::getView()->getAbsolutePath($path);
$cacheKey = $fullPath. '::' . ($includeMountPoints ? 'include' : 'exclude');
- $cached = $memcache->get($cacheKey);
- if ($cached) {
- return $cached;
+ if ($useCache) {
+ $cached = $memcache->get($cacheKey);
+ if ($cached) {
+ return $cached;
+ }
}
if (!$rootInfo) {
diff --git a/lib/private/legacy/OC_Image.php b/lib/private/legacy/OC_Image.php
index a212d639084..9ccc6409ba0 100644
--- a/lib/private/legacy/OC_Image.php
+++ b/lib/private/legacy/OC_Image.php
@@ -1,4 +1,7 @@
<?php
+
+declare(strict_types=1);
+
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
@@ -50,14 +53,15 @@ class OC_Image implements \OCP\IImage {
// Default memory limit for images to load (128 MBytes).
protected const DEFAULT_MEMORY_LIMIT = 128;
+ // Default quality for jpeg images
+ protected const DEFAULT_JPEG_QUALITY = 80;
+
/** @var false|resource|\GdImage */
protected $resource = false; // tmp resource.
/** @var int */
protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident.
- /** @var string */
+ /** @var null|string */
protected $mimeType = 'image/png'; // Default to png
- /** @var int */
- protected $bitDepth = 24;
/** @var null|string */
protected $filePath = null;
/** @var finfo */
@@ -102,7 +106,7 @@ class OC_Image implements \OCP\IImage {
*
* @return bool
*/
- public function valid() {
+ public function valid(): bool {
if ((is_resource($this->resource) && get_resource_type($this->resource) === 'gd') ||
(is_object($this->resource) && get_class($this->resource) === \GdImage::class)) {
return true;
@@ -112,12 +116,12 @@ class OC_Image implements \OCP\IImage {
}
/**
- * Returns the MIME type of the image or an empty string if no image is loaded.
+ * Returns the MIME type of the image or null if no image is loaded.
*
* @return string
*/
- public function mimeType() {
- return $this->valid() ? $this->mimeType : '';
+ public function mimeType(): ?string {
+ return $this->valid() ? $this->mimeType : null;
}
/**
@@ -125,7 +129,7 @@ class OC_Image implements \OCP\IImage {
*
* @return int
*/
- public function width() {
+ public function width(): int {
if ($this->valid()) {
$width = imagesx($this->resource);
if ($width !== false) {
@@ -140,7 +144,7 @@ class OC_Image implements \OCP\IImage {
*
* @return int
*/
- public function height() {
+ public function height(): int {
if ($this->valid()) {
$height = imagesy($this->resource);
if ($height !== false) {
@@ -155,7 +159,7 @@ class OC_Image implements \OCP\IImage {
*
* @return int
*/
- public function widthTopLeft() {
+ public function widthTopLeft(): int {
$o = $this->getOrientation();
$this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, ['app' => 'core']);
switch ($o) {
@@ -179,7 +183,7 @@ class OC_Image implements \OCP\IImage {
*
* @return int
*/
- public function heightTopLeft() {
+ public function heightTopLeft(): int {
$o = $this->getOrientation();
$this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, ['app' => 'core']);
switch ($o) {
@@ -204,7 +208,7 @@ class OC_Image implements \OCP\IImage {
* @param string $mimeType
* @return bool
*/
- public function show($mimeType = null) {
+ public function show(string $mimeType = null): bool {
if ($mimeType === null) {
$mimeType = $this->mimeType();
}
@@ -220,7 +224,7 @@ class OC_Image implements \OCP\IImage {
* @return bool
*/
- public function save($filePath = null, $mimeType = null) {
+ public function save(?string $filePath = null, ?string $mimeType = null): bool {
if ($mimeType === null) {
$mimeType = $this->mimeType();
}
@@ -243,7 +247,7 @@ class OC_Image implements \OCP\IImage {
* @return bool
* @throws Exception
*/
- private function _output($filePath = null, $mimeType = null) {
+ private function _output(?string $filePath = null, ?string $mimeType = null): bool {
if ($filePath) {
if (!file_exists(dirname($filePath))) {
mkdir(dirname($filePath), 0777, true);
@@ -252,7 +256,7 @@ class OC_Image implements \OCP\IImage {
if (!$isWritable) {
$this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', ['app' => 'core']);
return false;
- } elseif ($isWritable && file_exists($filePath) && !is_writable($filePath)) {
+ } elseif (file_exists($filePath) && !is_writable($filePath)) {
$this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', ['app' => 'core']);
return false;
}
@@ -290,6 +294,8 @@ class OC_Image implements \OCP\IImage {
$retVal = imagegif($this->resource, $filePath);
break;
case IMAGETYPE_JPEG:
+ /** @psalm-suppress InvalidScalarArgument */
+ imageinterlace($this->resource, (PHP_VERSION_ID >= 80000 ? true : 1));
$retVal = imagejpeg($this->resource, $filePath, $this->getJpegQuality());
break;
case IMAGETYPE_PNG:
@@ -307,7 +313,7 @@ class OC_Image implements \OCP\IImage {
$retVal = imagewbmp($this->resource, $filePath);
break;
case IMAGETYPE_BMP:
- $retVal = imagebmp($this->resource, $filePath, $this->bitDepth);
+ $retVal = imagebmp($this->resource, $filePath);
break;
default:
$retVal = imagepng($this->resource, $filePath);
@@ -348,12 +354,11 @@ class OC_Image implements \OCP\IImage {
}
/**
- * @return string Returns the mimetype of the data. Returns the empty string
- * if the data is not valid.
+ * @return string Returns the mimetype of the data. Returns null if the data is not valid.
*/
- public function dataMimeType() {
+ public function dataMimeType(): ?string {
if (!$this->valid()) {
- return '';
+ return null;
}
switch ($this->mimeType) {
@@ -369,7 +374,7 @@ class OC_Image implements \OCP\IImage {
/**
* @return null|string Returns the raw image data.
*/
- public function data() {
+ public function data(): ?string {
if (!$this->valid()) {
return null;
}
@@ -379,12 +384,10 @@ class OC_Image implements \OCP\IImage {
$res = imagepng($this->resource);
break;
case "image/jpeg":
+ /** @psalm-suppress InvalidScalarArgument */
+ imageinterlace($this->resource, (PHP_VERSION_ID >= 80000 ? true : 1));
$quality = $this->getJpegQuality();
- if ($quality !== null) {
- $res = imagejpeg($this->resource, null, $quality);
- } else {
- $res = imagejpeg($this->resource);
- }
+ $res = imagejpeg($this->resource, null, $quality);
break;
case "image/gif":
$res = imagegif($this->resource);
@@ -408,14 +411,15 @@ class OC_Image implements \OCP\IImage {
}
/**
- * @return int|null
+ * @return int
*/
- protected function getJpegQuality() {
- $quality = $this->config->getAppValue('preview', 'jpeg_quality', 90);
- if ($quality !== null) {
- $quality = min(100, max(10, (int) $quality));
+ protected function getJpegQuality(): int {
+ $quality = $this->config->getAppValue('preview', 'jpeg_quality', (string) self::DEFAULT_JPEG_QUALITY);
+ // TODO: remove when getAppValue is type safe
+ if ($quality === null) {
+ $quality = self::DEFAULT_JPEG_QUALITY;
}
- return $quality;
+ return min(100, max(10, (int) $quality));
}
/**
@@ -424,7 +428,7 @@ class OC_Image implements \OCP\IImage {
*
* @return int The orientation or -1 if no EXIF data is available.
*/
- public function getOrientation() {
+ public function getOrientation(): int {
if ($this->exif !== null) {
return $this->exif['Orientation'];
}
@@ -456,7 +460,7 @@ class OC_Image implements \OCP\IImage {
return $exif['Orientation'];
}
- public function readExif($data) {
+ public function readExif($data): void {
if (!is_callable('exif_read_data')) {
$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
return;
@@ -482,7 +486,7 @@ class OC_Image implements \OCP\IImage {
*
* @return bool
*/
- public function fixOrientation() {
+ public function fixOrientation(): bool {
if (!$this->valid()) {
$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
return false;
@@ -712,7 +716,7 @@ class OC_Image implements \OCP\IImage {
}
break;
case IMAGETYPE_BMP:
- $this->resource = $this->imagecreatefrombmp($imagePath);
+ $this->resource = imagecreatefrombmp($imagePath);
break;
case IMAGETYPE_WEBP:
if (imagetypes() & IMG_WEBP) {
@@ -774,10 +778,7 @@ class OC_Image implements \OCP\IImage {
* @param string $str A string of image data as read from a file.
* @return bool|resource|\GdImage An image resource or false on error
*/
- public function loadFromData($str) {
- if (!is_string($str)) {
- return false;
- }
+ public function loadFromData(string $str) {
if (!$this->checkImageDataSize($str)) {
return false;
}
@@ -803,10 +804,7 @@ class OC_Image implements \OCP\IImage {
* @param string $str A string base64 encoded string of image data.
* @return bool|resource|\GdImage An image resource or false on error
*/
- public function loadFromBase64($str) {
- if (!is_string($str)) {
- return false;
- }
+ public function loadFromBase64(string $str) {
$data = base64_decode($str);
if ($data) { // try to load from string data
if (!$this->checkImageDataSize($data)) {
@@ -827,170 +825,12 @@ class OC_Image implements \OCP\IImage {
}
/**
- * Create a new image from file or URL
- *
- * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm
- * @version 1.00
- * @param string $fileName <p>
- * Path to the BMP image.
- * </p>
- * @return bool|resource|\GdImage an image resource identifier on success, <b>FALSE</b> on errors.
- */
- private function imagecreatefrombmp($fileName) {
- if (!($fh = fopen($fileName, 'rb'))) {
- $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, ['app' => 'core']);
- return false;
- }
- // read file header
- $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
- // check for bitmap
- if ($meta['type'] != 19778) {
- fclose($fh);
- $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
- return false;
- }
- // read image header
- $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
- // read additional 16bit header
- if ($meta['bits'] == 16) {
- $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
- }
- // set bytes and padding
- $meta['bytes'] = $meta['bits'] / 8;
- $this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call
- $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
- if ($meta['decal'] == 4) {
- $meta['decal'] = 0;
- }
- // obtain imagesize
- if ($meta['imagesize'] < 1) {
- $meta['imagesize'] = $meta['filesize'] - $meta['offset'];
- // in rare cases filesize is equal to offset so we need to read physical size
- if ($meta['imagesize'] < 1) {
- $meta['imagesize'] = @filesize($fileName) - $meta['offset'];
- if ($meta['imagesize'] < 1) {
- fclose($fh);
- $this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
- return false;
- }
- }
- }
- // calculate colors
- $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
- // read color palette
- $palette = [];
- if ($meta['bits'] < 16) {
- $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
- // in rare cases the color value is signed
- if ($palette[1] < 0) {
- foreach ($palette as $i => $color) {
- $palette[$i] = $color + 16777216;
- }
- }
- }
- if (!$this->checkImageMemory($meta['width'], $meta['height'])) {
- fclose($fh);
- return false;
- }
- // create gd image
- $im = imagecreatetruecolor($meta['width'], $meta['height']);
- if ($im == false) {
- fclose($fh);
- $this->logger->warning(
- 'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'],
- ['app' => 'core']);
- return false;
- }
-
- $data = fread($fh, $meta['imagesize']);
- $p = 0;
- $vide = chr(0);
- $y = $meta['height'] - 1;
- $error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!';
- // loop through the image data beginning with the lower left corner
- while ($y >= 0) {
- $x = 0;
- while ($x < $meta['width']) {
- switch ($meta['bits']) {
- case 32:
- case 24:
- if (!($part = substr($data, $p, 3))) {
- $this->logger->warning($error, ['app' => 'core']);
- return $im;
- }
- $color = @unpack('V', $part . $vide);
- break;
- case 16:
- if (!($part = substr($data, $p, 2))) {
- fclose($fh);
- $this->logger->warning($error, ['app' => 'core']);
- return $im;
- }
- $color = @unpack('v', $part);
- $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3);
- break;
- case 8:
- $color = @unpack('n', $vide . ($data[$p] ?? ''));
- $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
- break;
- case 4:
- $color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
- $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
- $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
- break;
- case 1:
- $color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
- switch (($p * 8) % 8) {
- case 0:
- $color[1] = $color[1] >> 7;
- break;
- case 1:
- $color[1] = ($color[1] & 0x40) >> 6;
- break;
- case 2:
- $color[1] = ($color[1] & 0x20) >> 5;
- break;
- case 3:
- $color[1] = ($color[1] & 0x10) >> 4;
- break;
- case 4:
- $color[1] = ($color[1] & 0x8) >> 3;
- break;
- case 5:
- $color[1] = ($color[1] & 0x4) >> 2;
- break;
- case 6:
- $color[1] = ($color[1] & 0x2) >> 1;
- break;
- case 7:
- $color[1] = ($color[1] & 0x1);
- break;
- }
- $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
- break;
- default:
- fclose($fh);
- $this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', ['app' => 'core']);
- return false;
- }
- imagesetpixel($im, $x, $y, $color[1]);
- $x++;
- $p += $meta['bytes'];
- }
- $y--;
- $p += $meta['decal'];
- }
- fclose($fh);
- return $im;
- }
-
- /**
* Resizes the image preserving ratio.
*
- * @param integer $maxSize The maximum size of either the width or height.
+ * @param int $maxSize The maximum size of either the width or height.
* @return bool
*/
- public function resize($maxSize) {
+ public function resize(int $maxSize): bool {
if (!$this->valid()) {
$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
return false;
@@ -1005,7 +845,7 @@ class OC_Image implements \OCP\IImage {
* @param $maxSize
* @return resource|bool|\GdImage
*/
- private function resizeNew($maxSize) {
+ private function resizeNew(int $maxSize) {
if (!$this->valid()) {
$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
return false;
@@ -1041,7 +881,6 @@ class OC_Image implements \OCP\IImage {
return $this->valid();
}
-
/**
* @param int $width
* @param int $height
@@ -1086,7 +925,7 @@ class OC_Image implements \OCP\IImage {
* @param int $size maximum size for the result (optional)
* @return bool for success or failure
*/
- public function centerCrop($size = 0) {
+ public function centerCrop(int $size = 0): bool {
if (!$this->valid()) {
$this->logger->debug('OC_Image->centerCrop, No image loaded', ['app' => 'core']);
return false;
@@ -1100,10 +939,10 @@ class OC_Image implements \OCP\IImage {
$width = $height = min($widthOrig, $heightOrig);
if ($ratioOrig > 1) {
- $x = ($widthOrig / 2) - ($width / 2);
+ $x = (int) (($widthOrig / 2) - ($width / 2));
$y = 0;
} else {
- $y = ($heightOrig / 2) - ($height / 2);
+ $y = (int) (($heightOrig / 2) - ($height / 2));
$x = 0;
}
if ($size > 0) {
@@ -1196,11 +1035,11 @@ class OC_Image implements \OCP\IImage {
*
* Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up
*
- * @param integer $maxWidth
- * @param integer $maxHeight
+ * @param int $maxWidth
+ * @param int $maxHeight
* @return bool
*/
- public function fitIn($maxWidth, $maxHeight) {
+ public function fitIn(int $maxWidth, int $maxHeight): bool {
if (!$this->valid()) {
$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
return false;
@@ -1219,11 +1058,11 @@ class OC_Image implements \OCP\IImage {
/**
* Shrinks larger images to fit within specified boundaries while preserving ratio.
*
- * @param integer $maxWidth
- * @param integer $maxHeight
+ * @param int $maxWidth
+ * @param int $maxHeight
* @return bool
*/
- public function scaleDownToFit($maxWidth, $maxHeight) {
+ public function scaleDownToFit(int $maxWidth, int $maxHeight): bool {
if (!$this->valid()) {
$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
return false;
@@ -1259,7 +1098,6 @@ class OC_Image implements \OCP\IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->imageType = $this->imageType;
$image->mimeType = $this->mimeType;
- $image->bitDepth = $this->bitDepth;
$image->resource = $this->cropNew($x, $y, $w, $h);
return $image;
@@ -1269,7 +1107,6 @@ class OC_Image implements \OCP\IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->imageType = $this->imageType;
$image->mimeType = $this->mimeType;
- $image->bitDepth = $this->bitDepth;
$image->resource = $this->preciseResizeNew($width, $height);
return $image;
@@ -1279,7 +1116,6 @@ class OC_Image implements \OCP\IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->imageType = $this->imageType;
$image->mimeType = $this->mimeType;
- $image->bitDepth = $this->bitDepth;
$image->resource = $this->resizeNew($maxSize);
return $image;
@@ -1288,7 +1124,7 @@ class OC_Image implements \OCP\IImage {
/**
* Destroys the current image and resets the object
*/
- public function destroy() {
+ public function destroy(): void {
if ($this->valid()) {
imagedestroy($this->resource);
}
@@ -1300,123 +1136,6 @@ class OC_Image implements \OCP\IImage {
}
}
-if (!function_exists('imagebmp')) {
- /**
- * Output a BMP image to either the browser or a file
- *
- * @link http://www.ugia.cn/wp-data/imagebmp.php
- * @author legend <legendsky@hotmail.com>
- * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
- * @author mgutt <marc@gutt.it>
- * @version 1.00
- * @param resource|\GdImage $im
- * @param string $fileName [optional] <p>The path to save the file to.</p>
- * @param int $bit [optional] <p>Bit depth, (default is 24).</p>
- * @param int $compression [optional]
- * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
- */
- function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) {
- if (!in_array($bit, [1, 4, 8, 16, 24, 32])) {
- $bit = 24;
- } elseif ($bit == 32) {
- $bit = 24;
- }
- $bits = (int)pow(2, $bit);
- imagetruecolortopalette($im, true, $bits);
- $width = imagesx($im);
- $height = imagesy($im);
- $colorsNum = imagecolorstotal($im);
- $rgbQuad = '';
- if ($bit <= 8) {
- for ($i = 0; $i < $colorsNum; $i++) {
- $colors = imagecolorsforindex($im, $i);
- $rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0";
- }
- $bmpData = '';
- if ($compression == 0 || $bit < 8) {
- $compression = 0;
- $extra = '';
- $padding = 4 - ceil($width / (8 / $bit)) % 4;
- if ($padding % 4 != 0) {
- $extra = str_repeat("\0", $padding);
- }
- for ($j = $height - 1; $j >= 0; $j--) {
- $i = 0;
- while ($i < $width) {
- $bin = 0;
- $limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
- for ($k = 8 - $bit; $k >= $limit; $k -= $bit) {
- $index = imagecolorat($im, $i, $j);
- $bin |= $index << $k;
- $i++;
- }
- $bmpData .= chr($bin);
- }
- $bmpData .= $extra;
- }
- } // RLE8
- elseif ($compression == 1 && $bit == 8) {
- for ($j = $height - 1; $j >= 0; $j--) {
- $lastIndex = null;
- $sameNum = 0;
- for ($i = 0; $i <= $width; $i++) {
- $index = imagecolorat($im, $i, $j);
- if ($index !== $lastIndex || $sameNum > 255) {
- if ($sameNum != 0) {
- $bmpData .= chr($sameNum) . chr($lastIndex);
- }
- $lastIndex = $index;
- $sameNum = 1;
- } else {
- $sameNum++;
- }
- }
- $bmpData .= "\0\0";
- }
- $bmpData .= "\0\1";
- }
- $sizeQuad = strlen($rgbQuad);
- $sizeData = strlen($bmpData);
- } else {
- $extra = '';
- $padding = 4 - ($width * ($bit / 8)) % 4;
- if ($padding % 4 != 0) {
- $extra = str_repeat("\0", $padding);
- }
- $bmpData = '';
- for ($j = $height - 1; $j >= 0; $j--) {
- for ($i = 0; $i < $width; $i++) {
- $index = imagecolorat($im, $i, $j);
- $colors = imagecolorsforindex($im, $index);
- if ($bit == 16) {
- $bin = 0 << $bit;
- $bin |= ($colors['red'] >> 3) << 10;
- $bin |= ($colors['green'] >> 3) << 5;
- $bin |= $colors['blue'] >> 3;
- $bmpData .= pack("v", $bin);
- } else {
- $bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
- }
- }
- $bmpData .= $extra;
- }
- $sizeQuad = 0;
- $sizeData = strlen($bmpData);
- $colorsNum = 0;
- }
- $fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad);
- $infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0);
- if ($fileName != '') {
- $fp = fopen($fileName, 'wb');
- fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
- fclose($fp);
- return true;
- }
- echo $fileHeader . $infoHeader . $rgbQuad . $bmpData;
- return true;
- }
-}
-
if (!function_exists('exif_imagetype')) {
/**
* Workaround if exif_imagetype does not exist
@@ -1425,7 +1144,7 @@ if (!function_exists('exif_imagetype')) {
* @param string $fileName
* @return string|boolean
*/
- function exif_imagetype($fileName) {
+ function exif_imagetype(string $fileName) {
if (($info = getimagesize($fileName)) !== false) {
return $info[2];
}
diff --git a/lib/private/legacy/OC_Response.php b/lib/private/legacy/OC_Response.php
index e4525fe9e10..b849710e90b 100644
--- a/lib/private/legacy/OC_Response.php
+++ b/lib/private/legacy/OC_Response.php
@@ -52,19 +52,6 @@ class OC_Response {
* @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);
}
diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php
index 4778fe33097..429f7ed5d05 100644
--- a/lib/private/legacy/OC_Util.php
+++ b/lib/private/legacy/OC_Util.php
@@ -255,7 +255,7 @@ class OC_Util {
closedir($dir);
return;
}
- stream_copy_to_stream($sourceStream, $child->fopen('w'));
+ $child->putContent($sourceStream);
}
}
}