Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement iCalendar / RFC 5545 specification to allow multi-line fields #42

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist/**/*.js
docs/**/*.js
docs-site/**/*.js
lib/**/*.js
3 changes: 3 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ module.exports = function(config) {
// web server port
port: 9876,

listenAddress: "0.0.0.0",
hostname: "0.0.0.0",


// enable / disable colors in the output (reporters and logs)
colors: true,
Expand Down
28 changes: 24 additions & 4 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ export default class helpers {
return formattedDate.replace("+00:00", "Z");
}

formatRFC5545Text(raw) {
// https://tools.ietf.org/html/rfc5545#section-3.1
// Lines of text SHOULD NOT be longer than 75 octets, excluding the line
// break. Long content lines SHOULD be split into a multiple line
// representations using a line "folding" technique.
// https://tools.ietf.org/html/rfc5545#section-3.3.11
// An intentional formatted text line break MUST only be included in
// a "TEXT" property value by representing the line break with the
// character sequence of BACKSLASH, followed by a LATIN SMALL LETTER
// N or a LATIN CAPITAL LETTER N, that is "\n" or "\N".
const text = raw || "";
const escaped = text.replace(/[\r\n]/g, '\\n');
const splits = [];
for (let split = escaped; (split.length !== 0); split = split.substr(75)) {
splits.push(split.substr(0, 75));
}
const formatted = splits.join("\n ");
return formatted;
}

calculateDuration(startTime, endTime) {
// snag parameters and format properly in UTC
let end = moment.utc(endTime).format("DD/MM/YYYY HH:mm:ss");
Expand Down Expand Up @@ -77,9 +97,9 @@ export default class helpers {
"URL:" + document.URL,
"DTSTART:" + this.formatTime(event.startTime),
"DTEND:" + this.formatTime(event.endTime),
"SUMMARY:" + event.title,
"DESCRIPTION:" + event.description,
"LOCATION:" + event.location,
"SUMMARY:" + this.formatRFC5545Text(event.title),
"DESCRIPTION:" + this.formatRFC5545Text(event.description),
"LOCATION:" + this.formatRFC5545Text(event.location),
"END:VEVENT",
"END:VCALENDAR"
].join("\n");
Expand Down Expand Up @@ -112,4 +132,4 @@ export default class helpers {

return mobile;
}
}
}
84 changes: 84 additions & 0 deletions test/formatRFC5545Text_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import helpersClass from "../src/helpers";


describe("helpers", () => {
const CHARS_10 = "1234567890";
const CHARS_75 = [ CHARS_10, CHARS_10, CHARS_10, CHARS_10, CHARS_10, CHARS_10, CHARS_10, CHARS_10 ].join("").substr(0, 75);
const helpers = new helpersClass();


describe("formatRFC5545Text", () => {
it("should format a false-y value", () => {
expect(helpers.formatRFC5545Text("")).to.equal("");
expect(helpers.formatRFC5545Text()).to.equal("");
expect(helpers.formatRFC5545Text(null)).to.equal("");
});

it("should format a short String", () => {
expect(helpers.formatRFC5545Text("x")).to.equal("x");
expect(helpers.formatRFC5545Text(CHARS_75)).to.equal(CHARS_75);
});

it("should split a String every 75 characters", () => {
expect(helpers.formatRFC5545Text(`${CHARS_75}x`)).to.equal(`${CHARS_75}\n x`);
expect(helpers.formatRFC5545Text(`${CHARS_75}${CHARS_75}x`)).to.equal(`${CHARS_75}\n ${CHARS_75}\n x`);
});

it("should escape CRs and CRLFs", () => {
expect(helpers.formatRFC5545Text(`
carriage return
line feed`)).to.equal("\\ncarriage return\\nline feed");
expect(helpers.formatRFC5545Text("carriage\rreturn\r")).to.equal("carriage\\nreturn\\n");
});

it("should split a String with escaped CRLFs", () => {
const CHARS_74 = CHARS_75.substr(0, 74);
expect(helpers.formatRFC5545Text(`${CHARS_75}\n${CHARS_10}`)).to.equal(`${CHARS_75}\n \\n${CHARS_10}`);
expect(helpers.formatRFC5545Text(`${CHARS_74}\n${CHARS_10}`)).to.equal(`${CHARS_74}\\\n n${CHARS_10}`);
});
});


describe("buildUrl", () => {
const EVENT = {
startTime: "2019-04-10T00:00:00Z",
endTime: "2019-04-10T01:02:03Z",
location: CHARS_10,
title: "TITLE\n\nON MULTIPLE LINES",
description: [ CHARS_10, CHARS_75, CHARS_10 ].join("\n"),
};

it("honors RFC-5545 by default", () => {
const calendarUrl = helpers.buildUrl(EVENT, "");
expect(calendarUrl).to.equal(`
BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
URL:${document.URL}
DTSTART:20190410T000000Z
DTEND:20190410T010203Z
SUMMARY:TITLE\\n\\nON MULTIPLE LINES
DESCRIPTION:1234567890\\n123456789012345678901234567890123456789012345678901234567890123
456789012345\\n1234567890
LOCATION:1234567890
END:VEVENT
END:VCALENDAR
`.trim());
});

it("does not use RFC-5545 for { type: 'google' }", () => {
const calendarUrl = helpers.buildUrl(EVENT, "google");
expect(calendarUrl).to.equal("https://calendar.google.com/calendar/render?action=TEMPLATE&dates=20190410T000000Z/20190410T010203Z&location=1234567890&text=TITLE%0A%0AON%20MULTIPLE%20LINES&details=1234567890%0A123456789012345678901234567890123456789012345678901234567890123456789012345%0A1234567890");
});

it("does not use RFC-5545 for { type: 'yahoo' }", () => {
const calendarUrl = helpers.buildUrl(EVENT, "yahoo");
expect(calendarUrl).to.equal("https://calendar.yahoo.com/?v=60&view=d&type=20&title=TITLE%0A%0AON%20MULTIPLE%20LINES&st=20190410T000000Z&dur=1:02&desc=1234567890%0A123456789012345678901234567890123456789012345678901234567890123456789012345%0A1234567890&in_loc=1234567890");
});

it("does not use RFC-5545 for { type: 'outlookcom' }", () => {
const calendarUrl = helpers.buildUrl(EVENT, "outlookcom");
expect(calendarUrl).to.match(new RegExp("https://outlook.live.com/owa/[?]rru=addevent&startdt=20190410T000000Z&enddt=20190410T010203Z&subject=TITLE%0A%0AON%20MULTIPLE%20LINES&location=1234567890&body=1234567890%0A123456789012345678901234567890123456789012345678901234567890123456789012345%0A1234567890&allday=false&uid=[0-9]+_[0-9]+&path=/calendar/view/Month"));
});
});
});