Skip to content

Commit

Permalink
Fix outlook event invite description with links
Browse files Browse the repository at this point in the history
Fixed Outlook event invite descriptions to preserve links after
sanitization by applying the prepareCalendarDescription function
  • Loading branch information
andrehgdias committed Jan 27, 2025
1 parent 0437771 commit 30a33d9
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import { Dialog } from "../../../../common/gui/base/Dialog.js"
import { createAsyncDropdown, DROPDOWN_MARGIN, PosRect, showDropdown } from "../../../../common/gui/base/Dropdown.js"
import { Keys } from "../../../../common/api/common/TutanotaConstants.js"
import type { HtmlSanitizer } from "../../../../common/misc/HtmlSanitizer.js"
import { prepareCalendarDescription } from "../../../../common/calendar/date/CalendarUtils.js"
import { BootIcons } from "../../../../common/gui/base/icons/BootIcons.js"
import { IconButton } from "../../../../common/gui/base/IconButton.js"
import { convertTextToHtml } from "../../../../common/misc/Formatter.js"
import { CalendarEventPreviewViewModel } from "./CalendarEventPreviewViewModel.js"
import { showDeletePopup } from "../CalendarGuiUtils.js"
import { prepareCalendarDescription } from "../../../../common/api/common/utils/CommonCalendarUtils.js"

/**
* small modal displaying all relevant information about an event in a compact fashion. offers limited editing capabilities to participants in the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import m from "mithril"
import { clone, Thunk } from "@tutao/tutanota-utils"
import { CalendarEventUidIndexEntry } from "../../../../common/api/worker/facades/lazy/CalendarFacade.js"
import { EventEditorDialog } from "../eventeditor-view/CalendarEventEditDialog.js"
import { convertTextToHtml } from "../../../../common/misc/Formatter.js"
import { prepareCalendarDescription } from "../../../../common/api/common/utils/CommonCalendarUtils.js"

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

async sanitizeDescription(): Promise<void> {
const { htmlSanitizer } = await import("../../../../common/misc/HtmlSanitizer.js")
this.sanitizedDescription = htmlSanitizer.sanitizeHTML(this.calendarEvent.description, {
blockExternalContent: true,
}).html
this.sanitizedDescription = prepareCalendarDescription(
this.calendarEvent.description,
(s) =>
htmlSanitizer.sanitizeHTML(convertTextToHtml(s), {
blockExternalContent: false,
}).html,
)
}

getSanitizedDescription() {
Expand Down
29 changes: 29 additions & 0 deletions src/common/api/common/utils/CommonCalendarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,32 @@ export function isBefore(dateA: Date, dateB: Date, comparisonType: "dateTime" |
throw new Error("Unknown comparison method")
}
}

/**
* Prepare calendar event description to be shown to the user.
*
* Outlook invitations frequently include links enclosed within "<>" in the email/event description.
* Sanitizing this string can cause the links to be lost.
* To prevent this, we use this function to remove the "<>" characters before applying sanitization whenever necessary.
*
* They look like this:
* ```
* text<https://example.com>
* ```
*
* @param description Description to clean up
* @param sanitizer Sanitizer to apply after preparing the description
*/
export function prepareCalendarDescription(description: string, sanitizer: (s: string) => string): string {
const prepared = description.replace(/<(http|https):\/\/[A-z0-9$-_.+!*(),/?]+>/gi, (possiblyLink) => {
try {
const withoutBrackets = possiblyLink.slice(1, -1)
const url = new URL(withoutBrackets)
return ` <a href="${url.toString()}">${withoutBrackets}</a>`
} catch (e) {
return possiblyLink
}
})

return sanitizer(prepared)
}
26 changes: 0 additions & 26 deletions src/common/calendar/date/CalendarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,32 +823,6 @@ export function findFirstPrivateCalendar(calendarInfo: ReadonlyMap<Id, CalendarI
return null
}

/**
* Prepare calendar event description to be shown to the user.
*
* It is needed to fix special format of links from Outlook which otherwise disappear during sanitizing.
* They look like this:
* ```
* text<https://example.com>
* ```
*
* @param description description to clean up
* @param sanitizer optional sanitizer to apply after preparing the description
*/
export function prepareCalendarDescription(description: string, sanitizer: (s: string) => string): string {
const prepared = description.replace(/<(http|https):\/\/[A-z0-9$-_.+!*(),/?]+>/gi, (possiblyLink) => {
try {
const withoutBrackets = possiblyLink.slice(1, -1)
const url = new URL(withoutBrackets)
return `<a href="${url.toString()}">${withoutBrackets}</a>`
} catch (e) {
return possiblyLink
}
})

return sanitizer(prepared)
}

export const DEFAULT_HOUR_OF_DAY = 6

/** Get CSS class for the date element. */
Expand Down
10 changes: 9 additions & 1 deletion src/common/misc/SanitizedTextViewModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { HtmlSanitizer } from "./HtmlSanitizer.js"
import { noOp } from "@tutao/tutanota-utils"
import { convertTextToHtml } from "./Formatter.js"
import { prepareCalendarDescription } from "../api/common/utils/CommonCalendarUtils.js"

export class SanitizedTextViewModel {
private sanitizedText: string | null = null
Expand All @@ -14,7 +16,13 @@ export class SanitizedTextViewModel {

get content(): string {
if (this.sanitizedText == null) {
this.sanitizedText = this.sanitizer.sanitizeHTML(this.text, { blockExternalContent: false }).html
this.sanitizedText = prepareCalendarDescription(
this.text,
(s) =>
this.sanitizer.sanitizeHTML(convertTextToHtml(s), {
blockExternalContent: false,
}).html,
)
}
return this.sanitizedText
}
Expand Down
10 changes: 7 additions & 3 deletions test/tests/calendar/CalendarUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,19 @@ import {
getWeekNumber,
isEventBetweenDays,
parseAlarmInterval,
prepareCalendarDescription,
StandardAlarmInterval,
} from "../../../src/common/calendar/date/CalendarUtils.js"
import { lang } from "../../../src/common/misc/LanguageViewModel.js"
import { DateWrapperTypeRef, GroupMembershipTypeRef, GroupTypeRef, UserTypeRef } from "../../../src/common/api/entities/sys/TypeRefs.js"
import { AccountType, EndType, GroupType, RepeatPeriod, ShareCapability } from "../../../src/common/api/common/TutanotaConstants.js"
import { timeStringFromParts } from "../../../src/common/misc/Formatter.js"
import { DateTime } from "luxon"
import { generateEventElementId, getAllDayDateUTC, serializeAlarmInterval } from "../../../src/common/api/common/utils/CommonCalendarUtils.js"
import {
generateEventElementId,
getAllDayDateUTC,
prepareCalendarDescription,
serializeAlarmInterval,
} from "../../../src/common/api/common/utils/CommonCalendarUtils.js"
import { hasCapabilityOnGroup } from "../../../src/common/sharing/GroupUtils.js"
import {
CalendarEvent,
Expand Down Expand Up @@ -491,7 +495,7 @@ o.spec("calendar utils tests", function () {
o.spec("prepareCalendarDescription", function () {
o("angled link replaced with a proper link", function () {
o(prepareCalendarDescription("JoinBlahBlah<https://the-link.com/path>", identity)).equals(
`JoinBlahBlah<a href="https://the-link.com/path">https://the-link.com/path</a>`,
`JoinBlahBlah <a href="https://the-link.com/path">https://the-link.com/path</a>`,
)
})
o("normal HTML link is not touched", function () {
Expand Down

0 comments on commit 30a33d9

Please sign in to comment.