Skip to content

Commit dbbf01a

Browse files
andrehgdiasmup
authored andcommitted
Fix outlook event invite description with links
Fixed Outlook event invite descriptions to preserve links after sanitization by applying the prepareCalendarDescription function
1 parent 88cf9d5 commit dbbf01a

File tree

6 files changed

+55
-34
lines changed

6 files changed

+55
-34
lines changed

src/calendar-app/calendar/gui/eventpopup/CalendarEventPopup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import { Dialog } from "../../../../common/gui/base/Dialog.js"
99
import { createAsyncDropdown, DROPDOWN_MARGIN, PosRect, showDropdown } from "../../../../common/gui/base/Dropdown.js"
1010
import { Keys } from "../../../../common/api/common/TutanotaConstants.js"
1111
import type { HtmlSanitizer } from "../../../../common/misc/HtmlSanitizer.js"
12-
import { prepareCalendarDescription } from "../../../../common/calendar/date/CalendarUtils.js"
1312
import { BootIcons } from "../../../../common/gui/base/icons/BootIcons.js"
1413
import { IconButton } from "../../../../common/gui/base/IconButton.js"
1514
import { convertTextToHtml } from "../../../../common/misc/Formatter.js"
1615
import { CalendarEventPreviewViewModel } from "./CalendarEventPreviewViewModel.js"
1716
import { showDeletePopup } from "../CalendarGuiUtils.js"
17+
import { prepareCalendarDescription } from "../../../../common/api/common/utils/CommonCalendarUtils.js"
1818

1919
/**
2020
* small modal displaying all relevant information about an event in a compact fashion. offers limited editing capabilities to participants in the

src/calendar-app/calendar/gui/eventpopup/CalendarEventPreviewViewModel.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import m from "mithril"
99
import { clone, Thunk } from "@tutao/tutanota-utils"
1010
import { CalendarEventUidIndexEntry } from "../../../../common/api/worker/facades/lazy/CalendarFacade.js"
1111
import { EventEditorDialog } from "../eventeditor-view/CalendarEventEditDialog.js"
12+
import { convertTextToHtml } from "../../../../common/misc/Formatter.js"
13+
import { prepareCalendarDescription } from "../../../../common/api/common/utils/CommonCalendarUtils.js"
1214

1315
/**
1416
* makes decisions about which operations are available from the popup and knows how to implement them depending on the event's type.
@@ -215,9 +217,13 @@ export class CalendarEventPreviewViewModel {
215217

216218
async sanitizeDescription(): Promise<void> {
217219
const { htmlSanitizer } = await import("../../../../common/misc/HtmlSanitizer.js")
218-
this.sanitizedDescription = htmlSanitizer.sanitizeHTML(this.calendarEvent.description, {
219-
blockExternalContent: true,
220-
}).html
220+
this.sanitizedDescription = prepareCalendarDescription(
221+
this.calendarEvent.description,
222+
(s) =>
223+
htmlSanitizer.sanitizeHTML(convertTextToHtml(s), {
224+
blockExternalContent: false,
225+
}).html,
226+
)
221227
}
222228

223229
getSanitizedDescription() {

src/common/api/common/utils/CommonCalendarUtils.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,32 @@ export function isBefore(dateA: Date, dateB: Date, comparisonType: "dateTime" |
202202
throw new Error("Unknown comparison method")
203203
}
204204
}
205+
206+
/**
207+
* Prepare calendar event description to be shown to the user.
208+
*
209+
* Outlook invitations frequently include links enclosed within "<>" in the email/event description.
210+
* Sanitizing this string can cause the links to be lost.
211+
* To prevent this, we use this function to remove the "<>" characters before applying sanitization whenever necessary.
212+
*
213+
* They look like this:
214+
* ```
215+
* text<https://example.com>
216+
* ```
217+
*
218+
* @param description Description to clean up
219+
* @param sanitizer Sanitizer to apply after preparing the description
220+
*/
221+
export function prepareCalendarDescription(description: string, sanitizer: (s: string) => string): string {
222+
const prepared = description.replace(/<(http|https):\/\/[A-z0-9$-_.+!*(),/?]+>/gi, (possiblyLink) => {
223+
try {
224+
const withoutBrackets = possiblyLink.slice(1, -1)
225+
const url = new URL(withoutBrackets)
226+
return ` <a href="${url.toString()}">${withoutBrackets}</a>`
227+
} catch (e) {
228+
return possiblyLink
229+
}
230+
})
231+
232+
return sanitizer(prepared)
233+
}

src/common/calendar/date/CalendarUtils.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -823,32 +823,6 @@ export function findFirstPrivateCalendar(calendarInfo: ReadonlyMap<Id, CalendarI
823823
return null
824824
}
825825

826-
/**
827-
* Prepare calendar event description to be shown to the user.
828-
*
829-
* It is needed to fix special format of links from Outlook which otherwise disappear during sanitizing.
830-
* They look like this:
831-
* ```
832-
* text<https://example.com>
833-
* ```
834-
*
835-
* @param description description to clean up
836-
* @param sanitizer optional sanitizer to apply after preparing the description
837-
*/
838-
export function prepareCalendarDescription(description: string, sanitizer: (s: string) => string): string {
839-
const prepared = description.replace(/<(http|https):\/\/[A-z0-9$-_.+!*(),/?]+>/gi, (possiblyLink) => {
840-
try {
841-
const withoutBrackets = possiblyLink.slice(1, -1)
842-
const url = new URL(withoutBrackets)
843-
return `<a href="${url.toString()}">${withoutBrackets}</a>`
844-
} catch (e) {
845-
return possiblyLink
846-
}
847-
})
848-
849-
return sanitizer(prepared)
850-
}
851-
852826
export const DEFAULT_HOUR_OF_DAY = 6
853827

854828
/** Get CSS class for the date element. */

src/common/misc/SanitizedTextViewModel.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { HtmlSanitizer } from "./HtmlSanitizer.js"
22
import { noOp } from "@tutao/tutanota-utils"
3+
import { convertTextToHtml } from "./Formatter.js"
4+
import { prepareCalendarDescription } from "../api/common/utils/CommonCalendarUtils.js"
35

46
export class SanitizedTextViewModel {
57
private sanitizedText: string | null = null
@@ -14,7 +16,13 @@ export class SanitizedTextViewModel {
1416

1517
get content(): string {
1618
if (this.sanitizedText == null) {
17-
this.sanitizedText = this.sanitizer.sanitizeHTML(this.text, { blockExternalContent: false }).html
19+
this.sanitizedText = prepareCalendarDescription(
20+
this.text,
21+
(s) =>
22+
this.sanitizer.sanitizeHTML(convertTextToHtml(s), {
23+
blockExternalContent: false,
24+
}).html,
25+
)
1826
}
1927
return this.sanitizedText
2028
}

test/tests/calendar/CalendarUtilsTest.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,19 @@ import {
2424
getWeekNumber,
2525
isEventBetweenDays,
2626
parseAlarmInterval,
27-
prepareCalendarDescription,
2827
StandardAlarmInterval,
2928
} from "../../../src/common/calendar/date/CalendarUtils.js"
3029
import { lang } from "../../../src/common/misc/LanguageViewModel.js"
3130
import { DateWrapperTypeRef, GroupMembershipTypeRef, GroupTypeRef, UserTypeRef } from "../../../src/common/api/entities/sys/TypeRefs.js"
3231
import { AccountType, EndType, GroupType, RepeatPeriod, ShareCapability } from "../../../src/common/api/common/TutanotaConstants.js"
3332
import { timeStringFromParts } from "../../../src/common/misc/Formatter.js"
3433
import { DateTime } from "luxon"
35-
import { generateEventElementId, getAllDayDateUTC, serializeAlarmInterval } from "../../../src/common/api/common/utils/CommonCalendarUtils.js"
34+
import {
35+
generateEventElementId,
36+
getAllDayDateUTC,
37+
prepareCalendarDescription,
38+
serializeAlarmInterval,
39+
} from "../../../src/common/api/common/utils/CommonCalendarUtils.js"
3640
import { hasCapabilityOnGroup } from "../../../src/common/sharing/GroupUtils.js"
3741
import {
3842
CalendarEvent,
@@ -491,7 +495,7 @@ o.spec("calendar utils tests", function () {
491495
o.spec("prepareCalendarDescription", function () {
492496
o("angled link replaced with a proper link", function () {
493497
o(prepareCalendarDescription("JoinBlahBlah<https://the-link.com/path>", identity)).equals(
494-
`JoinBlahBlah<a href="https://the-link.com/path">https://the-link.com/path</a>`,
498+
`JoinBlahBlah <a href="https://the-link.com/path">https://the-link.com/path</a>`,
495499
)
496500
})
497501
o("normal HTML link is not touched", function () {

0 commit comments

Comments
 (0)