aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private
diff options
context:
space:
mode:
authorTomasz Grobelny <tomasz@grobelny.net>2018-11-03 23:55:06 +0000
committerTomasz Grobelny <tomasz@grobelny.net>2018-11-04 09:39:19 +0000
commit1fa6e0be23f0684ce76de3311c52e01495a4de7e (patch)
treeef50b564c793d77ef5ade04349f6c5f97a22c7f5 /lib/private
parent41687ef00fa5667467564770a4e29403e32d167f (diff)
parentb9783993da7c689b0b1e55dc5696b0c3f90a4c10 (diff)
downloadnextcloud-server-1fa6e0be23f0684ce76de3311c52e01495a4de7e.tar.gz
nextcloud-server-1fa6e0be23f0684ce76de3311c52e01495a4de7e.zip
Merge remote-tracking branch 'upstream/master' into fix_file_move
Signed-off-by: Tomasz Grobelny <tomasz@grobelny.net>
Diffstat (limited to 'lib/private')
-rw-r--r--lib/private/AppFramework/Http/Request.php48
-rw-r--r--lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php4
-rw-r--r--lib/private/Authentication/Exceptions/ExpiredTokenException.php4
-rw-r--r--lib/private/Authentication/Token/DefaultTokenProvider.php1
-rw-r--r--lib/private/Authentication/Token/IProvider.php1
-rw-r--r--lib/private/Authentication/Token/Manager.php1
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenProvider.php1
-rw-r--r--lib/private/BackgroundJob/JobList.php40
-rw-r--r--lib/private/Collaboration/Collaborators/MailPlugin.php21
-rw-r--r--lib/private/Collaboration/Collaborators/RemotePlugin.php69
-rw-r--r--lib/private/Files/ObjectStore/ObjectStoreStorage.php68
-rw-r--r--lib/private/Files/Storage/Common.php22
-rw-r--r--lib/private/Files/Storage/Local.php4
-rw-r--r--lib/private/Files/Storage/Wrapper/Encryption.php9
-rw-r--r--lib/private/Files/Storage/Wrapper/Jail.php15
-rw-r--r--lib/private/Files/Storage/Wrapper/Wrapper.php17
-rw-r--r--lib/private/Files/Stream/CountReadStream.php65
-rw-r--r--lib/private/FullTextSearch/FullTextSearchManager.php227
-rw-r--r--lib/private/Mail/EMailTemplate.php5
-rw-r--r--lib/private/Mail/Mailer.php6
-rw-r--r--lib/private/Preview/Generator.php18
-rw-r--r--lib/private/Repair.php2
-rw-r--r--lib/private/Repair/NC15/SetVcardDatabaseUID.php137
-rw-r--r--lib/private/Server.php8
-rw-r--r--lib/private/Settings/Admin/Sharing.php9
-rw-r--r--lib/private/Share/Share.php20
-rw-r--r--lib/private/Share20/DefaultShareProvider.php15
-rw-r--r--lib/private/Share20/Share.php30
-rw-r--r--lib/private/Template/JSConfigHelper.php5
-rw-r--r--lib/private/Updater.php27
-rw-r--r--lib/private/User/Session.php12
31 files changed, 774 insertions, 137 deletions
diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php
index 0485d178b49..2c745973ed2 100644
--- a/lib/private/AppFramework/Http/Request.php
+++ b/lib/private/AppFramework/Http/Request.php
@@ -320,14 +320,18 @@ class Request implements \ArrayAccess, \Countable, IRequest {
// There's a few headers that seem to end up in the top-level
// server array.
- switch($name) {
+ switch ($name) {
case 'CONTENT_TYPE' :
case 'CONTENT_LENGTH' :
if (isset($this->server[$name])) {
return $this->server[$name];
}
break;
-
+ case 'REMOTE_ADDR' :
+ if (isset($this->server[$name])) {
+ return $this->server[$name];
+ }
+ break;
}
return '';
@@ -595,6 +599,44 @@ class Request implements \ArrayAccess, \Countable, IRequest {
}
/**
+ * Checks if given $remoteAddress matches given $trustedProxy.
+ * If $trustedProxy is an IPv4 IP range given in CIDR notation, true will be returned if
+ * $remoteAddress is an IPv4 address within that IP range.
+ * Otherwise $remoteAddress will be compared to $trustedProxy literally and the result
+ * will be returned.
+ * @return boolean true if $remoteAddress matches $trustedProxy, false otherwise
+ */
+ protected function matchesTrustedProxy($trustedProxy, $remoteAddress) {
+ $cidrre = '/^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\/([0-9]{1,2})$/';
+
+ if (preg_match($cidrre, $trustedProxy, $match)) {
+ $net = $match[1];
+ $shiftbits = min(32, max(0, 32 - intval($match[2])));
+ $netnum = ip2long($net) >> $shiftbits;
+ $ipnum = ip2long($remoteAddress) >> $shiftbits;
+
+ return $ipnum === $netnum;
+ }
+
+ return $trustedProxy === $remoteAddress;
+ }
+
+ /**
+ * Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
+ * For details regarding what "match" means, refer to `matchesTrustedProxy`.
+ * @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
+ */
+ protected function isTrustedProxy($trustedProxies, $remoteAddress) {
+ foreach ($trustedProxies as $tp) {
+ if ($this->matchesTrustedProxy($tp, $remoteAddress)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Returns the remote address, if the connection came from a trusted proxy
* and `forwarded_for_headers` has been configured then the IP address
* specified in this header will be returned instead.
@@ -605,7 +647,7 @@ class Request implements \ArrayAccess, \Countable, IRequest {
$remoteAddress = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
- if(\is_array($trustedProxies) && \in_array($remoteAddress, $trustedProxies)) {
+ if(\is_array($trustedProxies) && $this->isTrustedProxy($trustedProxies, $remoteAddress)) {
$forwardedForHeaders = $this->config->getSystemValue('forwarded_for_headers', [
'HTTP_X_FORWARDED_FOR'
// only have one default, so we cannot ship an insecure product out of the box
diff --git a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
index 463e7cd93c9..7c1c4595e9a 100644
--- a/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
+++ b/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php
@@ -39,6 +39,8 @@ class PasswordConfirmationMiddleware extends Middleware {
private $userSession;
/** @var ITimeFactory */
private $timeFactory;
+ /** @var array */
+ private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true];
/**
* PasswordConfirmationMiddleware constructor.
@@ -73,7 +75,7 @@ class PasswordConfirmationMiddleware extends Middleware {
$lastConfirm = (int) $this->session->get('last-password-confirm');
// we can't check the password against a SAML backend, so skip password confirmation in this case
- if ($backendClassName !== 'user_saml' && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay
+ if (!isset($this->excludedUserBackEnds[$backendClassName]) && $lastConfirm < ($this->timeFactory->getTime() - (30 * 60 + 15))) { // allow 15 seconds delay
throw new NotConfirmedException();
}
}
diff --git a/lib/private/Authentication/Exceptions/ExpiredTokenException.php b/lib/private/Authentication/Exceptions/ExpiredTokenException.php
index a45ca5b6955..d5b2e2cbca7 100644
--- a/lib/private/Authentication/Exceptions/ExpiredTokenException.php
+++ b/lib/private/Authentication/Exceptions/ExpiredTokenException.php
@@ -21,9 +21,9 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-namespace OC\Authentication\Token;
+namespace OC\Authentication\Exceptions;
-use OC\Authentication\Exceptions\InvalidTokenException;
+use OC\Authentication\Token\IToken;
class ExpiredTokenException extends InvalidTokenException {
/** @var IToken */
diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php
index a27a875a27f..98609a3f14b 100644
--- a/lib/private/Authentication/Token/DefaultTokenProvider.php
+++ b/lib/private/Authentication/Token/DefaultTokenProvider.php
@@ -29,6 +29,7 @@ declare(strict_types=1);
namespace OC\Authentication\Token;
use Exception;
+use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OCP\AppFramework\Db\DoesNotExistException;
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
index 7ee76b7b384..21223cecdf7 100644
--- a/lib/private/Authentication/Token/IProvider.php
+++ b/lib/private/Authentication/Token/IProvider.php
@@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OC\Authentication\Token;
+use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php
index 98a48f41523..3174599221d 100644
--- a/lib/private/Authentication/Token/Manager.php
+++ b/lib/private/Authentication/Token/Manager.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace OC\Authentication\Token;
+use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
index 33c0b1d59eb..9f596ac4568 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace OC\Authentication\Token;
+use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OCP\AppFramework\Db\DoesNotExistException;
diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php
index fab608cf164..e890c35868b 100644
--- a/lib/private/BackgroundJob/JobList.php
+++ b/lib/private/BackgroundJob/JobList.php
@@ -48,9 +48,6 @@ class JobList implements IJobList {
/**@var ITimeFactory */
protected $timeFactory;
- /** @var int - 12 hours * 3600 seconds*/
- private $jobTimeOut = 43200;
-
/**
* @param IDBConnection $connection
* @param IConfig $config
@@ -186,7 +183,7 @@ class JobList implements IJobList {
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from('jobs')
- ->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - $this->jobTimeOut, IQueryBuilder::PARAM_INT)))
+ ->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->lte('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
->orderBy('last_checked', 'ASC')
->setMaxResults(1);
@@ -346,39 +343,4 @@ class JobList implements IJobList {
->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
$query->execute();
}
-
- /**
- * checks if a job is still running (reserved_at time is smaller than 12 hours ago)
- *
- * Background information:
- *
- * The 12 hours is the same timeout that is also used to re-schedule an non-terminated
- * job (see getNext()). The idea here is to give a job enough time to run very
- * long but still be able to recognize that it maybe crashed and re-schedule it
- * after the timeout. It's more likely to be crashed at that time than it ran
- * that long.
- *
- * In theory it could lead to an nearly endless loop (as in - at most 12 hours).
- * The cron command will not start new jobs when maintenance mode is active and
- * this method is only executed in maintenance mode (see where it is called in
- * the upgrader class. So this means in the worst case we wait 12 hours when a
- * job has crashed. On the other hand: then the instance should be fixed anyways.
- *
- * @return bool
- */
- public function isAnyJobRunning(): bool {
- $query = $this->connection->getQueryBuilder();
- $query->select('*')
- ->from('jobs')
- ->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - $this->jobTimeOut, IQueryBuilder::PARAM_INT)))
- ->setMaxResults(1);
- $result = $query->execute();
- $row = $result->fetch();
- $result->closeCursor();
-
- if ($row) {
- return true;
- }
- return false;
- }
}
diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php
index 101d6845ec3..6faa5d5d125 100644
--- a/lib/private/Collaboration/Collaborators/MailPlugin.php
+++ b/lib/private/Collaboration/Collaborators/MailPlugin.php
@@ -84,11 +84,17 @@ class MailPlugin implements ISearchPlugin {
foreach ($addressBookContacts as $contact) {
if (isset($contact['EMAIL'])) {
$emailAddresses = $contact['EMAIL'];
- if (!is_array($emailAddresses)) {
+ if (\is_string($emailAddresses)) {
$emailAddresses = [$emailAddresses];
}
- foreach ($emailAddresses as $emailAddress) {
+ foreach ($emailAddresses as $type => $emailAddress) {
$displayName = $emailAddress;
+ $emailAddressType = null;
+ if (\is_array($emailAddress)) {
+ $emailAddressData = $emailAddress;
+ $emailAddress = $emailAddressData['value'];
+ $emailAddressType = $emailAddressData['type'];
+ }
if (isset($contact['FN'])) {
$displayName = $contact['FN'] . ' (' . $emailAddress . ')';
}
@@ -121,6 +127,8 @@ class MailPlugin implements ISearchPlugin {
if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
$singleResult = [[
'label' => $displayName,
+ 'uuid' => $contact['UID'],
+ 'name' => $contact['FN'],
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $cloud->getUser(),
@@ -142,6 +150,8 @@ class MailPlugin implements ISearchPlugin {
if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
$userResults['wide'][] = [
'label' => $displayName,
+ 'uuid' => $contact['UID'],
+ 'name' => $contact['FN'],
'value' => [
'shareType' => Share::SHARE_TYPE_USER,
'shareWith' => $cloud->getUser(),
@@ -160,6 +170,9 @@ class MailPlugin implements ISearchPlugin {
}
$result['exact'][] = [
'label' => $displayName,
+ 'uuid' => $contact['UID'],
+ 'name' => $contact['FN'],
+ 'type' => $emailAddressType ?? '',
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $emailAddress,
@@ -168,6 +181,9 @@ class MailPlugin implements ISearchPlugin {
} else {
$result['wide'][] = [
'label' => $displayName,
+ 'uuid' => $contact['UID'],
+ 'name' => $contact['FN'],
+ 'type' => $emailAddressType ?? '',
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $emailAddress,
@@ -194,6 +210,7 @@ class MailPlugin implements ISearchPlugin {
if (!$searchResult->hasExactIdMatch($emailType) && filter_var($search, FILTER_VALIDATE_EMAIL)) {
$result['exact'][] = [
'label' => $search,
+ 'uuid' => $search,
'value' => [
'shareType' => Share::SHARE_TYPE_EMAIL,
'shareWith' => $search,
diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php
index e0f5298f83b..d877346b155 100644
--- a/lib/private/Collaboration/Collaborators/RemotePlugin.php
+++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php
@@ -30,6 +30,8 @@ use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\Contacts\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IConfig;
+use OCP\IUserManager;
+use OCP\IUserSession;
use OCP\Share;
class RemotePlugin implements ISearchPlugin {
@@ -41,12 +43,20 @@ class RemotePlugin implements ISearchPlugin {
private $cloudIdManager;
/** @var IConfig */
private $config;
+ /** @var IUserManager */
+ private $userManager;
+ /** @var string */
+ private $userId = '';
- public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) {
+ public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IUserManager $userManager, IUserSession $userSession) {
$this->contactsManager = $contactsManager;
$this->cloudIdManager = $cloudIdManager;
$this->config = $config;
-
+ $this->userManager = $userManager;
+ $user = $userSession->getUser();
+ if ($user !== null) {
+ $this->userId = $user->getUID();
+ }
$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
}
@@ -63,23 +73,47 @@ class RemotePlugin implements ISearchPlugin {
}
if (isset($contact['CLOUD'])) {
$cloudIds = $contact['CLOUD'];
- if (!is_array($cloudIds)) {
+ if (is_string($cloudIds)) {
$cloudIds = [$cloudIds];
}
$lowerSearch = strtolower($search);
foreach ($cloudIds as $cloudId) {
+ $cloudIdType = '';
+ if (\is_array($cloudId)) {
+ $cloudIdData = $cloudId;
+ $cloudId = $cloudIdData['value'];
+ $cloudIdType = $cloudIdData['type'];
+ }
try {
- list(, $serverUrl) = $this->splitUserRemote($cloudId);
+ list($remoteUser, $serverUrl) = $this->splitUserRemote($cloudId);
} catch (\InvalidArgumentException $e) {
continue;
}
+ $localUser = $this->userManager->get($remoteUser);
+ /**
+ * Add local share if remote cloud id matches a local user ones
+ */
+ if ($localUser !== null && $remoteUser !== $this->userId && $cloudId === $localUser->getCloudId() ) {
+ $result['wide'][] = [
+ 'label' => $contact['FN'],
+ 'uuid' => $contact['UID'],
+ 'value' => [
+ 'shareType' => Share::SHARE_TYPE_USER,
+ 'shareWith' => $remoteUser
+ ]
+ ];
+ }
+
if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) {
if (strtolower($cloudId) === $lowerSearch) {
$searchResult->markExactIdMatch($resultType);
}
$result['exact'][] = [
'label' => $contact['FN'] . " ($cloudId)",
+ 'uuid' => $contact['UID'],
+ 'name' => $contact['FN'],
+ 'type' => $cloudIdType,
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $cloudId,
@@ -89,6 +123,9 @@ class RemotePlugin implements ISearchPlugin {
} else {
$result['wide'][] = [
'label' => $contact['FN'] . " ($cloudId)",
+ 'uuid' => $contact['UID'],
+ 'name' => $contact['FN'],
+ 'type' => $cloudIdType,
'value' => [
'shareType' => Share::SHARE_TYPE_REMOTE,
'shareWith' => $cloudId,
@@ -106,14 +143,24 @@ class RemotePlugin implements ISearchPlugin {
$result['wide'] = array_slice($result['wide'], $offset, $limit);
}
+ /**
+ * Add generic share with remote item for valid cloud ids that are not users of the local instance
+ */
if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) {
- $result['exact'][] = [
- 'label' => $search,
- 'value' => [
- 'shareType' => Share::SHARE_TYPE_REMOTE,
- 'shareWith' => $search,
- ],
- ];
+ try {
+ list($remoteUser, $serverUrl) = $this->splitUserRemote($search);
+ $localUser = $this->userManager->get($remoteUser);
+ if ($localUser === null || $search !== $localUser->getCloudId()) {
+ $result['exact'][] = [
+ 'label' => $search,
+ 'value' => [
+ 'shareType' => Share::SHARE_TYPE_REMOTE,
+ 'shareWith' => $search,
+ ],
+ ];
+ }
+ } catch (\InvalidArgumentException $e) {
+ }
}
$searchResult->addResultSet($resultType, $result['wide'], $result['exact']);
diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
index 3ce919a4cbe..71acd27783c 100644
--- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php
+++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php
@@ -28,6 +28,7 @@ namespace OC\Files\ObjectStore;
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\IteratorDirectory;
use OC\Files\Cache\CacheEntry;
+use OC\Files\Stream\CountReadStream;
use OCP\Files\ObjectStore\IObjectStore;
class ObjectStoreStorage extends \OC\Files\Storage\Common {
@@ -382,25 +383,48 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
}
public function writeBack($tmpFile, $path) {
+ $size = filesize($tmpFile);
+ $this->writeStream($path, fopen($tmpFile, 'r'), $size);
+ }
+
+ /**
+ * external changes are not supported, exclusive access to the object storage is assumed
+ *
+ * @param string $path
+ * @param int $time
+ * @return false
+ */
+ public function hasUpdated($path, $time) {
+ return false;
+ }
+
+ public function needsPartFile() {
+ return false;
+ }
+
+ public function file_put_contents($path, $data) {
+ $stream = fopen('php://temp', 'r+');
+ fwrite($stream, $data);
+ rewind($stream);
+ return $this->writeStream($path, $stream, strlen($data)) > 0;
+ }
+
+ public function writeStream(string $path, $stream, int $size = null): int {
$stat = $this->stat($path);
if (empty($stat)) {
// create new file
- $stat = array(
+ $stat = [
'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
- );
+ ];
}
// update stat with new data
$mTime = time();
- $stat['size'] = filesize($tmpFile);
+ $stat['size'] = (int)$size;
$stat['mtime'] = $mTime;
$stat['storage_mtime'] = $mTime;
- // run path based detection first, to use file extension because $tmpFile is only a random string
$mimetypeDetector = \OC::$server->getMimeTypeDetector();
$mimetype = $mimetypeDetector->detectPath($path);
- if ($mimetype === 'application/octet-stream') {
- $mimetype = $mimetypeDetector->detect($tmpFile);
- }
$stat['mimetype'] = $mimetype;
$stat['etag'] = $this->getETag($path);
@@ -408,7 +432,20 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
$fileId = $this->getCache()->put($path, $stat);
try {
//upload to object storage
- $this->objectStore->writeObject($this->getURN($fileId), fopen($tmpFile, 'r'));
+ if ($size === null) {
+ $countStream = CountReadStream::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
+ $this->getCache()->update($fileId, [
+ 'size' => $writtenSize
+ ]);
+ $size = $writtenSize;
+ });
+ $this->objectStore->writeObject($this->getURN($fileId), $countStream);
+ if (is_resource($countStream)) {
+ fclose($countStream);
+ }
+ } else {
+ $this->objectStore->writeObject($this->getURN($fileId), $stream);
+ }
} catch (\Exception $ex) {
$this->getCache()->remove($path);
$this->logger->logException($ex, [
@@ -417,20 +454,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
]);
throw $ex; // make this bubble up
}
- }
- /**
- * external changes are not supported, exclusive access to the object storage is assumed
- *
- * @param string $path
- * @param int $time
- * @return false
- */
- public function hasUpdated($path, $time) {
- return false;
- }
-
- public function needsPartFile() {
- return false;
+ return $size;
}
}
diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php
index b6c82f3a1df..72fe3a79792 100644
--- a/lib/private/Files/Storage/Common.php
+++ b/lib/private/Files/Storage/Common.php
@@ -54,6 +54,7 @@ use OCP\Files\InvalidPathException;
use OCP\Files\ReservedWordException;
use OCP\Files\Storage\ILockingStorage;
use OCP\Files\Storage\IStorage;
+use OCP\Files\Storage\IWriteStreamStorage;
use OCP\ILogger;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
@@ -69,7 +70,7 @@ use OCP\Lock\LockedException;
* Some \OC\Files\Storage\Common methods call functions which are first defined
* in classes which extend it, e.g. $this->stat() .
*/
-abstract class Common implements Storage, ILockingStorage {
+abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
use LocalTempFileTrait;
@@ -809,4 +810,23 @@ abstract class Common implements Storage, ILockingStorage {
public function needsPartFile() {
return true;
}
+
+ /**
+ * fallback implementation
+ *
+ * @param string $path
+ * @param resource $stream
+ * @param int $size
+ * @return int
+ */
+ public function writeStream(string $path, $stream, int $size = null): int {
+ $target = $this->fopen($path, 'w');
+ if (!$target) {
+ return 0;
+ }
+ list($count, $result) = \OC_Helper::streamCopy($stream, $target);
+ fclose($stream);
+ fclose($target);
+ return $count;
+ }
}
diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php
index 46b53dcf95c..5f7232e64b3 100644
--- a/lib/private/Files/Storage/Local.php
+++ b/lib/private/Files/Storage/Local.php
@@ -462,4 +462,8 @@ class Local extends \OC\Files\Storage\Common {
return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
}
}
+
+ public function writeStream(string $path, $stream, int $size = null): int {
+ return (int)file_put_contents($this->getSourcePath($path), $stream);
+ }
}
diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php
index 42653b2d4a6..e1c1225e0cc 100644
--- a/lib/private/Files/Storage/Wrapper/Encryption.php
+++ b/lib/private/Files/Storage/Wrapper/Encryption.php
@@ -1029,4 +1029,13 @@ class Encryption extends Wrapper {
}
+ public function writeStream(string $path, $stream, int $size = null): int {
+ // always fall back to fopen
+ $target = $this->fopen($path, 'w');
+ list($count, $result) = \OC_Helper::streamCopy($stream, $target);
+ fclose($stream);
+ fclose($target);
+ return $count;
+ }
+
}
diff --git a/lib/private/Files/Storage/Wrapper/Jail.php b/lib/private/Files/Storage/Wrapper/Jail.php
index 56514af6d80..f21b5716467 100644
--- a/lib/private/Files/Storage/Wrapper/Jail.php
+++ b/lib/private/Files/Storage/Wrapper/Jail.php
@@ -29,6 +29,7 @@ use OC\Files\Cache\Wrapper\CacheJail;
use OC\Files\Cache\Wrapper\JailPropagator;
use OC\Files\Filesystem;
use OCP\Files\Storage\IStorage;
+use OCP\Files\Storage\IWriteStreamStorage;
use OCP\Lock\ILockingProvider;
/**
@@ -515,4 +516,18 @@ class Jail extends Wrapper {
$this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
return $this->propagator;
}
+
+ public function writeStream(string $path, $stream, int $size = null): int {
+ $storage = $this->getWrapperStorage();
+ if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
+ /** @var IWriteStreamStorage $storage */
+ return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
+ } else {
+ $target = $this->fopen($path, 'w');
+ list($count, $result) = \OC_Helper::streamCopy($stream, $target);
+ fclose($stream);
+ fclose($target);
+ return $count;
+ }
+ }
}
diff --git a/lib/private/Files/Storage/Wrapper/Wrapper.php b/lib/private/Files/Storage/Wrapper/Wrapper.php
index 060c596ad65..f9c84b89fe5 100644
--- a/lib/private/Files/Storage/Wrapper/Wrapper.php
+++ b/lib/private/Files/Storage/Wrapper/Wrapper.php
@@ -32,9 +32,10 @@ namespace OC\Files\Storage\Wrapper;
use OCP\Files\InvalidPathException;
use OCP\Files\Storage\ILockingStorage;
use OCP\Files\Storage\IStorage;
+use OCP\Files\Storage\IWriteStreamStorage;
use OCP\Lock\ILockingProvider;
-class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage {
+class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStreamStorage {
/**
* @var \OC\Files\Storage\Storage $storage
*/
@@ -621,4 +622,18 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage {
public function needsPartFile() {
return $this->getWrapperStorage()->needsPartFile();
}
+
+ public function writeStream(string $path, $stream, int $size = null): int {
+ $storage = $this->getWrapperStorage();
+ if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
+ /** @var IWriteStreamStorage $storage */
+ return $storage->writeStream($path, $stream, $size);
+ } else {
+ $target = $this->fopen($path, 'w');
+ list($count, $result) = \OC_Helper::streamCopy($stream, $target);
+ fclose($stream);
+ fclose($target);
+ return $count;
+ }
+ }
}
diff --git a/lib/private/Files/Stream/CountReadStream.php b/lib/private/Files/Stream/CountReadStream.php
new file mode 100644
index 00000000000..93cadf8f214
--- /dev/null
+++ b/lib/private/Files/Stream/CountReadStream.php
@@ -0,0 +1,65 @@
+<?php declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
+ *
+ * @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\Files\Stream;
+
+use Icewind\Streams\Wrapper;
+
+class CountReadStream extends Wrapper {
+ /** @var int */
+ private $count;
+
+ /** @var callback */
+ private $callback;
+
+ public static function wrap($source, $callback) {
+ $context = stream_context_create(array(
+ 'count' => array(
+ 'source' => $source,
+ 'callback' => $callback,
+ )
+ ));
+ return Wrapper::wrapSource($source, $context, 'count', self::class);
+ }
+
+ public function dir_opendir($path, $options) {
+ return false;
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path) {
+ $context = $this->loadContext('count');
+
+ $this->callback = $context['callback'];
+ return true;
+ }
+
+ public function stream_read($count) {
+ $result = parent::stream_read($count);
+ $this->count += strlen($result);
+ return $result;
+ }
+
+ public function stream_close() {
+ $result = parent::stream_close();
+ call_user_func($this->callback, $this->count);
+ return $result;
+ }
+}
diff --git a/lib/private/FullTextSearch/FullTextSearchManager.php b/lib/private/FullTextSearch/FullTextSearchManager.php
new file mode 100644
index 00000000000..9a9b077cf23
--- /dev/null
+++ b/lib/private/FullTextSearch/FullTextSearchManager.php
@@ -0,0 +1,227 @@
+<?php
+declare(strict_types=1);
+
+
+/**
+ * FullTextSearch - Full text search framework for Nextcloud
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Maxence Lange <maxence@artificial-owl.com>
+ * @copyright 2018, Maxence Lange <maxence@artificial-owl.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\FullTextSearch;
+
+
+use OCP\FullTextSearch\Exceptions\FullTextSearchAppNotAvailableException;
+use OCP\FullTextSearch\IFullTextSearchManager;
+use OCP\FullTextSearch\Model\IIndex;
+use OCP\FullTextSearch\Model\ISearchResult;
+use OCP\FullTextSearch\Service\IIndexService;
+use OCP\FullTextSearch\Service\IProviderService;
+use OCP\FullTextSearch\Service\ISearchService;
+
+
+/**
+ * Class FullTextSearchManager
+ *
+ * @package OC\FullTextSearch
+ */
+class FullTextSearchManager implements IFullTextSearchManager {
+
+
+ /** @var IProviderService */
+ private $providerService;
+
+ /** @var IIndexService */
+ private $indexService;
+
+ /** @var ISearchService */
+ private $searchService;
+
+
+ /**
+ * @since 15.0.0
+ *
+ * @param IProviderService $providerService
+ */
+ public function registerProviderService(IProviderService $providerService) {
+ $this->providerService = $providerService;
+ }
+
+ /**
+ * @since 15.0.0
+ *
+ * @param IIndexService $indexService
+ */
+ public function registerIndexService(IIndexService $indexService) {
+ $this->indexService = $indexService;
+ }
+
+ /**
+ * @since 15.0.0
+ *
+ * @param ISearchService $searchService
+ */
+ public function registerSearchService(ISearchService $searchService) {
+ $this->searchService = $searchService;
+ }
+
+
+ /**
+ * @return IProviderService
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ private function getProviderService(): IProviderService {
+ if ($this->providerService === null) {
+ throw new FullTextSearchAppNotAvailableException('No IProviderService registered');
+ }
+
+ return $this->providerService;
+ }
+
+
+ /**
+ * @return IIndexService
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ private function getIndexService(): IIndexService {
+ if ($this->indexService === null) {
+ throw new FullTextSearchAppNotAvailableException('No IIndexService registered');
+ }
+
+ return $this->indexService;
+ }
+
+
+ /**
+ * @return ISearchService
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ private function getSearchService(): ISearchService {
+ if ($this->searchService === null) {
+ throw new FullTextSearchAppNotAvailableException('No ISearchService registered');
+ }
+
+ return $this->searchService;
+ }
+
+
+ /**
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ public function addJavascriptAPI() {
+ $this->getProviderService()->addJavascriptAPI();
+ }
+
+
+ /**
+ * @param string $providerId
+ *
+ * @return bool
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ public function isProviderIndexed(string $providerId): bool {
+ return $this->getProviderService()->isProviderIndexed($providerId);
+ }
+
+
+ /**
+ * @param string $providerId
+ * @param string $documentId
+ * @return IIndex
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ public function getIndex(string $providerId, string $documentId): IIndex {
+ return $this->getIndexService()->getIndex($providerId, $documentId);
+ }
+
+ /**
+ * @param string $providerId
+ * @param string $documentId
+ * @param string $userId
+ * @param int $status
+ *
+ * @see IIndex for available value for $status.
+ *
+ * @return IIndex
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ public function createIndex(string $providerId, string $documentId, string $userId, int $status = 0): IIndex {
+ return $this->getIndexService()->getIndex($providerId, $documentId);
+ }
+
+
+ /**
+ * @param string $providerId
+ * @param string $documentId
+ * @param int $status
+ * @param bool $reset
+ *
+ * @see IIndex for available value for $status.
+ *
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ public function updateIndexStatus(string $providerId, string $documentId, int $status, bool $reset = false) {
+ $this->getIndexService()->updateIndexStatus($providerId, $documentId, $status, $reset);
+ }
+
+ /**
+ * @param string $providerId
+ * @param array $documentIds
+ * @param int $status
+ * @param bool $reset
+ *
+ * @see IIndex for available value for $status.
+ *
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ public function updateIndexesStatus(string $providerId, array $documentIds, int $status, bool $reset = false) {
+ $this->getIndexService()->updateIndexesStatus($providerId, $documentIds, $status, $reset);
+ }
+
+
+ /**
+ * @param IIndex[] $indexes
+ *
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ public function updateIndexes(array $indexes) {
+ $this->getIndexService()->updateIndexes($indexes);
+ }
+
+
+ /**
+ * @param array $request
+ * @param string $userId
+ *
+ * @return ISearchResult[]
+ * @throws FullTextSearchAppNotAvailableException
+ */
+ public function search(array $request, string $userId = ''): array {
+ $searchRequest = $this->getSearchService()->generateSearchRequest($request);
+
+ return $this->getSearchService()->search($userId, $searchRequest);
+ }
+
+
+}
+
diff --git a/lib/private/Mail/EMailTemplate.php b/lib/private/Mail/EMailTemplate.php
index 4c56a316042..05a884a67fa 100644
--- a/lib/private/Mail/EMailTemplate.php
+++ b/lib/private/Mail/EMailTemplate.php
@@ -185,12 +185,13 @@ EOF;
</table>
EOF;
+ // note: listBegin (like bodyBegin) is not processed through sprintf, so "%" is not escaped as "%%". (bug #12151)
protected $listBegin = <<<EOF
-<table class="row description" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%%">
+<table class="row description" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%">
<tbody>
<tr style="padding:0;text-align:left;vertical-align:top">
<th class="small-12 large-12 columns first last" style="Margin:0 auto;color:#0a0a0a;font-family:Lucida Grande,Geneva,Verdana,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0 auto;padding:0;padding-bottom:30px;padding-left:30px;padding-right:30px;text-align:left;width:550px">
- <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
+ <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%">
EOF;
protected $listItem = <<<EOF
diff --git a/lib/private/Mail/Mailer.php b/lib/private/Mail/Mailer.php
index 6f148bc0c6e..df23b669365 100644
--- a/lib/private/Mail/Mailer.php
+++ b/lib/private/Mail/Mailer.php
@@ -274,7 +274,11 @@ class Mailer implements IMailer {
$binaryPath = '/var/qmail/bin/sendmail';
break;
default:
- $binaryPath = '/usr/sbin/sendmail';
+ $sendmail = \OC_Helper::findBinaryPath('sendmail');
+ if ($sendmail === null) {
+ $sendmail = '/usr/sbin/sendmail';
+ }
+ $binaryPath = $sendmail;
break;
}
diff --git a/lib/private/Preview/Generator.php b/lib/private/Preview/Generator.php
index 86579e3480b..1f7decf2b79 100644
--- a/lib/private/Preview/Generator.php
+++ b/lib/private/Preview/Generator.php
@@ -298,19 +298,23 @@ class Generator {
if ($height !== $maxHeight && $width !== $maxWidth) {
/*
- * Scale to the nearest power of two
+ * Scale to the nearest power of four
*/
- $pow2height = 2 ** ceil(log($height) / log(2));
- $pow2width = 2 ** ceil(log($width) / log(2));
+ $pow4height = 4 ** ceil(log($height) / log(4));
+ $pow4width = 4 ** ceil(log($width) / log(4));
- $ratioH = $height / $pow2height;
- $ratioW = $width / $pow2width;
+ // Minimum size is 64
+ $pow4height = max($pow4height, 64);
+ $pow4width = max($pow4width, 64);
+
+ $ratioH = $height / $pow4height;
+ $ratioW = $width / $pow4width;
if ($ratioH < $ratioW) {
- $width = $pow2width;
+ $width = $pow4width;
$height /= $ratioW;
} else {
- $height = $pow2height;
+ $height = $pow4height;
$width /= $ratioH;
}
}
diff --git a/lib/private/Repair.php b/lib/private/Repair.php
index ad9662ca1d7..01724fd6a38 100644
--- a/lib/private/Repair.php
+++ b/lib/private/Repair.php
@@ -39,6 +39,7 @@ use OC\Repair\NC11\FixMountStorages;
use OC\Repair\NC13\AddLogRotateJob;
use OC\Repair\NC14\AddPreviewBackgroundCleanupJob;
use OC\Repair\NC14\RepairPendingCronJobs;
+use OC\Repair\NC15\SetVcardDatabaseUID;
use OC\Repair\OldGroupMembershipShares;
use OC\Repair\Owncloud\DropAccountTermsTable;
use OC\Repair\Owncloud\SaveAccountsTableData;
@@ -139,6 +140,7 @@ class Repair implements IOutput{
new AddPreviewBackgroundCleanupJob(\OC::$server->getJobList()),
new AddCleanupUpdaterBackupsJob(\OC::$server->getJobList()),
new RepairPendingCronJobs(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
+ new SetVcardDatabaseUID(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()),
];
}
diff --git a/lib/private/Repair/NC15/SetVcardDatabaseUID.php b/lib/private/Repair/NC15/SetVcardDatabaseUID.php
new file mode 100644
index 00000000000..ccf6c47cbc8
--- /dev/null
+++ b/lib/private/Repair/NC15/SetVcardDatabaseUID.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.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\Repair\NC15;
+
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+use Sabre\VObject\Reader;
+
+class SetVcardDatabaseUID implements IRepairStep {
+ const MAX_ROWS = 1000;
+
+ /** @var IDBConnection */
+ private $connection;
+
+ /** @var IConfig */
+ private $config;
+
+ private $updateQuery;
+
+ public function __construct(IDBConnection $connection, IConfig $config) {
+ $this->connection = $connection;
+ $this->config = $config;
+ }
+
+ public function getName() {
+ return 'Extract the vcard uid and store it in the db';
+ }
+
+ /**
+ * @return \Generator
+ * @suppress SqlInjectionChecker
+ */
+ private function getInvalidEntries() {
+ $builder = $this->connection->getQueryBuilder();
+
+ $builder->select('id', 'carddata')
+ ->from('cards')
+ ->where($builder->expr()->isNull('uid'))
+ ->setMaxResults(self::MAX_ROWS);
+
+ do {
+ $result = $builder->execute();
+ $rows = $result->fetchAll();
+ foreach ($rows as $row) {
+ yield $row;
+ }
+ $result->closeCursor();
+ } while (count($rows) > 0);
+ }
+
+ /**
+ * Extract UID from vcard
+ *
+ * @param string $cardData the vcard raw data
+ * @return string the uid or empty if none
+ */
+ private function getUID(string $cardData): string {
+ $vCard = Reader::read($cardData);
+ if ($vCard->UID) {
+ $uid = $vCard->UID->getValue();
+ return $uid;
+ }
+
+ return '';
+ }
+
+ /**
+ * @param int $id
+ * @param string $uid
+ */
+ private function update(int $id, string $uid) {
+ if (!$this->updateQuery) {
+ $builder = $this->connection->getQueryBuilder();
+
+ $this->updateQuery = $builder->update('cards')
+ ->set('uid', $builder->createParameter('uid'))
+ ->where($builder->expr()->eq('id', $builder->createParameter('id')));
+ }
+
+ $this->updateQuery->setParameter('id', $id);
+ $this->updateQuery->setParameter('uid', $uid);
+
+ $this->updateQuery->execute();
+ }
+
+ private function repair(): int {
+ $this->connection->beginTransaction();
+ $entries = $this->getInvalidEntries();
+ $count = 0;
+ foreach ($entries as $entry) {
+ $count++;
+ $uid = $this->getUID($entry['carddata']);
+ $this->update($entry['id'], $uid);
+ }
+ $this->connection->commit();
+
+ return $count;
+ }
+
+ private function shouldRun() {
+ $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0.0');
+
+ // was added to 15.0.0.2
+ return version_compare($versionFromBeforeUpdate, '15.0.0.2', '<=');
+ }
+
+ public function run(IOutput $output) {
+ if ($this->shouldRun()) {
+ $count = $this->repair();
+
+ $output->info('Fixed ' . $count . ' vcards');
+ }
+ }
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 32d7705919c..204345708b9 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -82,6 +82,7 @@ use OC\Files\Node\LazyRoot;
use OC\Files\Node\Root;
use OC\Files\Storage\StorageFactory;
use OC\Files\View;
+use OC\FullTextSearch\FullTextSearchManager;
use OC\Http\Client\ClientService;
use OC\IntegrityCheck\Checker;
use OC\IntegrityCheck\Helpers\AppLocator;
@@ -138,6 +139,7 @@ use OCP\Federation\ICloudIdManager;
use OCP\Authentication\LoginCredentials\IStore;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorageFactory;
+use OCP\FullTextSearch\IFullTextSearchManager;
use OCP\GlobalScale\IConfig;
use OCP\ICacheFactory;
use OCP\IDBConnection;
@@ -758,7 +760,7 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerService('TrustedDomainHelper', function ($c) {
return new TrustedDomainHelper($this->getConfig());
});
- $this->registerService('Throttler', function (Server $c) {
+ $this->registerService(Throttler::class, function (Server $c) {
return new Throttler(
$c->getDatabaseConnection(),
new TimeFactory(),
@@ -766,6 +768,7 @@ class Server extends ServerContainer implements IServerContainer {
$c->getConfig()
);
});
+ $this->registerAlias('Throttler', Throttler::class);
$this->registerService('IntegrityCodeChecker', function (Server $c) {
// IConfig and IAppManager requires a working database. This code
// might however be called when ownCloud is not yet setup.
@@ -1182,7 +1185,8 @@ class Server extends ServerContainer implements IServerContainer {
return new StorageFactory();
});
- $this->registerAlias(IDashboardManager::class, Dashboard\DashboardManager::class);
+ $this->registerAlias(IDashboardManager::class, DashboardManager::class);
+ $this->registerAlias(IFullTextSearchManager::class, FullTextSearchManager::class);
$this->connectDispatcher();
}
diff --git a/lib/private/Settings/Admin/Sharing.php b/lib/private/Settings/Admin/Sharing.php
index dfc0b11478b..ee6a64c85bf 100644
--- a/lib/private/Settings/Admin/Sharing.php
+++ b/lib/private/Settings/Admin/Sharing.php
@@ -32,6 +32,7 @@ use OCP\Constants;
use OCP\IConfig;
use OCP\IL10N;
use OCP\Settings\ISettings;
+use OCP\Share\IManager;
use OCP\Util;
class Sharing implements ISettings {
@@ -41,12 +42,16 @@ class Sharing implements ISettings {
/** @var IL10N */
private $l;
+ /** @var IManager */
+ private $shareManager;
+
/**
* @param IConfig $config
*/
- public function __construct(IConfig $config, IL10N $l) {
+ public function __construct(IConfig $config, IL10N $l, IManager $shareManager) {
$this->config = $config;
$this->l = $l;
+ $this->shareManager = $shareManager;
}
/**
@@ -65,7 +70,7 @@ class Sharing implements ISettings {
'allowResharing' => $this->config->getAppValue('core', 'shareapi_allow_resharing', 'yes'),
'allowShareDialogUserEnumeration' => $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes'),
'enforceLinkPassword' => Util::isPublicLinkPasswordRequired(),
- 'onlyShareWithGroupMembers' => Share::shareWithGroupMembersOnly(),
+ 'onlyShareWithGroupMembers' => $this->shareManager->shareWithGroupMembersOnly(),
'shareAPIEnabled' => $this->config->getAppValue('core', 'shareapi_enabled', 'yes'),
'shareDefaultExpireDateSet' => $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no'),
'shareExpireAfterNDays' => $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'),
diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php
index 36912dcafe3..76a6a1baeca 100644
--- a/lib/private/Share/Share.php
+++ b/lib/private/Share/Share.php
@@ -341,7 +341,7 @@ class Share extends Constants {
}
$uidOwner = \OC_User::getUser();
- $shareWithinGroupOnly = self::shareWithGroupMembersOnly();
+ $shareWithinGroupOnly = \OC::$server->getConfig()->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
if (is_null($itemSourceName)) {
$itemSourceName = $itemSource;
@@ -2055,15 +2055,6 @@ class Share extends Constants {
}
/**
- * check if user can only share with group members
- * @return bool
- */
- public static function shareWithGroupMembersOnly() {
- $value = \OC::$server->getConfig()->getAppValue('core', 'shareapi_only_share_with_group_members', 'no');
- return $value === 'yes';
- }
-
- /**
* @return bool
*/
public static function isDefaultExpireDateEnabled() {
@@ -2104,15 +2095,6 @@ class Share extends Constants {
}
/**
- * @param IConfig $config
- * @return bool
- */
- public static function enforcePassword(IConfig $config) {
- $enforcePassword = $config->getAppValue('core', 'shareapi_enforce_links_password', 'no');
- return $enforcePassword === 'yes';
- }
-
- /**
* @param string $password
* @throws \Exception
*/
diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php
index 9c5d78a5958..589b64fc58a 100644
--- a/lib/private/Share20/DefaultShareProvider.php
+++ b/lib/private/Share20/DefaultShareProvider.php
@@ -146,6 +146,8 @@ class DefaultShareProvider implements IShareProvider {
//Set the GID of the group we share with
$qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
+ //set label for public link
+ $qb->setValue('label', $qb->createNamedParameter($share->getLabel()));
//Set the token of the share
$qb->setValue('token', $qb->createNamedParameter($share->getToken()));
@@ -154,6 +156,8 @@ class DefaultShareProvider implements IShareProvider {
$qb->setValue('password', $qb->createNamedParameter($share->getPassword()));
}
+ $qb->setValue('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL));
+
//If an expiration date is set store it
if ($share->getExpirationDate() !== null) {
$qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime'));
@@ -225,6 +229,9 @@ class DefaultShareProvider implements IShareProvider {
*
* @param \OCP\Share\IShare $share
* @return \OCP\Share\IShare The share object
+ * @throws ShareNotFound
+ * @throws \OCP\Files\InvalidPathException
+ * @throws \OCP\Files\NotFoundException
*/
public function update(\OCP\Share\IShare $share) {
@@ -288,6 +295,7 @@ class DefaultShareProvider implements IShareProvider {
$qb->update('share')
->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
->set('password', $qb->createNamedParameter($share->getPassword()))
+ ->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
@@ -296,6 +304,8 @@ class DefaultShareProvider implements IShareProvider {
->set('token', $qb->createNamedParameter($share->getToken()))
->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE))
->set('note', $qb->createNamedParameter($share->getNote()))
+ ->set('label', $qb->createNamedParameter($share->getLabel()))
+ ->set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0), IQueryBuilder::PARAM_INT)
->execute();
}
@@ -918,7 +928,8 @@ class DefaultShareProvider implements IShareProvider {
->setPermissions((int)$data['permissions'])
->setTarget($data['file_target'])
->setNote($data['note'])
- ->setMailSend((bool)$data['mail_send']);
+ ->setMailSend((bool)$data['mail_send'])
+ ->setLabel($data['label']);
$shareTime = new \DateTime();
$shareTime->setTimestamp((int)$data['stime']);
@@ -930,6 +941,7 @@ class DefaultShareProvider implements IShareProvider {
$share->setSharedWith($data['share_with']);
} else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
$share->setPassword($data['password']);
+ $share->setSendPasswordByTalk((bool)$data['password_by_talk']);
$share->setToken($data['token']);
}
@@ -953,6 +965,7 @@ class DefaultShareProvider implements IShareProvider {
}
$share->setProviderId($this->identifier());
+ $share->setHideDownload((int)$data['hide_download'] === 1);
return $share;
}
diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php
index 71c0453d9e5..f9b548c1adf 100644
--- a/lib/private/Share20/Share.php
+++ b/lib/private/Share20/Share.php
@@ -30,6 +30,7 @@ use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\IUserManager;
use OCP\Share\Exceptions\IllegalIDChangeException;
+use OCP\Share\IShare;
class Share implements \OCP\Share\IShare {
@@ -75,6 +76,8 @@ class Share implements \OCP\Share\IShare {
private $shareTime;
/** @var bool */
private $mailSend;
+ /** @var string */
+ private $label = '';
/** @var IRootFolder */
private $rootFolder;
@@ -85,6 +88,9 @@ class Share implements \OCP\Share\IShare {
/** @var ICacheEntry|null */
private $nodeCacheEntry;
+ /** @var bool */
+ private $hideDownload = false;
+
public function __construct(IRootFolder $rootFolder, IUserManager $userManager) {
$this->rootFolder = $rootFolder;
$this->userManager = $userManager;
@@ -333,6 +339,21 @@ class Share implements \OCP\Share\IShare {
/**
* @inheritdoc
*/
+ public function setLabel($label) {
+ $this->label = $label;
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getLabel() {
+ return $this->label;
+ }
+
+ /**
+ * @inheritdoc
+ */
public function setExpirationDate($expireDate) {
//TODO checks
@@ -514,4 +535,13 @@ class Share implements \OCP\Share\IShare {
public function getNodeCacheEntry() {
return $this->nodeCacheEntry;
}
+
+ public function setHideDownload(bool $hide): IShare {
+ $this->hideDownload = $hide;
+ return $this;
+ }
+
+ public function getHideDownload(): bool {
+ return $this->hideDownload;
+ }
}
diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php
index b691a8a64cb..ad9ff0b6757 100644
--- a/lib/private/Template/JSConfigHelper.php
+++ b/lib/private/Template/JSConfigHelper.php
@@ -70,6 +70,9 @@ class JSConfigHelper {
/** @var CapabilitiesManager */
private $capabilitiesManager;
+ /** @var array user back-ends excluded from password verification */
+ private $excludedUserBackEnds = ['user_saml' => true, 'user_globalsiteselector' => true];
+
/**
* @param IL10N $l
* @param Defaults $defaults
@@ -158,7 +161,7 @@ class JSConfigHelper {
$array = [
"oc_debug" => $this->config->getSystemValue('debug', false) ? 'true' : 'false',
"oc_isadmin" => $this->groupManager->isAdmin($uid) ? 'true' : 'false',
- "backendAllowsPasswordConfirmation" => $userBackend === 'user_saml'? 'false' : 'true',
+ "backendAllowsPasswordConfirmation" => !isset($this->excludedUserBackEnds[$userBackend]) ? 'true' : 'false',
"oc_dataURL" => is_string($dataLocation) ? "\"".$dataLocation."\"" : 'false',
"oc_webroot" => "\"".\OC::$WEBROOT."\"",
"oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution
diff --git a/lib/private/Updater.php b/lib/private/Updater.php
index 8a2b1cd188c..4b4723be94f 100644
--- a/lib/private/Updater.php
+++ b/lib/private/Updater.php
@@ -38,7 +38,6 @@ use OC\DB\MigrationService;
use OC\Hooks\BasicEmitter;
use OC\IntegrityCheck\Checker;
use OC_App;
-use OCP\BackgroundJob\IJobList;
use OCP\IConfig;
use OCP\ILogger;
use OCP\Util;
@@ -67,9 +66,6 @@ class Updater extends BasicEmitter {
/** @var Installer */
private $installer;
- /** @var IJobList */
- private $jobList;
-
private $logLevelNames = [
0 => 'Debug',
1 => 'Info',
@@ -78,16 +74,20 @@ class Updater extends BasicEmitter {
4 => 'Fatal',
];
+ /**
+ * @param IConfig $config
+ * @param Checker $checker
+ * @param ILogger $log
+ * @param Installer $installer
+ */
public function __construct(IConfig $config,
Checker $checker,
- ILogger $log,
- Installer $installer,
- IJobList $jobList) {
+ ILogger $log = null,
+ Installer $installer) {
$this->log = $log;
$this->config = $config;
$this->checker = $checker;
$this->installer = $installer;
- $this->jobList = $jobList;
}
/**
@@ -114,11 +114,6 @@ class Updater extends BasicEmitter {
$installedVersion = $this->config->getSystemValue('version', '0.0.0');
$currentVersion = implode('.', \OCP\Util::getVersion());
- // see https://github.com/nextcloud/server/issues/9992 for potential problem
- if (version_compare($installedVersion, '14.0.0.9', '>=')) {
- $this->waitForCronToFinish();
- }
-
$this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, array('app' => 'core'));
$success = true;
@@ -617,12 +612,6 @@ class Updater extends BasicEmitter {
});
}
- private function waitForCronToFinish() {
- while ($this->jobList->isAnyJobRunning()) {
- $this->emit('\OC\Updater', 'waitForCronToFinish');
- sleep(5);
- }
- }
}
diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php
index a9c638dca93..674f38e2401 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -38,6 +38,7 @@
namespace OC\User;
use OC;
+use OC\Authentication\Exceptions\ExpiredTokenException;
use OC\Authentication\Exceptions\InvalidTokenException;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
@@ -401,7 +402,13 @@ class Session implements IUserSession, Emitter {
$this->manager->emit('\OC\User', 'preLogin', array($user, $password));
}
- $isTokenPassword = $this->isTokenPassword($password);
+ try {
+ $isTokenPassword = $this->isTokenPassword($password);
+ } catch (ExpiredTokenException $e) {
+ // Just return on an expired token no need to check further or record a failed login
+ return false;
+ }
+
if (!$isTokenPassword && $this->isTokenAuthEnforced()) {
throw new PasswordLoginForbiddenException();
}
@@ -474,11 +481,14 @@ class Session implements IUserSession, Emitter {
*
* @param string $password
* @return boolean
+ * @throws ExpiredTokenException
*/
public function isTokenPassword($password) {
try {
$this->tokenProvider->getToken($password);
return true;
+ } catch (ExpiredTokenException $e) {
+ throw $e;
} catch (InvalidTokenException $ex) {
return false;
}