refactor(share): extract shared expiration date validation

Both `validateExpirationDateInternal()` and `validateExpirationDateLink()` implemented the
same algorithm for:

- skipping validation when no expiration date is allowed
- rejecting past expiration dates
- applying a default expiration date for new shares
- enforcing the maximum allowed expiration date
- running the `verifyExpirationDate` hook

This change keeps the existing behavior but centralizes the logic so future fixes only need to be made in one place.

This commit also fixes a tiny unrelated quoting bug in `dispatchEvent()` that caused garbled error-log output in `dispatchEvent()`.

Signed-off-by: Josh <josh.t.richards@gmail.com>
This commit is contained in:
Josh 2026-04-20 09:11:09 -04:00 committed by GitHub
parent 63a1a083f8
commit af076d5c05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -276,33 +276,28 @@ class Manager implements IManager {
}
/**
* Validate if the expiration date fits the system settings
* Validate if the expiration date fits the provided share policy settings.
*
* @param IShare $share The share to validate the expiration date of
* @param bool $isEnforced Whether an expiration date is mandatory and capped
* @param bool $defaultExpireDate Whether a default expiration date should be applied
* @param int $defaultExpireDays Maximum/default number of expiration days
* @param string $configProp Config key used to retrieve the admin-configured default days
* @return IShare The modified share object
* @throws GenericShareException
* @throws \InvalidArgumentException
* @throws \Exception
*/
protected function validateExpirationDateInternal(IShare $share): IShare {
$isRemote = $share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP;
protected function validateExpirationDate(
IShare $share,
bool $isEnforced,
bool $defaultExpireDate,
int $defaultExpireDays,
string $configProp,
): IShare {
$expirationDate = $share->getExpirationDate();
if ($isRemote) {
$defaultExpireDate = $this->shareApiRemoteDefaultExpireDate();
$defaultExpireDays = $this->shareApiRemoteDefaultExpireDays();
$configProp = 'remote_defaultExpDays';
$isEnforced = $this->shareApiRemoteDefaultExpireDateEnforced();
} else {
$defaultExpireDate = $this->shareApiInternalDefaultExpireDate();
$defaultExpireDays = $this->shareApiInternalDefaultExpireDays();
$configProp = 'internal_defaultExpDays';
$isEnforced = $this->shareApiInternalDefaultExpireDateEnforced();
}
// If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
// Then skip expiration date validation as null is accepted
// If no-expiration is allowed and expiration is not enforced, null is accepted.
if (!$share->getNoExpirationDate() || $isEnforced) {
if ($expirationDate !== null) {
$expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
@ -315,7 +310,7 @@ class Manager implements IManager {
}
}
// If expiredate is empty set a default one if there is a default
// If expire date is empty, set a default one for new shares if configured.
$fullId = null;
try {
$fullId = $share->getFullId();
@ -326,6 +321,7 @@ class Manager implements IManager {
if ($fullId === null && $expirationDate === null && $defaultExpireDate) {
$expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
$expirationDate->setTime(23, 59, 59);
$days = (int)$this->config->getAppValue('core', $configProp, (string)$defaultExpireDays);
if ($days > $defaultExpireDays) {
$days = $defaultExpireDays;
@ -333,7 +329,7 @@ class Manager implements IManager {
$expirationDate->add(new \DateInterval('P' . $days . 'D'));
}
// If we enforce the expiration date check that is does not exceed
// If expiration is enforced, it must be present and must not exceed the maximum.
if ($isEnforced) {
if (empty($expirationDate)) {
throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
@ -343,7 +339,14 @@ class Manager implements IManager {
$date->setTime(23, 59, 59);
$date->add(new \DateInterval('P' . $defaultExpireDays . 'D'));
if ($date < $expirationDate) {
throw new GenericShareException($this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $defaultExpireDays), code: 404);
throw new GenericShareException(
$this->l->n(
'Cannot set expiration date more than %n day in the future',
'Cannot set expiration date more than %n days in the future',
$defaultExpireDays
),
code: 404,
);
}
}
}
@ -366,6 +369,39 @@ class Manager implements IManager {
return $share;
}
/**
* Validate if the expiration date fits the system settings
*
* @param IShare $share The share to validate the expiration date of
* @return IShare The modified share object
* @throws GenericShareException
* @throws \InvalidArgumentException
* @throws \Exception
*/
protected function validateExpirationDateInternal(IShare $share): IShare {
$isRemote = $share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP;
if ($isRemote) {
$defaultExpireDate = $this->shareApiRemoteDefaultExpireDate();
$defaultExpireDays = $this->shareApiRemoteDefaultExpireDays();
$configProp = 'remote_defaultExpDays';
$isEnforced = $this->shareApiRemoteDefaultExpireDateEnforced();
} else {
$defaultExpireDate = $this->shareApiInternalDefaultExpireDate();
$defaultExpireDays = $this->shareApiInternalDefaultExpireDays();
$configProp = 'internal_defaultExpDays';
$isEnforced = $this->shareApiInternalDefaultExpireDateEnforced();
}
return $this->validateExpirationDate(
$share,
$isEnforced,
$defaultExpireDate,
$defaultExpireDays,
$configProp,
);
}
/**
* Validate if the expiration date fits the system settings
*
@ -376,77 +412,13 @@ class Manager implements IManager {
* @throws \Exception
*/
protected function validateExpirationDateLink(IShare $share): IShare {
$expirationDate = $share->getExpirationDate();
$isEnforced = $this->shareApiLinkDefaultExpireDateEnforced();
// If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
// Then skip expiration date validation as null is accepted
if (!($share->getNoExpirationDate() && !$isEnforced)) {
if ($expirationDate !== null) {
$expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
$expirationDate->setTime(23, 59, 59);
$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
$date->setTime(0, 0, 0);
if ($date >= $expirationDate) {
throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
}
}
// If expiredate is empty set a default one if there is a default
$fullId = null;
try {
$fullId = $share->getFullId();
} catch (\UnexpectedValueException $e) {
// This is a new share
}
if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
$expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
$expirationDate->setTime(23, 59, 59);
$days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays());
if ($days > $this->shareApiLinkDefaultExpireDays()) {
$days = $this->shareApiLinkDefaultExpireDays();
}
$expirationDate->add(new \DateInterval('P' . $days . 'D'));
}
// If we enforce the expiration date check that is does not exceed
if ($isEnforced) {
if (empty($expirationDate)) {
throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
}
$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
$date->setTime(23, 59, 59);
$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
if ($date < $expirationDate) {
throw new GenericShareException(
$this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $this->shareApiLinkDefaultExpireDays()),
code: 404,
);
}
}
}
$accepted = true;
$message = '';
Util::emitHook('\OC\Share', 'verifyExpirationDate', [
'expirationDate' => &$expirationDate,
'accepted' => &$accepted,
'message' => &$message,
'passwordSet' => $share->getPassword() !== null,
]);
if (!$accepted) {
throw new \Exception($message);
}
$share->setExpirationDate($expirationDate);
return $share;
return $this->validateExpirationDate(
$share,
$this->shareApiLinkDefaultExpireDateEnforced(),
$this->shareApiLinkDefaultExpireDate(),
$this->shareApiLinkDefaultExpireDays(),
'link_defaultExpDays',
);
}
/**
@ -1981,7 +1953,7 @@ class Manager implements IManager {
try {
$this->dispatcher->dispatchTyped($event);
} catch (\Exception $e) {
$this->logger->error("Error while sending ' . $name . ' event", ['exception' => $e]);
$this->logger->error("Error while sending '$name' event", ['exception' => $e]);
}
}