Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
43d20f8
fix(TextProcessingApiController): Set better attribute on routes
marcelklehr Sep 17, 2025
b165b3d
build(deps-dev): bump webpack from 5.101.3 to 5.102.1
dependabot[bot] Oct 11, 2025
2b9a2a9
chore(assets): Recompile assets
nextcloud-command Oct 16, 2025
8ffdaac
build(hub): 30.0.17 RC1
blizzz Oct 14, 2025
48b4c16
chore: bump @nextcloud/browserslist-config from 3.0.1 to 3.1.0
skjnldsv Oct 17, 2025
e1054a9
chore: compile assets
skjnldsv Oct 17, 2025
34b363f
fix(dav): Restrict properties allowed object classes
come-nc Oct 14, 2025
bd3565c
fix(dav): Allow arrays (of scalars) in property values
come-nc Oct 14, 2025
9da0db4
fix(dav): Allow array of array of scalars, and fix error message
come-nc Oct 16, 2025
2f176e6
fix: translation on /unsupported screen
luka-nextcloud Oct 13, 2025
311f3f7
build(hub): 30.0.17 RC2
Oct 21, 2025
e19a36c
build(hub): 30.0.17
Oct 23, 2025
28b6c89
fix(FilesPicker): Prevent selection of nodes without create permission
artonge Oct 17, 2025
393034f
chore(deps): Bump @nextcloud/dialogs to v6.4.1
artonge Oct 22, 2025
b990d58
test(files): Adapte to new @nextcloud/files version
artonge Oct 22, 2025
8161fd5
chore(assets): Recompile assets
nextcloud-command Oct 22, 2025
50f4b09
feat(config): add `maximum.supported.desktop.version`
skjnldsv Nov 27, 2024
fcf9289
fix(files_trashbin): Fix size propagation when moving file to trash
provokateurin Sep 8, 2025
ccb3494
fix(TaskProcessing): Cache task types by user language
marcelklehr Sep 17, 2025
da59dee
fix(comments): add inline container size query to comment body
edward-ly Sep 19, 2025
6b7b8da
chore(assets): Recompile assets
nextcloud-command Nov 2, 2025
6f9a833
fix: validate filename when creating file from template
kesselb Sep 25, 2025
74446a9
fix(security): Update CA certificate bundle
nextcloud-command Nov 5, 2025
07c4a73
fix(files_versions): Do not assume source exist when migrating versio…
artonge Oct 31, 2025
d6d0570
feat(EphemeralSessions): Introduce lax period
artonge Nov 5, 2025
1bae60d
fix(security): Update code signing revocation list
nextcloud-command Nov 11, 2025
da1acee
fix(ai-apis): reject text inputs that are longer than 64K chars
julien-nc Nov 5, 2025
4ac19be
fix: Force direct login after password reset
juliusknorr Nov 5, 2025
54d136d
chore(assets): Recompile assets
nextcloud-command Nov 5, 2025
fb137c0
fix(lostpassword): Delete lost password token on password change
CarlSchwan Sep 1, 2025
7dec09c
fix(files): Stop overwriting scan_permissions
provokateurin Sep 28, 2025
65f75c4
fix: cast fileid to string in getMountsForFileId
icewind1991 Aug 29, 2025
2a1eb86
build(deps): Bump symfony/http-foundation to 6.4.29
nickvergessen Nov 20, 2025
6c26cb9
feat: emit an event when an S3 bucket is created
leftybournes Nov 20, 2025
35e7d01
feat: allow configuring multiple objectstore configurations
icewind1991 Dec 1, 2022
3728a12
feat: add command to get user objectstore config mappings
icewind1991 Nov 25, 2025
20a73f7
feat: allow object store configuration aliases for easier migrations
icewind1991 Jun 5, 2025
fae8e6c
feat: multi object store rename command
icewind1991 Jun 5, 2025
12e74d7
fix: make bucket mapper work with new multi-object-store config
icewind1991 Aug 28, 2025
15624ac
fix: ensure all object store configuration have distict bucket names
icewind1991 Sep 5, 2025
15152b2
feat(CertificateManager): Add option to specify the default certifica…
provokateurin May 11, 2025
265a9ac
feat(ObjectStore): Allow overriding arguments per bucket
provokateurin Dec 1, 2025
b8d7aad
fix(security): Update CA certificate bundle
nextcloud-command Dec 3, 2025
9806900
chore(files): Remove regular stats request
artonge Nov 26, 2025
96c4dbb
chore(assets): Recompile assets
nextcloud-command Dec 2, 2025
27c194a
chore(deps): Update flake to 25.11 and haze to 2.1.3
provokateurin Dec 1, 2025
b90776a
fix(flake): Allow using EOL PHP versions
provokateurin Dec 6, 2025
54ee51c
refactor(workflowengine): Check if class is correct
CarlSchwan Nov 24, 2025
335118a
fix(comments): Check comment object
nickvergessen Dec 11, 2025
62cf997
fix(dav): handle HTML in CalDAV invitations
ChristophWurst Dec 5, 2025
afaadbe
fix(security): Update code signing revocation list
nextcloud-command Dec 12, 2025
75c8040
fix(NewUserDialog): allow to deselect a group from the list
Antreesy Oct 23, 2025
bff9683
chore(assets): Recompile assets
nextcloud-command Oct 24, 2025
073ec09
fix: Fix orphan shares blocking moving other shares
come-nc Dec 1, 2025
f56f33f
fix: Log missing nodes of shares at debug level
come-nc Dec 2, 2025
cec1a80
fix(security): Update code signing revocation list
nextcloud-command Dec 27, 2025
36b32d2
fix(security): Update code signing revocation list
nextcloud-command Jan 6, 2026
27a2285
feat(Config.php): change `array_merge` to `array_replace_recursive` w…
szaimen Jan 6, 2026
69ba8ac
feat: don't gate perBucket object store configuration behind multibucket
icewind1991 Dec 18, 2025
488c754
feat: also send x-user-id for dav responses
icewind1991 Dec 19, 2025
c3bd878
feat: restrict calendar invitation participants
SebastianKrupinski Dec 23, 2025
ca7f7fd
fix(l10n): Fix language selection
nickvergessen Dec 17, 2025
4b1296c
feat(flake): Add reuse cli
provokateurin Jan 26, 2026
4a9f7a9
chore(deps): Update flake.lock
provokateurin Jan 26, 2026
92c3e97
fix(security): Update code signing revocation list
nextcloud-command Jan 31, 2026
e3ba426
ci: Pin actions
AndyScherzinger Feb 1, 2026
06cf356
chore: Improve SVG handling in link previews
benjaminfrueh Feb 1, 2026
26195d5
fix: add X-User-Id header to logout response before clearing the user…
salmart-dev Feb 3, 2026
053dbda
fix(TaskProcessing): Refactor TextToImage fallback
marcelklehr Jan 12, 2026
faf1475
Fix: Run cs:fix
marcelklehr Feb 3, 2026
cbf8dc2
fix: Use ProcessingException
marcelklehr Feb 5, 2026
d6b655e
fix(TaskProcessing): Inject missing dependency
marcelklehr Feb 19, 2026
62ed143
fix(TaskProcessing): Remove unused code
marcelklehr Feb 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/dependabot-approve-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}

# Nextcloud bot approve and merge request
- uses: ahmadnassri/action-dependabot-auto-merge@45fc124d949b19b6b8bf6645b6c9d55f4f9ac61a # v2
- uses: ahmadnassri/action-dependabot-auto-merge@45fc124d949b19b6b8bf6645b6c9d55f4f9ac61a # v2.6.6
with:
target: minor
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ jobs:
before.json
after.json

- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
if: failure() && steps.compare.outcome == 'failure'
with:
github-token: ${{secrets.GITHUB_TOKEN}}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
issues: write

steps:
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with:
repo-token: ${{ secrets.COMMAND_BOT_PAT }}
stale-issue-message: >
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/static-code-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:

- name: Upload Analysis results to GitHub
if: always()
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@439137e1b50c27ba9e2f9befc93e43091b449c34 # v3.32.0
with:
sarif_file: results.sarif

Expand Down Expand Up @@ -85,7 +85,7 @@ jobs:

- name: Upload Security Analysis results to GitHub
if: always()
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with:
sarif_file: results.sarif

Expand Down
2 changes: 1 addition & 1 deletion 3rdparty
Submodule 3rdparty updated 35 files
+1 −1 composer.json
+14 −10 composer.lock
+111 −111 composer/autoload_static.php
+11 −7 composer/installed.json
+3 −3 composer/installed.php
+2 −3 composer/platform_check.php
+4 −4 symfony/http-foundation/BinaryFileResponse.php
+2 −2 symfony/http-foundation/Cookie.php
+1 −1 symfony/http-foundation/File/Exception/AccessDeniedException.php
+1 −1 symfony/http-foundation/File/Exception/FileNotFoundException.php
+1 −1 symfony/http-foundation/File/Exception/UnexpectedTypeException.php
+4 −4 symfony/http-foundation/File/File.php
+2 −2 symfony/http-foundation/File/UploadedFile.php
+2 −2 symfony/http-foundation/HeaderBag.php
+1 −1 symfony/http-foundation/HeaderUtils.php
+5 −5 symfony/http-foundation/InputBag.php
+2 −2 symfony/http-foundation/IpUtils.php
+2 −2 symfony/http-foundation/JsonResponse.php
+5 −5 symfony/http-foundation/ParameterBag.php
+2 −2 symfony/http-foundation/RedirectResponse.php
+53 −21 symfony/http-foundation/Request.php
+4 −4 symfony/http-foundation/Response.php
+8 −8 symfony/http-foundation/ResponseHeaderBag.php
+2 −2 symfony/http-foundation/Session/SessionUtils.php
+2 −2 symfony/http-foundation/Session/Storage/Handler/AbstractSessionHandler.php
+1 −1 symfony/http-foundation/Session/Storage/Handler/IdentityMarshaller.php
+1 −1 symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
+2 −2 symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php
+7 −7 symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
+1 −1 symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
+2 −2 symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php
+1 −1 symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php
+1 −1 symfony/http-foundation/Session/Storage/MockArraySessionStorage.php
+1 −1 symfony/http-foundation/Session/Storage/MockFileSessionStorage.php
+5 −5 symfony/http-foundation/Session/Storage/NativeSessionStorage.php
1 change: 1 addition & 0 deletions apps/comments/src/components/Comment.vue
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ $comment-padding: 10px;
display: flex;
flex-grow: 1;
flex-direction: column;
container-type: inline-size;
}

&__header {
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@
'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => $baseDir . '/../lib/Connector/Sabre/SharesPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\TagList' => $baseDir . '/../lib/Connector/Sabre/TagList.php',
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => $baseDir . '/../lib/Connector/Sabre/TagsPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\UserIdHeaderPlugin' => $baseDir . '/../lib/Connector/Sabre/UserIdHeaderPlugin.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php',
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/SharesPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\TagList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagList.php',
'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagsPlugin.php',
'OCA\\DAV\\Connector\\Sabre\\UserIdHeaderPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/UserIdHeaderPlugin.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,31 @@ private function addBulletList(IEMailTemplate $template,
IL10N $l10n,
string $calendarDisplayName,
VEvent $vevent):void {
$template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'),
$this->getAbsoluteImagePath('actions/info.png'));
$template->addBodyListItem(
htmlspecialchars($calendarDisplayName),
$l10n->t('Calendar:'),
$this->getAbsoluteImagePath('actions/info.png'),
htmlspecialchars($calendarDisplayName),
);

$template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'),
$this->getAbsoluteImagePath('places/calendar.png'));

if (isset($vevent->LOCATION)) {
$template->addBodyListItem((string) $vevent->LOCATION, $l10n->t('Where:'),
$this->getAbsoluteImagePath('actions/address.png'));
$template->addBodyListItem(
htmlspecialchars((string) $vevent->LOCATION),
$l10n->t('Where:'),
$this->getAbsoluteImagePath('actions/address.png'),
htmlspecialchars((string) $vevent->LOCATION),
);
}
if (isset($vevent->DESCRIPTION)) {
$template->addBodyListItem((string) $vevent->DESCRIPTION, $l10n->t('Description:'),
$this->getAbsoluteImagePath('actions/more.png'));
$template->addBodyListItem(
htmlspecialchars((string) $vevent->DESCRIPTION),
$l10n->t('Description:'),
$this->getAbsoluteImagePath('actions/more.png'),
htmlspecialchars((string) $vevent->DESCRIPTION),
);
}
}

Expand Down
12 changes: 12 additions & 0 deletions apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ public function schedule(Message $iTipMessage) {
$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
return;
}

// Check if external attendees are disabled
$externalAttendeesDisabled = $this->config->getValueBool('dav', 'caldav_external_attendees_disabled', false);
if ($externalAttendeesDisabled && !$this->imipService->isSystemUser($recipient)) {
$this->logger->debug('Invitation not sent to external attendee (external attendees disabled)', [
'uid' => $iTipMessage->uid,
'attendee' => $recipient,
]);
$iTipMessage->scheduleStatus = '5.0; External attendees are disabled';
return;
}

$recipientName = $iTipMessage->recipientName ? (string) $iTipMessage->recipientName : null;

$newEvents = $iTipMessage->message;
Expand Down
49 changes: 33 additions & 16 deletions apps/dav/lib/CalDAV/Schedule/IMipService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IUserManager;
use OCP\L10N\IFactory as L10NFactory;
use OCP\Mail\IEMailTemplate;
use OCP\Security\ISecureRandom;
Expand All @@ -24,6 +25,7 @@
use Sabre\VObject\Parameter;
use Sabre\VObject\Property;
use Sabre\VObject\Recur\EventIterator;
use function htmlspecialchars;

class IMipService {

Expand All @@ -34,7 +36,8 @@ class IMipService {
private L10NFactory $l10nFactory;
private IL10N $l10n;
private ITimeFactory $timeFactory;

private IUserManager $userManager;

/** @var string[] */
private const STRING_DIFF = [
'meeting_title' => 'SUMMARY',
Expand All @@ -48,13 +51,16 @@ public function __construct(URLGenerator $urlGenerator,
IDBConnection $db,
ISecureRandom $random,
L10NFactory $l10nFactory,
ITimeFactory $timeFactory) {
ITimeFactory $timeFactory,
IUserManager $userManager,
) {
$this->urlGenerator = $urlGenerator;
$this->config = $config;
$this->db = $db;
$this->random = $random;
$this->l10nFactory = $l10nFactory;
$this->timeFactory = $timeFactory;
$this->userManager = $userManager;
$language = $this->l10nFactory->findGenericLanguage();
$locale = $this->l10nFactory->findLocale($language);
$this->l10n = $this->l10nFactory->get('dav', $language, $locale);
Expand Down Expand Up @@ -88,10 +94,11 @@ private function generateDiffString(VEvent $vevent, VEvent $oldVEvent, string $p
if (!isset($vevent->$property)) {
return $default;
}
$newstring = $vevent->$property->getValue();
$value = $vevent->$property->getValue();
$newstring = $value === null ? null : htmlspecialchars($value);
if (isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring) {
$oldstring = $oldVEvent->$property->getValue();
return sprintf($strikethrough, $oldstring, $newstring);
return sprintf($strikethrough, htmlspecialchars($oldstring), $newstring);
}
return $newstring;
}
Expand All @@ -103,9 +110,9 @@ private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent,
if (!isset($vevent->$property)) {
return $default;
}
/** @var string|null $newString */
$newString = $vevent->$property->getValue();
$oldString = isset($oldVEvent->$property) ? $oldVEvent->$property->getValue() : null;
$value = $vevent->$property->getValue();
$newString = $value === null ? null : htmlspecialchars($value);
$oldString = isset($oldVEvent->$property) ? htmlspecialchars($oldVEvent->$property->getValue()) : null;
if ($oldString !== $newString) {
return sprintf(
"<span style='text-decoration: line-through'>%s</span><br />%s",
Expand Down Expand Up @@ -805,10 +812,10 @@ public function buildCancelledBodyData(VEvent $vEvent): array {
$strikethrough = "<span style='text-decoration: line-through'>%s</span>";

$newMeetingWhen = $this->generateWhenString($eventReaderCurrent);
$newSummary = isset($vEvent->SUMMARY) && (string) $vEvent->SUMMARY !== '' ? (string) $vEvent->SUMMARY : $this->l10n->t('Untitled event');
$newDescription = isset($vEvent->DESCRIPTION) && (string) $vEvent->DESCRIPTION !== '' ? (string) $vEvent->DESCRIPTION : $defaultVal;
$newSummary = htmlspecialchars(isset($vEvent->SUMMARY) && (string) $vEvent->SUMMARY !== '' ? (string) $vEvent->SUMMARY : $this->l10n->t('Untitled event'));
$newDescription = htmlspecialchars(isset($vEvent->DESCRIPTION) && (string) $vEvent->DESCRIPTION !== '' ? (string) $vEvent->DESCRIPTION : $defaultVal);
$newUrl = isset($vEvent->URL) && (string) $vEvent->URL !== '' ? sprintf('<a href="%1$s">%1$s</a>', $vEvent->URL) : $defaultVal;
$newLocation = isset($vEvent->LOCATION) && (string) $vEvent->LOCATION !== '' ? (string) $vEvent->LOCATION : $defaultVal;
$newLocation = htmlspecialchars(isset($vEvent->LOCATION) && (string) $vEvent->LOCATION !== '' ? (string) $vEvent->LOCATION : $defaultVal);
$newLocationHtml = $this->linkify($newLocation) ?? $newLocation;

$data = [];
Expand Down Expand Up @@ -1058,30 +1065,30 @@ public function addAttendees(IEMailTemplate $template, VEvent $vevent) {
*/
public function addBulletList(IEMailTemplate $template, VEvent $vevent, $data) {
$template->addBodyListItem(
$data['meeting_title_html'] ?? $data['meeting_title'], $this->l10n->t('Title:'),
$data['meeting_title_html'] ?? htmlspecialchars($data['meeting_title']), $this->l10n->t('Title:'),
$this->getAbsoluteImagePath('caldav/title.png'), $data['meeting_title'], '', IMipPlugin::IMIP_INDENT);
if ($data['meeting_when'] !== '') {
$template->addBodyListItem($data['meeting_when_html'] ?? $data['meeting_when'], $this->l10n->t('When:'),
$template->addBodyListItem($data['meeting_when_html'] ?? htmlspecialchars($data['meeting_when']), $this->l10n->t('When:'),
$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_when'], '', IMipPlugin::IMIP_INDENT);
}
if ($data['meeting_location'] !== '') {
$template->addBodyListItem($data['meeting_location_html'] ?? $data['meeting_location'], $this->l10n->t('Location:'),
$template->addBodyListItem($data['meeting_location_html'] ?? htmlspecialchars($data['meeting_location']), $this->l10n->t('Location:'),
$this->getAbsoluteImagePath('caldav/location.png'), $data['meeting_location'], '', IMipPlugin::IMIP_INDENT);
}
if ($data['meeting_url'] !== '') {
$template->addBodyListItem($data['meeting_url_html'] ?? $data['meeting_url'], $this->l10n->t('Link:'),
$template->addBodyListItem($data['meeting_url_html'] ?? htmlspecialchars($data['meeting_url']), $this->l10n->t('Link:'),
$this->getAbsoluteImagePath('caldav/link.png'), $data['meeting_url'], '', IMipPlugin::IMIP_INDENT);
}
if (isset($data['meeting_occurring'])) {
$template->addBodyListItem($data['meeting_occurring_html'] ?? $data['meeting_occurring'], $this->l10n->t('Occurring:'),
$template->addBodyListItem($data['meeting_occurring_html'] ?? htmlspecialchars($data['meeting_occurring']), $this->l10n->t('Occurring:'),
$this->getAbsoluteImagePath('caldav/time.png'), $data['meeting_occurring'], '', IMipPlugin::IMIP_INDENT);
}

$this->addAttendees($template, $vevent);

/* Put description last, like an email body, since it can be arbitrarily long */
if ($data['meeting_description']) {
$template->addBodyListItem($data['meeting_description_html'] ?? $data['meeting_description'], $this->l10n->t('Description:'),
$template->addBodyListItem($data['meeting_description_html'] ?? htmlspecialchars($data['meeting_description']), $this->l10n->t('Description:'),
$this->getAbsoluteImagePath('caldav/description.png'), $data['meeting_description'], '', IMipPlugin::IMIP_INDENT);
}
}
Expand Down Expand Up @@ -1180,6 +1187,16 @@ public function getReplyingAttendee(Message $iTipMessage): ?Property {
return null;
}

/**
* Check if an email address belongs to a system user
*
* @param string $email
* @return bool True if the email belongs to a system user, false otherwise
*/
public function isSystemUser(string $email): bool {
return !empty($this->userManager->getByEmail($email));
}

public function isRoomOrResource(Property $attendee): bool {
$cuType = $attendee->offsetGet('CUTYPE');
if (!$cuType instanceof Parameter) {
Expand Down
9 changes: 7 additions & 2 deletions apps/dav/lib/Comments/EntityCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ public function getId() {
public function getChild($name) {
try {
$comment = $this->commentsManager->get($name);
if ($comment->getObjectType() !== $this->name
|| $comment->getObjectId() !== $this->id) {
throw new NotFound();
}
return new CommentNode(
$this->commentsManager,
$comment,
Expand Down Expand Up @@ -137,8 +141,9 @@ public function findChildren($limit = 0, $offset = 0, ?\DateTime $datetime = nul
*/
public function childExists($name) {
try {
$this->commentsManager->get($name);
return true;
$comment = $this->commentsManager->get($name);
return $comment->getObjectType() === $this->name
&& $comment->getObjectId() === $this->id;
} catch (NotFoundException $e) {
return false;
}
Expand Down
19 changes: 15 additions & 4 deletions apps/dav/lib/Connector/Sabre/BlockLegacyClientPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,22 @@ public function beforeHandler(RequestInterface $request) {
return;
}

$minimumSupportedDesktopVersion = $this->config->getSystemValue('minimum.supported.desktop.version', '2.3.0');
$minimumSupportedDesktopVersion = $this->config->getSystemValueString('minimum.supported.desktop.version', '2.3.0');
$maximumSupportedDesktopVersion = $this->config->getSystemValueString('maximum.supported.desktop.version', '99.99.99');

// Check if the client is a desktop client
preg_match(IRequest::USER_AGENT_CLIENT_DESKTOP, $userAgent, $versionMatches);
if (isset($versionMatches[1]) &&
version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) {
throw new \Sabre\DAV\Exception\Forbidden('Unsupported client version.');

// If the client is a desktop client and the version is too old, block it
if (isset($versionMatches[1]) && version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) {
$minimumSupportedDesktopVersion = htmlspecialchars($minimumSupportedDesktopVersion);
throw new \Sabre\DAV\Exception\Forbidden("This version of the client is unsupported. Upgrade to version $minimumSupportedDesktopVersion or later.");
}

// If the client is a desktop client and the version is too new, block it
if (isset($versionMatches[1]) && version_compare($versionMatches[1], $maximumSupportedDesktopVersion) === 1) {
$maximumSupportedDesktopVersion = htmlspecialchars($maximumSupportedDesktopVersion);
throw new \Sabre\DAV\Exception\Forbidden("This version of the client is unsupported. Downgrade to version $maximumSupportedDesktopVersion or earlier.");
}
}
}
3 changes: 2 additions & 1 deletion apps/dav/lib/Connector/Sabre/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ public function createServer(string $baseUri,
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin());

$server->addPlugin(new RequestIdHeaderPlugin(\OC::$server->get(IRequest::class)));
$server->addPlugin(new RequestIdHeaderPlugin($this->request));
$server->addPlugin(new UserIdHeaderPlugin($this->userSession));

// Some WebDAV clients do require Class 2 WebDAV support (locking), since
// we do not provide locking we emulate it using a fake locking plugin.
Expand Down
36 changes: 36 additions & 0 deletions apps/dav/lib/Connector/Sabre/UserIdHeaderPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\DAV\Connector\Sabre;

use OCP\IUserSession;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

class UserIdHeaderPlugin extends \Sabre\DAV\ServerPlugin {
public function __construct(
private readonly IUserSession $userSession,
) {
}

public function initialize(\Sabre\DAV\Server $server): void {
$server->on('afterMethod:*', [$this, 'afterMethod']);
}

/**
* Add the request id as a header in the response
*
* @param RequestInterface $request request
* @param ResponseInterface $response response
*/
public function afterMethod(RequestInterface $request, ResponseInterface $response): void {
if ($user = $this->userSession->getUser()) {
$response->setHeader('X-User-Id', $user->getUID());
}
}
}
Loading