Skip to content

Commit 50e9577

Browse files
DerDreschnerbackportbot[bot]
authored andcommitted
fix(ImipService): Make sure non-html fields are escaped and html fields are not
fix(ImipService): Make sure non-html fields are escaped and html fields are not Signed-off-by: David Dreschner <david.dreschner@nextcloud.com> [skip ci]
1 parent e511ce5 commit 50e9577

2 files changed

Lines changed: 416 additions & 53 deletions

File tree

apps/dav/lib/CalDAV/Schedule/IMipService.php

Lines changed: 69 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -76,26 +76,39 @@ public static function readPropertyWithDefault(VEvent $vevent, string $property,
7676
return $default;
7777
}
7878

79-
private function generateDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
80-
$strikethrough = "<span style='text-decoration: line-through'>%s</span><br />%s";
81-
if (!isset($vevent->$property)) {
82-
return $default;
79+
private function getStrikethroughString(?string $oldString, ?string $newValue = null): ?string {
80+
if ($oldString === null || $oldString === '') {
81+
return null;
8382
}
84-
$value = $vevent->$property->getValue();
85-
$newstring = $value === null ? null : htmlspecialchars($value);
86-
if (isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newstring) {
87-
$oldstring = $oldVEvent->$property->getValue();
88-
return sprintf($strikethrough, htmlspecialchars($oldstring), $newstring);
83+
84+
$strikethrough = '<span style="text-decoration: line-through">%s</span><br />%s';
85+
return sprintf($strikethrough, $oldString, $newValue ?? '');
86+
}
87+
88+
private function generateDiffString(VEvent $vEvent, VEvent $oldVEvent, string $property): ?string {
89+
if (!isset($vEvent->$property)) {
90+
return null;
8991
}
90-
return $newstring;
92+
93+
$newValue = $vEvent->$property->getValue();
94+
$newString = $newValue === null ? null : htmlspecialchars($newValue);
95+
96+
$propertyChanged = isset($oldVEvent->$property) && $oldVEvent->$property->getValue() !== $newString;
97+
if ($propertyChanged) {
98+
$oldValue = $oldVEvent->$property->getValue();
99+
$oldString = htmlspecialchars($oldValue);
100+
101+
return $this->getStrikethroughString($oldString, $newString);
102+
}
103+
return $newString;
91104
}
92105

93106
/**
94107
* Like generateDiffString() but linkifies the property values if they are urls.
95108
*/
96-
private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent, string $property, string $default): ?string {
97-
if (!isset($vevent->$property)) {
98-
return $default;
109+
private function generateLinkifiedDiffString(VEvent $vEvent, VEvent $oldVEvent, string $property): ?string {
110+
if (!isset($vEvent->$property)) {
111+
return null;
99112
}
100113
$value = $vevent->$property->getValue();
101114
$newString = $value === null ? null : htmlspecialchars($value);
@@ -107,7 +120,8 @@ private function generateLinkifiedDiffString(VEvent $vevent, VEvent $oldVEvent,
107120
$this->linkify($newString) ?? $newString ?? ''
108121
);
109122
}
110-
return $this->linkify($newString) ?? $newString;
123+
124+
return $this->getStrikethroughString($newString);
111125
}
112126

113127
/**
@@ -117,7 +131,15 @@ private function linkify(?string $url): ?string {
117131
if ($url === null) {
118132
return null;
119133
}
120-
if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
134+
135+
$isValidLinkUrl
136+
= filter_var($url, FILTER_VALIDATE_URL) !== false
137+
&& (
138+
str_starts_with($url, 'http://')
139+
|| str_starts_with($url, 'https://')
140+
);
141+
142+
if (!$isValidLinkUrl) {
121143
return null;
122144
}
123145

@@ -130,7 +152,6 @@ private function linkify(?string $url): ?string {
130152
* @return array
131153
*/
132154
public function buildBodyData(VEvent $vEvent, ?VEvent $oldVEvent): array {
133-
134155
// construct event reader
135156
$eventReaderCurrent = new EventReader($vEvent);
136157
$eventReaderPrevious = !empty($oldVEvent) ? new EventReader($oldVEvent) : null;
@@ -142,22 +163,26 @@ public function buildBodyData(VEvent $vEvent, ?VEvent $oldVEvent): array {
142163
$data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
143164
}
144165

145-
$data['meeting_url_html'] = self::readPropertyWithDefault($vEvent, 'URL', $defaultVal);
146-
147-
if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) {
148-
$data['meeting_location_html'] = $locationHtml;
149-
}
166+
$data['meeting_location_html'] = $this->linkify($data['meeting_location']);
167+
$data['meeting_url_html'] = $this->linkify($data['meeting_url']);
150168

151169
if (!empty($oldVEvent)) {
152-
$oldMeetingWhen = $this->generateWhenString($eventReaderPrevious);
153-
$data['meeting_title_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'SUMMARY', $data['meeting_title']);
154-
$data['meeting_description_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'DESCRIPTION', $data['meeting_description']);
155-
$data['meeting_location_html'] = $this->generateLinkifiedDiffString($vEvent, $oldVEvent, 'LOCATION', $data['meeting_location']);
156-
157-
$oldUrl = self::readPropertyWithDefault($oldVEvent, 'URL', $defaultVal);
158-
$data['meeting_url_html'] = !empty($oldUrl) && $oldUrl !== $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $oldUrl) : $data['meeting_url'];
170+
$data['meeting_title_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'SUMMARY');
171+
$data['meeting_description_html'] = $this->generateDiffString($vEvent, $oldVEvent, 'DESCRIPTION');
172+
$data['meeting_location_html'] = $this->generateLinkifiedDiffString($vEvent, $oldVEvent, 'LOCATION');
173+
174+
$oldMeetingUrl = self::readPropertyWithDefault($oldVEvent, 'URL', $defaultVal);
175+
$oldMeetingUrlAsLink = $this->linkify($oldMeetingUrl);
176+
$meetingUrlAsLinkChanged = !empty($oldMeetingUrlAsLink) && $oldMeetingUrlAsLink !== $data['meeting_url_html'];
177+
if ($meetingUrlAsLinkChanged) {
178+
$data['meeting_url_html'] = $this->getStrikethroughString(htmlspecialchars($oldMeetingUrl), $data['meeting_url_html']);
179+
}
159180

160-
$data['meeting_when_html'] = $oldMeetingWhen !== $data['meeting_when'] ? sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", $oldMeetingWhen, $data['meeting_when']) : $data['meeting_when'];
181+
$oldMeetingWhen = $this->generateWhenString($eventReaderPrevious);
182+
$meetingWhenChanged = $oldMeetingWhen !== $data['meeting_when'];
183+
$data['meeting_when_html'] = $meetingWhenChanged
184+
? $this->getStrikethroughString($oldMeetingWhen, $data['meeting_when'])
185+
: null;
161186
}
162187
// generate occurring next string
163188
if ($eventReaderCurrent->recurs()) {
@@ -181,11 +206,8 @@ public function buildReplyBodyData(VEvent $vEvent): array {
181206
$data[$key] = self::readPropertyWithDefault($vEvent, $property, $defaultVal);
182207
}
183208

184-
if (($locationHtml = $this->linkify($data['meeting_location'])) !== null) {
185-
$data['meeting_location_html'] = $locationHtml;
186-
}
187-
188-
$data['meeting_url_html'] = $data['meeting_url'] ? sprintf('<a href="%1$s">%1$s</a>', $data['meeting_url']) : '';
209+
$data['meeting_location_html'] = $this->linkify($data['meeting_location']);
210+
$data['meeting_url_html'] = $this->linkify($data['meeting_url']);
189211

190212
// generate occurring next string
191213
if ($eventReader->recurs()) {
@@ -468,7 +490,7 @@ public function generateWhenStringRecurringMonthly(EventReader $er): string {
468490
// days of month
469491
if ($er->recurringPattern() === 'R') {
470492
$days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' '
471-
. implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
493+
. implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
472494
} else {
473495
$days = implode(', ', $er->recurringDaysOfMonth());
474496
}
@@ -535,7 +557,7 @@ public function generateWhenStringRecurringYearly(EventReader $er): string {
535557
// days of month
536558
if ($er->recurringPattern() === 'R') {
537559
$days = implode(', ', array_map(function ($value) { return $this->localizeRelativePositionName($value); }, $er->recurringRelativePositionNamed())) . ' '
538-
. implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
560+
. implode(', ', array_map(function ($value) { return $this->localizeDayName($value); }, $er->recurringDaysOfWeekNamed()));
539561
} else {
540562
$days = $er->startDateTime()->format('jS');
541563
}
@@ -625,7 +647,6 @@ public function generateWhenStringRecurringFixed(EventReader $er): string {
625647
* @return string
626648
*/
627649
public function generateOccurringString(EventReader $er): string {
628-
629650
// initialize
630651
$occurrence = null;
631652
$occurrence2 = null;
@@ -796,26 +817,26 @@ public function buildCancelledBodyData(VEvent $vEvent): array {
796817
// construct event reader
797818
$eventReaderCurrent = new EventReader($vEvent);
798819
$defaultVal = '';
799-
$strikethrough = "<span style='text-decoration: line-through'>%s</span>";
800820

801821
$newMeetingWhen = $this->generateWhenString($eventReaderCurrent);
802-
$newSummary = htmlspecialchars(isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event'));
803-
$newDescription = htmlspecialchars(isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal);
804-
$newUrl = isset($vEvent->URL) && (string)$vEvent->URL !== '' ? sprintf('<a href="%1$s">%1$s</a>', $vEvent->URL) : $defaultVal;
805-
$newLocation = htmlspecialchars(isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal);
806-
$newLocationHtml = $this->linkify($newLocation) ?? $newLocation;
822+
$newSummary = isset($vEvent->SUMMARY) && (string)$vEvent->SUMMARY !== '' ? (string)$vEvent->SUMMARY : $this->l10n->t('Untitled event');
823+
$newDescription = isset($vEvent->DESCRIPTION) && (string)$vEvent->DESCRIPTION !== '' ? (string)$vEvent->DESCRIPTION : $defaultVal;
824+
$newUrl = isset($vEvent->URL) && (string)$vEvent->URL !== '' ? $this->linkify((string)$vEvent->URL) : $defaultVal;
825+
$newLocation = isset($vEvent->LOCATION) && (string)$vEvent->LOCATION !== '' ? (string)$vEvent->LOCATION : $defaultVal;
826+
$newLocationHtml = $this->linkify($newLocation);
807827

808828
$data = [];
809-
$data['meeting_when_html'] = $newMeetingWhen === '' ?: sprintf($strikethrough, $newMeetingWhen);
829+
$data['meeting_when_html'] = $this->getStrikethroughString(htmlspecialchars($newMeetingWhen));
810830
$data['meeting_when'] = $newMeetingWhen;
811-
$data['meeting_title_html'] = sprintf($strikethrough, $newSummary);
831+
$data['meeting_title_html'] = $this->getStrikethroughString(htmlspecialchars($newSummary));
812832
$data['meeting_title'] = $newSummary !== '' ? $newSummary: $this->l10n->t('Untitled event');
813-
$data['meeting_description_html'] = $newDescription !== '' ? sprintf($strikethrough, $newDescription) : '';
833+
$data['meeting_description_html'] = $this->getStrikethroughString(htmlspecialchars($newDescription));
814834
$data['meeting_description'] = $newDescription;
815-
$data['meeting_url_html'] = $newUrl !== '' ? sprintf($strikethrough, $newUrl) : '';
835+
$data['meeting_url_html'] = $this->getStrikethroughString($newUrl);
816836
$data['meeting_url'] = isset($vEvent->URL) ? (string)$vEvent->URL : '';
817-
$data['meeting_location_html'] = $newLocationHtml !== '' ? sprintf($strikethrough, $newLocationHtml) : '';
837+
$data['meeting_location_html'] = $this->getStrikethroughString($newLocationHtml ?? htmlspecialchars($newLocation));
818838
$data['meeting_location'] = $newLocation;
839+
819840
return $data;
820841
}
821842

0 commit comments

Comments
 (0)