├── .github ├── CODEOWNERS ├── workflows │ ├── conventional_commits.yml │ ├── release.yml │ ├── dependabot-approve-merge.yml │ ├── npm-audit-fix.yml │ └── lint-eslint.yml └── ISSUE_TEMPLATE │ ├── feature_request_form.yml │ └── bug_form.yml ├── .eslintrc.cjs ├── .gitignore ├── babel.config.cjs ├── src ├── factories │ ├── __mocks__ │ │ └── dateFactory.js │ ├── dateFactory.js │ └── icalFactory.js ├── helpers │ ├── __mocks__ │ │ └── cryptoHelper.js │ ├── cryptoHelper.js │ ├── stringHelper.js │ └── birthdayHelper.js ├── parsers │ ├── repairsteps │ │ ├── icalendar │ │ │ ├── icalendarConvertInvalidDateTimeValuesRepairStep.js │ │ │ ├── icalendarIllegalCreatedRepairStep.js │ │ │ ├── icalendarEmptyTriggerRepairStep.js │ │ │ ├── icalendarRemoveXNCGroupIdRepairStep.js │ │ │ ├── icalendarRemoveUnicodeSpecialNoncharactersRepairStep.js │ │ │ ├── icalendarAddMissingValueDateDoubleColonRepairStep.js │ │ │ ├── icalendarAddMissingValueDateRepairStep.js │ │ │ ├── icalendarAddMissingUIDRepairStep.js │ │ │ ├── index.js │ │ │ └── icalendarMultipleVCalendarBlocksRepairStep.js │ │ └── abstractRepairStep.js │ ├── index.js │ └── jcalendarParser.js ├── errors │ ├── illegalValueError.js │ ├── expectedICalJSError.js │ ├── unknownICALTypeError.js │ ├── modificationNotAllowedError.js │ ├── recurringWithoutDtStartError.js │ └── index.js ├── parameters │ └── index.js ├── recurrence │ └── index.js ├── components │ ├── nested │ │ └── index.js │ ├── index.js │ └── root │ │ ├── timezoneComponent.js │ │ ├── index.js │ │ └── journalComponent.js ├── properties │ ├── textProperty.js │ ├── freeBusyProperty.js │ ├── relationProperty.js │ ├── imageProperty.js │ └── index.js ├── config.js ├── values │ ├── abstractValue.js │ └── index.js └── traits │ ├── observer.js │ └── lockable.js ├── tests ├── assets │ ├── timezone-asia-kolkata.ics │ ├── vtodo-no-due-no-dtstart.ics │ ├── loader.js │ ├── vtodo-due-only.ics │ ├── vtodo-recurring-no-due-no-dtstart-invalid.ics │ ├── simple-date-dtstart-only.ics │ ├── simple-date-dtstart-duration.ics │ ├── simple-date-dtstart-dtend.ics │ ├── timezone-america-la.ics │ ├── timezone-america-nyc.ics │ ├── timezone-europe-berlin.ics │ ├── freebusy.ics │ ├── recurring-allday.ics │ ├── itip-cancel.ics │ ├── illegal-created.ics │ ├── illegal-created-sanitized.ics │ ├── import-vevent-vtodo.ics │ ├── x-nc-group-id-sanitized.ics │ ├── x-nc-group-id.ics │ ├── invalid-date-time.ics │ ├── invalid-date-time-sanitized.ics │ ├── missing-uid.ics │ ├── missing-uid-sanitized.ics │ ├── double-colon-missing-date-sanitized.ics │ ├── double-colon-missing-date.ics │ ├── very-long-event.ics │ ├── import-vevent-replace-alias.ics │ ├── simple-date-time-new-york-dtstart-only.ics │ ├── simple-date-time-new-york-dtstart-duration.ics │ ├── import-vjournal-vtodo.ics │ ├── missing-value-date.ics │ ├── simple-date-time-europe-berlin-dtstart-dtend.ics │ ├── missing-value-date-sanitized.ics │ ├── vtodo-dtstart-duration.ics │ ├── vtodo-due-dtstart.ics │ ├── vtodo-recurring-dtstart-duration.ics │ ├── vtodo-recurring-due-dtstart.ics │ ├── rrule-weekly-recurrence-id.ics │ ├── empty-trigger.ics │ ├── empty-trigger-sanitized.ics │ ├── recurring-recurrence-id-only.ics │ ├── weekly-recurring-with-exception.ics │ ├── vtodo-recurring-with-recurrence-exceptions.ics │ ├── multiple-vcalendar-blocks-sanitized.ics │ ├── unicode-non-character-fffe-after.ics │ ├── unicode-non-character-ffff-after.ics │ ├── unicode-non-character-fffe-before.ics │ ├── unicode-non-character-ffff-before.ics │ ├── empty-trigger-with-parameters.ics │ ├── empty-trigger-with-parameters-sanitized.ics │ ├── import-vevent.ics │ ├── complex-recurrence-id-modifications.ics │ └── multiple-vcalendar-blocks.ics ├── unit │ ├── helpers │ │ ├── cryptoHelper.test.js │ │ └── stringHelper.test.js │ ├── factories │ │ └── dateFactory.test.js │ ├── parsers │ │ └── repairsteps │ │ │ ├── icalendar │ │ │ ├── icalendarConvertCreatedDateTimeRepairStep.test.js │ │ │ ├── icalendarIllegalCreatedRepairStep.test.js │ │ │ ├── icalendarRemoveXNCGroupIdRepairStep.test.js │ │ │ ├── icalendarAddMissingValueDateRepairStep.test.js │ │ │ ├── icalendarMultipleVCalendarBlocksRepairStep.test.js │ │ │ ├── icalendarAddMissingUIDRepairStep.test.js │ │ │ ├── icalendarAddMissingValueDateDoubleColonRepairStep.test.js │ │ │ ├── icalendarEmptyTriggerRepairStep.test.js │ │ │ ├── icalendarRemoveUnicodeSpecialNoncharactersRepairStep.test.js │ │ │ └── index.test.js │ │ │ └── abstractRepairStep.test.js │ ├── values │ │ ├── abstractValue.test.js │ │ └── index.test.js │ ├── errors │ │ ├── illegalValueError.test.js │ │ ├── expectedICalJSError.test.js │ │ ├── recurringWithoutDtStartError.test.js │ │ └── modificationNotAllowedError.test.js │ ├── recurrence │ │ └── recurrenceManager.test.js │ ├── components │ │ ├── nested │ │ │ └── index.test.js │ │ └── root │ │ │ ├── eventComponent.test.js │ │ │ ├── freebusyComponent.test.js │ │ │ ├── abstractRecurringComponent.test.js │ │ │ ├── index.test.js │ │ │ └── timezoneComponent.test.js │ ├── config.test.js │ ├── traits │ │ ├── lockable.test.js │ │ └── observer.test.js │ └── properties │ │ ├── freeBusyProperty.test.js │ │ └── index.test.js └── integration │ ├── parser │ └── isAllDay.test.js │ └── creating │ └── createFreeBusyRequest.test.js ├── .editorconfig ├── vite.config.mjs ├── Limitations.md ├── README.md ├── package.json └── renovate.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Package maintainers 2 | * @GVodyanov @SebastianKrupinski 3 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@nextcloud', 4 | ], 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | calendar-js.iml 4 | 5 | coverage/ 6 | docs/ 7 | node_modules/ 8 | dist/ 9 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | const babelConfig = require('@nextcloud/babel-config') 2 | 3 | module.exports = babelConfig 4 | -------------------------------------------------------------------------------- /src/factories/__mocks__/dateFactory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | export function dateFactory() { 5 | return new Date(Date.UTC(2030, 0, 1, 0, 0, 0)) 6 | } 7 | -------------------------------------------------------------------------------- /tests/assets/timezone-asia-kolkata.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VTIMEZONE 2 | TZID:Asia/Kolkata 3 | BEGIN:STANDARD 4 | TZOFFSETFROM:+0530 5 | TZOFFSETTO:+0530 6 | TZNAME:IST 7 | DTSTART:19700101T000000 8 | END:STANDARD 9 | END:VTIMEZONE -------------------------------------------------------------------------------- /src/helpers/__mocks__/cryptoHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors 3 | * SPDX-License-Identifier: AGPL-3.0-or-later 4 | */ 5 | 6 | export function randomUUID() { 7 | return 'RANDOM UUID 123' 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | # General 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | [*.{js}] 8 | indent_style = tab 9 | insert_final_newline = true 10 | [Makefile] 11 | indent_style = tab 12 | [*.yml] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /tests/assets/vtodo-no-due-no-dtstart.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Nextcloud Tasks 0.11.3 4 | BEGIN:VTODO 5 | CREATED:20181119T183919 6 | DTSTAMP:20190918T095816 7 | LAST-MODIFIED:20190918T095816 8 | UID:pwen4kz18g 9 | SUMMARY:Test 1 10 | END:VTODO 11 | END:VCALENDAR 12 | -------------------------------------------------------------------------------- /tests/assets/loader.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | /** 4 | * global helper function to load an ics asset by name 5 | * 6 | * @param {string} assetName 7 | * @return {string} 8 | */ 9 | global.getAsset = (assetName) => { 10 | return fs.readFileSync('tests/assets/' + assetName + '.ics', 'UTF8') 11 | } -------------------------------------------------------------------------------- /tests/assets/vtodo-due-only.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Nextcloud Tasks 0.11.3 4 | BEGIN:VTODO 5 | CREATED:20181119T183919 6 | DTSTAMP:20190918T095816 7 | LAST-MODIFIED:20190918T095816 8 | UID:pwen4kz18g 9 | SUMMARY:Calendar 1 - Task 1 10 | PRIORITY:1 11 | DUE:20190101T123400 12 | END:VTODO 13 | END:VCALENDAR 14 | -------------------------------------------------------------------------------- /tests/assets/vtodo-recurring-no-due-no-dtstart-invalid.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Nextcloud Tasks 0.11.3 4 | BEGIN:VTODO 5 | CREATED:20181119T183919 6 | DTSTAMP:20190918T095816 7 | LAST-MODIFIED:20190918T095816 8 | UID:pwen4kz18g 9 | SUMMARY:Test 1 10 | RRULE:FREQ=WEEKLY;INTERVAL=2 11 | END:VTODO 12 | END:VCALENDAR 13 | -------------------------------------------------------------------------------- /vite.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors 3 | * SPDX-License-Identifier: AGPL-3.0-or-later 4 | */ 5 | 6 | import { createLibConfig } from '@nextcloud/vite-config' 7 | 8 | export default createLibConfig({ 9 | index: 'src/index.js', 10 | }, { 11 | libraryFormats: ['es', 'cjs'], 12 | }) 13 | -------------------------------------------------------------------------------- /Limitations.md: -------------------------------------------------------------------------------- 1 | # Limitations of this library 2 | 3 | This library does not provide support for: 4 | - the `EXRULE` property (Deprecated in RFC5545) 5 | - the `RANGE` parameter `THISANDPRIOR` (Deprecated in RFC5545) 6 | - `VALARM` components with the `ACTION` property set to `PROCEDURE` (Deprecated in RFC5545) 7 | - the `RRULE` frequencies `SECONDLY`, `MINUTELY`, `HOURLY` 8 | -------------------------------------------------------------------------------- /tests/assets/simple-date-dtstart-only.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20161004T144433Z 7 | UID:85560E76-1B0D-47E1-A735-21625767FCA4 8 | TRANSP:TRANSPARENT 9 | DTSTART;VALUE=DATE:20161005 10 | DTSTAMP:20161004T144437Z 11 | SEQUENCE:0 12 | END:VEVENT 13 | END:VCALENDAR -------------------------------------------------------------------------------- /tests/assets/simple-date-dtstart-duration.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20161004T144433Z 7 | UID:85560E76-1B0D-47E1-A735-21625767FCA4 8 | DURATION:P10DT0H0M0S 9 | TRANSP:TRANSPARENT 10 | DTSTART;VALUE=DATE:20161005 11 | DTSTAMP:20161004T144437Z 12 | SEQUENCE:0 13 | END:VEVENT 14 | END:VCALENDAR -------------------------------------------------------------------------------- /tests/assets/simple-date-dtstart-dtend.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20161004T144433Z 7 | UID:85560E76-1B0D-47E1-A735-21625767FCA4 8 | DTEND;VALUE=DATE:20161008 9 | TRANSP:TRANSPARENT 10 | DTSTART;VALUE=DATE:20161005 11 | DTSTAMP:20161004T144437Z 12 | SEQUENCE:0 13 | END:VEVENT 14 | END:VCALENDAR -------------------------------------------------------------------------------- /.github/workflows/conventional_commits.yml: -------------------------------------------------------------------------------- 1 | name: Conventional Commits 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | name: Conventional Commits 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 13 | - uses: webiny/action-conventional-commits@8bc41ff4e7d423d56fa4905f6ff79209a78776c7 # v1.3.0 14 | -------------------------------------------------------------------------------- /tests/assets/timezone-america-la.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VTIMEZONE 2 | TZID:America/Los_Angeles 3 | BEGIN:DAYLIGHT 4 | TZOFFSETFROM:-0800 5 | TZOFFSETTO:-0700 6 | TZNAME:PDT 7 | DTSTART:19700308T020000 8 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU 9 | END:DAYLIGHT 10 | BEGIN:STANDARD 11 | TZOFFSETFROM:-0700 12 | TZOFFSETTO:-0800 13 | TZNAME:PST 14 | DTSTART:19701101T020000 15 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU 16 | END:STANDARD 17 | END:VTIMEZONE 18 | -------------------------------------------------------------------------------- /tests/assets/timezone-america-nyc.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VTIMEZONE 2 | TZID:America/New_York 3 | BEGIN:DAYLIGHT 4 | TZOFFSETFROM:-0500 5 | TZOFFSETTO:-0400 6 | TZNAME:EDT 7 | DTSTART:19700308T020000 8 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU 9 | END:DAYLIGHT 10 | BEGIN:STANDARD 11 | TZOFFSETFROM:-0400 12 | TZOFFSETTO:-0500 13 | TZNAME:EST 14 | DTSTART:19701101T020000 15 | RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU 16 | END:STANDARD 17 | END:VTIMEZONE 18 | -------------------------------------------------------------------------------- /tests/assets/timezone-europe-berlin.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VTIMEZONE 2 | TZID:Europe/Berlin 3 | BEGIN:DAYLIGHT 4 | TZOFFSETFROM:+0100 5 | TZOFFSETTO:+0200 6 | TZNAME:CEST 7 | DTSTART:19700329T020000 8 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 9 | END:DAYLIGHT 10 | BEGIN:STANDARD 11 | TZOFFSETFROM:+0200 12 | TZOFFSETTO:+0100 13 | TZNAME:CET 14 | DTSTART:19701025T030000 15 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 16 | END:STANDARD 17 | END:VTIMEZONE 18 | -------------------------------------------------------------------------------- /tests/assets/freebusy.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Example/ExampleCalendarClient//EN 3 | METHOD:REQUEST 4 | VERSION:2.0 5 | BEGIN:VFREEBUSY 6 | ORGANIZER:mailto:a@example.com 7 | ATTENDEE;ROLE=CHAIR:mailto:a@example.com 8 | ATTENDEE:mailto:b@example.com 9 | ATTENDEE:mailto:c@example.com 10 | DTSTAMP:19970613T190000Z 11 | DTSTART:19970701T080000Z 12 | DTEND:19970701T200000 13 | UID:calsrv.example.com-873970198738777@example.com 14 | END:VFREEBUSY 15 | END:VCALENDAR 16 | -------------------------------------------------------------------------------- /tests/assets/recurring-allday.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//IDN nextcloud.com//Calendar app 2.0.2//EN 3 | CALSCALE:GREGORIAN 4 | VERSION:2.0 5 | BEGIN:VEVENT 6 | CREATED:20200401T142357Z 7 | DTSTAMP:20200401T142406Z 8 | LAST-MODIFIED:20200401T142406Z 9 | SEQUENCE:2 10 | UID:4f7a5e63-6ae5-43da-b949-7bad490882c5 11 | DTSTART;VALUE=DATE:20200401 12 | DTEND;VALUE=DATE:20200402 13 | RRULE:FREQ=WEEKLY;BYDAY=WE 14 | SUMMARY:Weekly test 15 | END:VEVENT 16 | END:VCALENDAR 17 | -------------------------------------------------------------------------------- /tests/assets/itip-cancel.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | METHOD:CANCEL 3 | PRODID:-//Example/ExampleCalendarClient//EN 4 | VERSION:2.0 5 | BEGIN:VEVENT 6 | UID:guid-1@example.com 7 | ORGANIZER:mailto:a@example.com 8 | ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:mailto:a@example.com 9 | ATTENDEE:mailto:b@example.com 10 | ATTENDEE:mailto:c@example.com 11 | ATTENDEE:mailto:d@example.com 12 | DTSTAMP:19970721T103000Z 13 | STATUS:CANCELLED 14 | SEQUENCE:3 15 | END:VEVENT 16 | END:VCALENDAR 17 | -------------------------------------------------------------------------------- /tests/assets/illegal-created.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTART;TZID=Europe/Amsterdam:20110104T100000 4 | DTEND;TZID=Europe/Amsterdam:20110104T170000 5 | RRULE:FREQ=WEEKLY;UNTIL=20110126T090000Z;BYDAY=MO,TU,WE,TH,FR 6 | DTSTAMP:20200421T145117Z 7 | UID:asdasdasdasdasdasd@google.com 8 | CREATED:00001231T000000Z 9 | DESCRIPTION: 10 | LAST-MODIFIED:20110124T101158Z 11 | LOCATION: 12 | SEQUENCE:4 13 | STATUS:CONFIRMED 14 | SUMMARY:test summary text 15 | TRANSP:OPAQUE 16 | END:VEVENT 17 | END:VCALENDAR 18 | -------------------------------------------------------------------------------- /tests/assets/illegal-created-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTART;TZID=Europe/Amsterdam:20110104T100000 4 | DTEND;TZID=Europe/Amsterdam:20110104T170000 5 | RRULE:FREQ=WEEKLY;UNTIL=20110126T090000Z;BYDAY=MO,TU,WE,TH,FR 6 | DTSTAMP:20200421T145117Z 7 | UID:asdasdasdasdasdasd@google.com 8 | CREATED:19700101T000000Z 9 | DESCRIPTION: 10 | LAST-MODIFIED:20110124T101158Z 11 | LOCATION: 12 | SEQUENCE:4 13 | STATUS:CONFIRMED 14 | SUMMARY:test summary text 15 | TRANSP:OPAQUE 16 | END:VEVENT 17 | END:VCALENDAR 18 | -------------------------------------------------------------------------------- /tests/assets/import-vevent-vtodo.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | UID:19970610T172345Z-AF23B2@example.com 4 | DTSTAMP:19970610T172345Z 5 | DTSTART:19970714T170000Z 6 | DTEND:19970715T040000Z 7 | SUMMARY:Bastille Day Party 8 | END:VEVENT 9 | BEGIN:VTODO 10 | UID:20070313T123432Z-456553@example.com 11 | DTSTAMP:20070313T123432Z 12 | DUE;VALUE=DATE:20070501 13 | SUMMARY:Submit Quebec Income Tax Return for 2006 14 | CLASS:CONFIDENTIAL 15 | CATEGORIES:FAMILY,FINANCE 16 | STATUS:NEEDS-ACTION 17 | END:VTODO 18 | END:VCALENDAR 19 | -------------------------------------------------------------------------------- /tests/assets/x-nc-group-id-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20170214T113154 7 | DTSTAMP:20170214T113154 8 | LAST-MODIFIED:20170214T113154 9 | UID:05RKYZIUO0OUM7H0A0SPL 10 | SUMMARY:TEST 11 | CLASS:PUBLIC 12 | STATUS:CONFIRMED 13 | DTSTART;TZID=Europe/Berlin:20170214T130000 14 | DTEND;TZID=Europe/Berlin:20170214T140000 15 | BEGIN:VALARM 16 | ACTION:DISPLAY 17 | TRIGGER:-PT15M 18 | END:VALARM 19 | END:VEVENT 20 | END:VCALENDAR 21 | -------------------------------------------------------------------------------- /tests/assets/x-nc-group-id.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20170214T113154 7 | DTSTAMP:20170214T113154 8 | LAST-MODIFIED:20170214T113154 9 | UID:05RKYZIUO0OUM7H0A0SPL 10 | SUMMARY:TEST 11 | CLASS:PUBLIC 12 | STATUS:CONFIRMED 13 | DTSTART;TZID=Europe/Berlin:20170214T130000 14 | DTEND;TZID=Europe/Berlin:20170214T140000 15 | BEGIN:VALARM 16 | ACTION;X-NC-GROUP-ID=0:DISPLAY 17 | TRIGGER:-PT15M 18 | END:VALARM 19 | END:VEVENT 20 | END:VCALENDAR 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | npm-registry: 6 | name: 'Npm registry' 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: write 10 | environment: npm registry 11 | steps: 12 | - name: Conventional nextcloud npm release 13 | uses: ChristophWurst/conventional-nextcloud-npm-release@de5407727bf973c74e8944853c38719aeff6bb8a # v1.0.0 14 | with: 15 | github-token: ${{ secrets.RELEASE_PAT }} 16 | npm-token: ${{ secrets.NPM_TOKEN }} 17 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarConvertInvalidDateTimeValuesRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors 3 | * SPDX-License-Identifier: AGPL-3.0-or-later 4 | */ 5 | 6 | import AbstractRepairStep from '../abstractRepairStep.js' 7 | 8 | export default class ICalendarConvertInvalidDateTimeValuesRepairStep extends AbstractRepairStep { 9 | 10 | /** 11 | * Please see the corresponding test files for an example of broken calendar-data 12 | * 13 | * @inheritDoc 14 | */ 15 | repair(ics) { 16 | return ics 17 | .replace(/^(CREATED|LAST-MODIFIED|DTSTAMP):([0-9]+)$/gm, '$1:$2T000000Z') 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tests/assets/invalid-date-time.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | METHOD:PUBLISH 4 | CALSCALE:GREGORIAN 5 | PRODID:-//WordPress - MECv6.5.6//EN 6 | X-ORIGINAL-URL:https://redacted.de/ 7 | X-WR-CALNAME:REDACTED 8 | X-WR-CALDESC: 9 | REFRESH-INTERVAL;VALUE=DURATION:PT1H 10 | X-PUBLISHED-TTL:PT1H 11 | X-MS-OLK-FORCEINSPECTOROPEN:TRUE 12 | BEGIN:VEVENT 13 | CLASS:PUBLIC 14 | UID:MEC-2a2d16a741cf3a7738ce320021a5e661@redacted.de 15 | DTSTART:20251006T170000Z 16 | DTEND:20251006T183000Z 17 | DTSTAMP:20250710 18 | CREATED:20250710 19 | LAST-MODIFIED:20250710 20 | PRIORITY:5 21 | TRANSP:OPAQUE 22 | SUMMARY:REDACTED 23 | DESCRIPTION:REDACTED 24 | URL:https://redacted.com/some-path 25 | END:VEVENT 26 | -------------------------------------------------------------------------------- /tests/assets/invalid-date-time-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | METHOD:PUBLISH 4 | CALSCALE:GREGORIAN 5 | PRODID:-//WordPress - MECv6.5.6//EN 6 | X-ORIGINAL-URL:https://redacted.de/ 7 | X-WR-CALNAME:REDACTED 8 | X-WR-CALDESC: 9 | REFRESH-INTERVAL;VALUE=DURATION:PT1H 10 | X-PUBLISHED-TTL:PT1H 11 | X-MS-OLK-FORCEINSPECTOROPEN:TRUE 12 | BEGIN:VEVENT 13 | CLASS:PUBLIC 14 | UID:MEC-2a2d16a741cf3a7738ce320021a5e661@redacted.de 15 | DTSTART:20251006T170000Z 16 | DTEND:20251006T183000Z 17 | DTSTAMP:20250710T000000Z 18 | CREATED:20250710T000000Z 19 | LAST-MODIFIED:20250710T000000Z 20 | PRIORITY:5 21 | TRANSP:OPAQUE 22 | SUMMARY:REDACTED 23 | DESCRIPTION:REDACTED 24 | URL:https://redacted.com/some-path 25 | END:VEVENT 26 | -------------------------------------------------------------------------------- /tests/assets/missing-uid.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20161004T144433Z 7 | UID:85560E76-1B0D-47E1-A735-21625767FCA4 8 | TRANSP:TRANSPARENT 9 | DTSTART;VALUE=DATE:20161005 10 | DTSTAMP:20161004T144437Z 11 | SEQUENCE:0 12 | END:VEVENT 13 | BEGIN:VEVENT 14 | CREATED:20161004T144433Z 15 | TRANSP:TRANSPARENT 16 | DTSTART;VALUE=DATE:20161005 17 | DTSTAMP:20161004T144437Z 18 | SEQUENCE:0 19 | END:VEVENT 20 | BEGIN:VEVENT 21 | CREATED:20161004T144433Z 22 | UID:85560E76-1B0D-47E1-A735-21625767FCA45 23 | TRANSP:TRANSPARENT 24 | DTSTART;VALUE=DATE:20161005 25 | DTSTAMP:20161004T144437Z 26 | SEQUENCE:0 27 | END:VEVENT 28 | END:VCALENDAR 29 | -------------------------------------------------------------------------------- /tests/assets/missing-uid-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20161004T144433Z 7 | UID:85560E76-1B0D-47E1-A735-21625767FCA4 8 | TRANSP:TRANSPARENT 9 | DTSTART;VALUE=DATE:20161005 10 | DTSTAMP:20161004T144437Z 11 | SEQUENCE:0 12 | END:VEVENT 13 | BEGIN:VEVENT 14 | UID:RANDOM UUID 123 15 | CREATED:20161004T144433Z 16 | TRANSP:TRANSPARENT 17 | DTSTART;VALUE=DATE:20161005 18 | DTSTAMP:20161004T144437Z 19 | SEQUENCE:0 20 | END:VEVENT 21 | BEGIN:VEVENT 22 | CREATED:20161004T144433Z 23 | UID:85560E76-1B0D-47E1-A735-21625767FCA45 24 | TRANSP:TRANSPARENT 25 | DTSTART;VALUE=DATE:20161005 26 | DTSTAMP:20161004T144437Z 27 | SEQUENCE:0 28 | END:VEVENT 29 | END:VCALENDAR 30 | -------------------------------------------------------------------------------- /tests/assets/double-colon-missing-date-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Tests// 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:GMT+2 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:GMT+1 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20160809T163629Z 24 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 25 | TRANSP:OPAQUE 26 | SUMMARY:Test 27 | DTSTART;VALUE=DATE:20160816 28 | DTSTAMP:20160809T163632Z 29 | SEQUENCE:0 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /tests/assets/double-colon-missing-date.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Tests// 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:GMT+2 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:GMT+1 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20160809T163629Z 24 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 25 | TRANSP:OPAQUE 26 | SUMMARY:Test 27 | DTSTART;TZID=Europe/Berlin:20160816T:: 28 | DTSTAMP:20160809T163632Z 29 | SEQUENCE:0 30 | END:VEVENT 31 | END:VCALENDAR 32 | -------------------------------------------------------------------------------- /tests/assets/very-long-event.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Tests// 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:GMT+2 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:GMT+1 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20160809T163629Z 24 | DTSTAMP:20160809T163629Z 25 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 26 | SUMMARY:TEST 27 | DTSTART;TZID=Europe/Berlin:20200301T150000 28 | DTEND;TZID=Europe/Berlin:20200401T160000 29 | END:VEVENT 30 | END:VCALENDAR 31 | -------------------------------------------------------------------------------- /tests/assets/import-vevent-replace-alias.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTAMP:19980309T231000Z 4 | UID:guid-1.example.com 5 | ORGANIZER:mailto:mrbig@example.com 6 | ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP: 7 | mailto:employee-A@example.com 8 | DESCRIPTION:Project XYZ Review Meeting 9 | CATEGORIES:MEETING 10 | CLASS:PUBLIC 11 | CREATED:19980309T130000Z 12 | SUMMARY:XYZ Project Review 13 | DTSTART;TZID=This_timezone_is_New_York:19980312T083000 14 | DTEND;TZID=America/Los_Angeles:19980312T093000 15 | LOCATION:1CP Conference Room 4350 16 | BEGIN:VALARM 17 | TRIGGER;VALUE=DATE-TIME:19970317T133000Z 18 | REPEAT:4 19 | DURATION:PT15M 20 | ACTION:AUDIO 21 | ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud 22 | END:VALARM 23 | END:VEVENT 24 | END:VCALENDAR 25 | -------------------------------------------------------------------------------- /tests/assets/simple-date-time-new-york-dtstart-only.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:America/New_York 7 | X-LIC-LOCATION:America/New_York 8 | BEGIN:DAYLIGHT 9 | TZOFFSETFROM:-0500 10 | TZOFFSETTO:-0400 11 | TZNAME:EDT 12 | DTSTART:19700308T020000 13 | RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 14 | END:DAYLIGHT 15 | BEGIN:STANDARD 16 | TZOFFSETFROM:-0400 17 | TZOFFSETTO:-0500 18 | TZNAME:EST 19 | DTSTART:19701101T020000 20 | RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 21 | END:STANDARD 22 | END:VTIMEZONE 23 | BEGIN:VEVENT 24 | CREATED:20161002T105635Z 25 | UID:9D0C33D1-334E-4B46-9E0E-D62C11E60700 26 | TRANSP:OPAQUE 27 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 28 | SUMMARY:Event 3 29 | DTSTART;TZID=America/New_York:20161105T235900 30 | DTSTAMP:20161002T105648Z 31 | SEQUENCE:0 32 | END:VEVENT 33 | END:VCALENDAR -------------------------------------------------------------------------------- /tests/assets/simple-date-time-new-york-dtstart-duration.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:America/New_York 7 | X-LIC-LOCATION:America/New_York 8 | BEGIN:DAYLIGHT 9 | TZOFFSETFROM:-0500 10 | TZOFFSETTO:-0400 11 | TZNAME:EDT 12 | DTSTART:19700308T020000 13 | RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 14 | END:DAYLIGHT 15 | BEGIN:STANDARD 16 | TZOFFSETFROM:-0400 17 | TZOFFSETTO:-0500 18 | TZNAME:EST 19 | DTSTART:19701101T020000 20 | RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 21 | END:STANDARD 22 | END:VTIMEZONE 23 | BEGIN:VEVENT 24 | CREATED:20161002T105555Z 25 | UID:C8E094B8-A7E6-4CF3-9E59-58608B9B61C5 26 | TRANSP:OPAQUE 27 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 28 | SUMMARY:Test New York 29 | DTSTART;TZID=America/New_York:20160925T000000 30 | DURATION:P15DT5H0M20S 31 | DTSTAMP:20161002T105633Z 32 | SEQUENCE:0 33 | END:VEVENT 34 | END:VCALENDAR -------------------------------------------------------------------------------- /src/errors/illegalValueError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | export default class IllegalValueError extends Error {} 24 | -------------------------------------------------------------------------------- /src/errors/expectedICalJSError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | export default class ExpectedICalJSError extends Error {} 24 | -------------------------------------------------------------------------------- /src/errors/unknownICALTypeError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | export default class UnknownICALTypeError extends Error {} 24 | -------------------------------------------------------------------------------- /src/errors/modificationNotAllowedError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | export default class ModificationNotAllowedError extends Error {} 24 | -------------------------------------------------------------------------------- /src/errors/recurringWithoutDtStartError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | export default class RecurringWithoutDtStartError extends Error {} 24 | -------------------------------------------------------------------------------- /tests/assets/import-vjournal-vtodo.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VJOURNAL 3 | UID:19970901T130000Z-123405@example.com 4 | DTSTAMP:19970901T130000Z 5 | DTSTART;VALUE=DATE:19970317 6 | SUMMARY:Staff meeting minutes 7 | DESCRIPTION:1. Staff meeting: Participants include Joe\, 8 | Lisa\, and Bob. Aurora project plans were reviewed. 9 | There is currently no budget reserves for this project. 10 | Lisa will escalate to management. Next meeting on Tuesday.\n 11 | 2. Telephone Conference: ABC Corp. sales representative 12 | called to discuss new printer. Promised to get us a demo by 13 | Friday.\n3. Henry Miller (Handsoff Insurance): Car was 14 | totaled by tree. Is looking into a loaner car. 555-2323 15 | (tel). 16 | END:VJOURNAL 17 | BEGIN:VTODO 18 | UID:20070313T123432Z-456553@example.com 19 | DTSTAMP:20070313T123432Z 20 | DUE;VALUE=DATE:20070501 21 | SUMMARY:Submit Quebec Income Tax Return for 2006 22 | CLASS:CONFIDENTIAL 23 | CATEGORIES:FAMILY,FINANCE 24 | STATUS:NEEDS-ACTION 25 | END:VTODO 26 | END:VCALENDAR 27 | -------------------------------------------------------------------------------- /tests/assets/missing-value-date.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20161004T144433Z 7 | UID:85560E76-1B0D-47E1-A735-21625767FCA4 8 | TRANSP:TRANSPARENT 9 | DTSTART;VALUE=DATE:20161005 10 | DTEND;VALUE=DATE:20161006 11 | DTSTAMP:20161004T144437Z 12 | SEQUENCE:0 13 | END:VEVENT 14 | BEGIN:VEVENT 15 | CREATED:20161004T144433Z 16 | UID:85560E76-1B0D-47E1-A735-21625767FCA5 17 | TRANSP:TRANSPARENT 18 | DTSTART;PARAMETER-FOR-PARAMETERS-SAKE:20161005 19 | DTEND:20161006 20 | DTSTAMP:20161004T144437Z 21 | SEQUENCE:0 22 | END:VEVENT 23 | BEGIN:VEVENT 24 | UID:Caldav-7-374 25 | X-SMT-CATEGORY-COLOR:-16743735 26 | CATEGORIES:Personal 27 | LAST-MODIFIED:19700101T000000Z 28 | TRANSP:OPAQUE 29 | DTSTART:20210608T183000Z 30 | DTEND:20210608T210000Z 31 | X-SMT-MISSING-YEAR:0 32 | DTSTAMP:20230503T144306Z 33 | STATUS:CONFIRMED 34 | RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU 35 | EXDATE:20230328 36 | EXDATE:20230418 37 | END:VEVENT 38 | END:VCALENDAR 39 | -------------------------------------------------------------------------------- /src/parameters/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2021 Richard Steinmetz 3 | * 4 | * @author Richard Steinmetz 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import Parameter from './parameter.js' 24 | 25 | export { 26 | Parameter, 27 | } 28 | -------------------------------------------------------------------------------- /tests/unit/helpers/cryptoHelper.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors 3 | * SPDX-License-Identifier: AGPL-3.0-or-later 4 | */ 5 | 6 | import { insecureUuidV4, randomUUID } from '../../../src/helpers/cryptoHelper.js' 7 | 8 | it('randomUUID should return a random UUIDv4', () => { 9 | expect(typeof randomUUID() === 'string').toEqual(true) 10 | for (let i = 0; i < 100; i++) { 11 | expect(randomUUID()) 12 | .toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) 13 | } 14 | }) 15 | 16 | it('randomUUID should use the built-in implementation in secure contexts', () => { 17 | crypto.randomUUID = () => 'RANDOM UUID 123' 18 | expect(randomUUID()).toBe('RANDOM UUID 123') 19 | }) 20 | 21 | it('insecureUuidV4 should return a random UUIDv4', () => { 22 | expect(typeof insecureUuidV4() === 'string').toEqual(true) 23 | for (let i = 0; i < 100; i++) { 24 | expect(insecureUuidV4()) 25 | .toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/) 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /src/recurrence/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2021 Richard Steinmetz 3 | * 4 | * @author Richard Steinmetz 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import RecurrenceManager from './recurrenceManager.js' 24 | 25 | export { 26 | RecurrenceManager, 27 | } 28 | -------------------------------------------------------------------------------- /tests/assets/simple-date-time-europe-berlin-dtstart-dtend.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Tests// 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:GMT+2 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:GMT+1 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20160809T163629Z 24 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 25 | DTEND;TZID=Europe/Berlin:20160816T100000 26 | TRANSP:OPAQUE 27 | SUMMARY:Test Europe Berlin 28 | DTSTART;TZID=Europe/Berlin:20160816T090000 29 | DTSTAMP:20160809T163632Z 30 | SEQUENCE:0 31 | ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Henry Cabot:mailto:hcabot@example.com 32 | ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="mailto:bob@example.com";PARTSTAT=ACCEPTED;CN=Jane Doe:mailto:jdoe@example.com 33 | END:VEVENT 34 | END:VCALENDAR -------------------------------------------------------------------------------- /tests/assets/missing-value-date-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VEVENT 6 | CREATED:20161004T144433Z 7 | UID:85560E76-1B0D-47E1-A735-21625767FCA4 8 | TRANSP:TRANSPARENT 9 | DTSTART;VALUE=DATE:20161005 10 | DTEND;VALUE=DATE:20161006 11 | DTSTAMP:20161004T144437Z 12 | SEQUENCE:0 13 | END:VEVENT 14 | BEGIN:VEVENT 15 | CREATED:20161004T144433Z 16 | UID:85560E76-1B0D-47E1-A735-21625767FCA5 17 | TRANSP:TRANSPARENT 18 | DTSTART;PARAMETER-FOR-PARAMETERS-SAKE;VALUE=DATE:20161005 19 | DTEND;VALUE=DATE:20161006 20 | DTSTAMP:20161004T144437Z 21 | SEQUENCE:0 22 | END:VEVENT 23 | BEGIN:VEVENT 24 | UID:Caldav-7-374 25 | X-SMT-CATEGORY-COLOR:-16743735 26 | CATEGORIES:Personal 27 | LAST-MODIFIED:19700101T000000Z 28 | TRANSP:OPAQUE 29 | DTSTART:20210608T183000Z 30 | DTEND:20210608T210000Z 31 | X-SMT-MISSING-YEAR:0 32 | DTSTAMP:20230503T144306Z 33 | STATUS:CONFIRMED 34 | RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU 35 | EXDATE;VALUE=DATE:20230328 36 | EXDATE;VALUE=DATE:20230418 37 | END:VEVENT 38 | END:VCALENDAR 39 | -------------------------------------------------------------------------------- /src/factories/dateFactory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | /** 24 | * Gets a new DateObject set to now 25 | * 26 | * @return {Date} 27 | */ 28 | export function dateFactory() { 29 | return new Date() 30 | } 31 | -------------------------------------------------------------------------------- /tests/unit/factories/dateFactory.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import { dateFactory } from '../../../src/factories/dateFactory.js'; 23 | 24 | it('DateFactory should return a date object', () => { 25 | expect(dateFactory() instanceof Date).toEqual(true) 26 | }) 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_form.yml: -------------------------------------------------------------------------------- 1 | name: "Feature request" 2 | description: "You have a neat idea that should be implemented?" 3 | labels: ["enhancement", "0. to triage"] 4 | body: 5 | - type: textarea 6 | id: description-problem 7 | attributes: 8 | label: Is your feature request related to a problem? Please describe. 9 | description: | 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | - type: textarea 12 | id: description-solution 13 | attributes: 14 | label: Describe the solution you'd like 15 | description: | 16 | A clear and concise description of what you want to happen. 17 | - type: textarea 18 | id: description-alternatives 19 | attributes: 20 | label: Describe alternatives you've considered 21 | description: | 22 | A clear and concise description of any alternative solutions or features you've considered. 23 | - type: textarea 24 | id: additional-context 25 | attributes: 26 | label: Additional context 27 | description: | 28 | Add any other context or screenshots about the feature request here. 29 | -------------------------------------------------------------------------------- /tests/assets/vtodo-dtstart-duration.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Flexibits Inc./Fantastical for Mac 3.0.9//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | X-LIC-LOCATION:Europe/Berlin 8 | BEGIN:DAYLIGHT 9 | TZNAME:CEST 10 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 11 | DTSTART:20000326T020000 12 | TZOFFSETFROM:+0100 13 | TZOFFSETTO:+0200 14 | END:DAYLIGHT 15 | BEGIN:STANDARD 16 | TZNAME:CET 17 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 18 | DTSTART:20001029T030000 19 | TZOFFSETFROM:+0200 20 | TZOFFSETTO:+0100 21 | END:STANDARD 22 | END:VTIMEZONE 23 | BEGIN:VTODO 24 | SUMMARY:Test 2 25 | STATUS:COMPLETED 26 | DURATION:PT15M 27 | COMPLETED:20200323T092758Z 28 | UID:BC56AD2C-2F85-4B06-A88D-93F4C3E0F9AB 29 | DTSTART;TZID=Europe/Berlin:20200317T090000 30 | DTSTAMP:20200323T092758Z 31 | SEQUENCE:1 32 | PERCENT-COMPLETE:100 33 | CREATED:20200316T103212Z 34 | BEGIN:VALARM 35 | UID:BAB5EEA4-D9FB-4F53-852E-9C04B9B5ADF4 36 | X-WR-ALARMUID:BAB5EEA4-D9FB-4F53-852E-9C04B9B5ADF4 37 | ACTION:DISPLAY 38 | DESCRIPTION:Event reminder 39 | ACKNOWLEDGED:20200317T085055Z 40 | TRIGGER;VALUE=DATE-TIME:20200317T080000Z 41 | END:VALARM 42 | END:VTODO 43 | END:VCALENDAR 44 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarConvertCreatedDateTimeRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors 3 | * SPDX-License-Identifier: AGPL-3.0-or-later 4 | */ 5 | 6 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js' 7 | import ICalendarConvertInvalidDateTimeValuesRepairStep from '../../../../../src/parsers/repairsteps/icalendar/icalendarConvertInvalidDateTimeValuesRepairStep.js' 8 | 9 | it('The repair step should inherit from AbstractRepairStep', () => { 10 | expect((new ICalendarConvertInvalidDateTimeValuesRepairStep() instanceof AbstractRepairStep)) 11 | .toBe(true) 12 | }) 13 | 14 | it('The repair step should have a priority', () => { 15 | expect(ICalendarConvertInvalidDateTimeValuesRepairStep.priority()).toBe(0) 16 | }) 17 | 18 | it('The repair step should repair broken calendar data', () => { 19 | const repairStep = new ICalendarConvertInvalidDateTimeValuesRepairStep() 20 | const brokenICS = getAsset('invalid-date-time') 21 | const fixedICS = getAsset('invalid-date-time-sanitized') 22 | 23 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/unit/values/abstractValue.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractValue from '../../../src/values/abstractValue.js'; 23 | 24 | it('AbstractValue should not be instantiable', () => { 25 | expect(() => { 26 | new AbstractValue({}) 27 | }).toThrow(TypeError, 'Cannot instantiate abstract class AbstractValue'); 28 | }) 29 | -------------------------------------------------------------------------------- /src/parsers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2021 Richard Steinmetz 3 | * 4 | * @author Richard Steinmetz 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import AbstractParser from './abstractParser.js' 24 | import ICalendarParser from './icalendarParser.js' 25 | import ParserManager from './parserManager.js' 26 | 27 | export { 28 | AbstractParser, 29 | ICalendarParser, 30 | ParserManager, 31 | } 32 | -------------------------------------------------------------------------------- /tests/assets/vtodo-due-dtstart.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Flexibits Inc./Fantastical for Mac 3.0.9//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | X-LIC-LOCATION:Europe/Berlin 8 | BEGIN:DAYLIGHT 9 | TZNAME:CEST 10 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 11 | DTSTART:20000326T020000 12 | TZOFFSETFROM:+0100 13 | TZOFFSETTO:+0200 14 | END:DAYLIGHT 15 | BEGIN:STANDARD 16 | TZNAME:CET 17 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 18 | DTSTART:20001029T030000 19 | TZOFFSETFROM:+0200 20 | TZOFFSETTO:+0100 21 | END:STANDARD 22 | END:VTIMEZONE 23 | BEGIN:VTODO 24 | SUMMARY:Test 2 25 | STATUS:COMPLETED 26 | DUE;TZID=Europe/Berlin:20200317T090000 27 | COMPLETED:20200323T092758Z 28 | UID:BC56AD2C-2F85-4B06-A88D-93F4C3E0F9AB 29 | DTSTART;TZID=Europe/Berlin:20200317T090000 30 | DTSTAMP:20200323T092758Z 31 | SEQUENCE:1 32 | PERCENT-COMPLETE:100 33 | CREATED:20200316T103212Z 34 | BEGIN:VALARM 35 | UID:BAB5EEA4-D9FB-4F53-852E-9C04B9B5ADF4 36 | X-WR-ALARMUID:BAB5EEA4-D9FB-4F53-852E-9C04B9B5ADF4 37 | ACTION:DISPLAY 38 | DESCRIPTION:Event reminder 39 | ACKNOWLEDGED:20200317T085055Z 40 | TRIGGER;VALUE=DATE-TIME:20200317T080000Z 41 | END:VALARM 42 | END:VTODO 43 | END:VCALENDAR 44 | -------------------------------------------------------------------------------- /tests/assets/vtodo-recurring-dtstart-duration.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Flexibits Inc./Fantastical for Mac 3.0.9//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | X-LIC-LOCATION:Europe/Berlin 8 | BEGIN:DAYLIGHT 9 | TZNAME:CEST 10 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 11 | DTSTART:20000326T020000 12 | TZOFFSETFROM:+0100 13 | TZOFFSETTO:+0200 14 | END:DAYLIGHT 15 | BEGIN:STANDARD 16 | TZNAME:CET 17 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 18 | DTSTART:20001029T030000 19 | TZOFFSETFROM:+0200 20 | TZOFFSETTO:+0100 21 | END:STANDARD 22 | END:VTIMEZONE 23 | BEGIN:VTODO 24 | SUMMARY:Test 2 25 | STATUS:COMPLETED 26 | DURATION:PT15M 27 | COMPLETED:20200323T092758Z 28 | UID:BC56AD2C-2F85-4B06-A88D-93F4C3E0F9AB 29 | DTSTART;TZID=Europe/Berlin:20200317T090000 30 | DTSTAMP:20200323T092758Z 31 | SEQUENCE:1 32 | PERCENT-COMPLETE:100 33 | CREATED:20200316T103212Z 34 | RRULE:FREQ=WEEKLY;INTERVAL=2 35 | BEGIN:VALARM 36 | UID:BAB5EEA4-D9FB-4F53-852E-9C04B9B5ADF4 37 | X-WR-ALARMUID:BAB5EEA4-D9FB-4F53-852E-9C04B9B5ADF4 38 | ACTION:DISPLAY 39 | DESCRIPTION:Event reminder 40 | ACKNOWLEDGED:20200317T085055Z 41 | TRIGGER;VALUE=DATE-TIME:20200317T080000Z 42 | END:VALARM 43 | END:VTODO 44 | END:VCALENDAR 45 | -------------------------------------------------------------------------------- /tests/assets/vtodo-recurring-due-dtstart.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Flexibits Inc./Fantastical for Mac 3.0.9//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | X-LIC-LOCATION:Europe/Berlin 8 | BEGIN:DAYLIGHT 9 | TZNAME:CEST 10 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 11 | DTSTART:20000326T020000 12 | TZOFFSETFROM:+0100 13 | TZOFFSETTO:+0200 14 | END:DAYLIGHT 15 | BEGIN:STANDARD 16 | TZNAME:CET 17 | RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 18 | DTSTART:20001029T030000 19 | TZOFFSETFROM:+0200 20 | TZOFFSETTO:+0100 21 | END:STANDARD 22 | END:VTIMEZONE 23 | BEGIN:VTODO 24 | SUMMARY:Test 2 25 | STATUS:COMPLETED 26 | DUE;TZID=Europe/Berlin:20200317T090000 27 | COMPLETED:20200323T092758Z 28 | UID:BC56AD2C-2F85-4B06-A88D-93F4C3E0F9AB 29 | DTSTART;TZID=Europe/Berlin:20200317T090000 30 | DTSTAMP:20200323T092758Z 31 | SEQUENCE:1 32 | PERCENT-COMPLETE:100 33 | CREATED:20200316T103212Z 34 | RRULE:FREQ=WEEKLY;INTERVAL=2 35 | BEGIN:VALARM 36 | UID:BAB5EEA4-D9FB-4F53-852E-9C04B9B5ADF4 37 | X-WR-ALARMUID:BAB5EEA4-D9FB-4F53-852E-9C04B9B5ADF4 38 | ACTION:DISPLAY 39 | DESCRIPTION:Event reminder 40 | ACKNOWLEDGED:20200317T085055Z 41 | TRIGGER;VALUE=DATE-TIME:20200317T080000Z 42 | END:VALARM 43 | END:VTODO 44 | END:VCALENDAR 45 | -------------------------------------------------------------------------------- /tests/unit/errors/illegalValueError.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import IllegalValueError from '../../../src/errors/illegalValueError.js'; 23 | 24 | it('IllegalValueError should be defined', () => { 25 | expect(IllegalValueError).toBeDefined() 26 | }) 27 | 28 | it('IllegalValueError should inherit from Error', () => { 29 | expect((new IllegalValueError()) instanceof Error).toBeTruthy() 30 | }) 31 | -------------------------------------------------------------------------------- /src/helpers/cryptoHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors 3 | * SPDX-License-Identifier: AGPL-3.0-or-later 4 | */ 5 | 6 | /** 7 | * Generates a random UUID v4. 8 | * 9 | * @return {string} 10 | */ 11 | export function randomUUID() { 12 | if (crypto?.randomUUID) { 13 | // Only available in secure contexts 14 | return crypto.randomUUID() 15 | } 16 | 17 | return insecureUuidV4() 18 | } 19 | 20 | /** 21 | * Generates a random UUID v4 from a weak, non-cryptographic random number generator. 22 | * Please use randomUUID() instead. 23 | * 24 | * Adapted from https://gist.github.com/scwood/3bff42cc005cc20ab7ec98f0d8e1d59d 25 | * Copyright 2018 Spencer Wood 26 | * 27 | * @return {string} 28 | */ 29 | export function insecureUuidV4() { 30 | const uuid = new Array(36) 31 | for (let i = 0; i < 36; i++) { 32 | uuid[i] = Math.floor(Math.random() * 16) 33 | } 34 | uuid[14] = 4 // set bits 12-15 of time-high-and-version to 0100 35 | uuid[19] = uuid[19] &= ~(1 << 2) // set bit 6 of clock-seq-and-reserved to zero 36 | uuid[19] = uuid[19] |= (1 << 3) // set bit 7 of clock-seq-and-reserved to one 37 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-' 38 | return uuid.map((x) => x.toString(16)).join('') 39 | } 40 | -------------------------------------------------------------------------------- /tests/unit/errors/expectedICalJSError.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import ExpectedICalJSError from '../../../src/errors/expectedICalJSError.js'; 23 | 24 | it('ExpectedICalJSError should be defined', () => { 25 | expect(ExpectedICalJSError).toBeDefined() 26 | }) 27 | 28 | it('ExpectedICalJSError should inherit from Error', () => { 29 | expect((new ExpectedICalJSError()) instanceof Error).toBeTruthy() 30 | }) 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_form.yml: -------------------------------------------------------------------------------- 1 | name: "Bug" 2 | description: "Have you encountered a bug?" 3 | labels: ["bug", "0. to triage"] 4 | body: 5 | - type: textarea 6 | id: reproduce 7 | attributes: 8 | label: Steps to reproduce 9 | description: | 10 | Describe the steps to reproduce the bug. 11 | The better your description is _(go 'here', click 'there'...)_ the fastest you'll get an _(accurate)_ answer. 12 | value: | 13 | 1. 14 | 2. 15 | 3. 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: Expected-behavior 20 | attributes: 21 | label: Expected behavior 22 | description: | 23 | Tell us what should happen 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: actual-behavior 28 | attributes: 29 | label: Actual behavior 30 | description: Tell us what happens instead 31 | validations: 32 | required: true 33 | - type: input 34 | id: library-version 35 | attributes: 36 | label: Library version 37 | - type: textarea 38 | id: additional-info 39 | attributes: 40 | label: Additional info 41 | description: Any additional information related to the issue (ex. browser console errors, software versions). 42 | -------------------------------------------------------------------------------- /tests/assets/rrule-weekly-recurrence-id.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:GMT+2 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:GMT+1 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20161003T140450Z 24 | UID:6D2955B1-5E46-4683-AA11-236D2E8458CE 25 | RRULE:FREQ=WEEKLY;INTERVAL=1 26 | DTEND;TZID=Europe/Berlin:20160928T100000 27 | TRANSP:OPAQUE 28 | SUMMARY:foobar 29 | DTSTART;TZID=Europe/Berlin:20160928T090000 30 | DTSTAMP:20161003T140538Z 31 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 32 | SEQUENCE:0 33 | END:VEVENT 34 | BEGIN:VEVENT 35 | CREATED:20161003T140450Z 36 | UID:6D2955B1-5E46-4683-AA11-236D2E8458CE 37 | DTEND;TZID=Europe/Berlin:20161013T100000 38 | TRANSP:OPAQUE 39 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 40 | SUMMARY:foobar 123 41 | DTSTART;TZID=Europe/Berlin:20161013T090000 42 | DTSTAMP:20161003T140622Z 43 | SEQUENCE:0 44 | RECURRENCE-ID;TZID=Europe/Berlin:20161012T090000 45 | END:VEVENT 46 | END:VCALENDAR -------------------------------------------------------------------------------- /tests/unit/errors/recurringWithoutDtStartError.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import RecurringWithoutDtStart from '../../../src/errors/recurringWithoutDtStartError.js'; 23 | 24 | it('RecurringWithoutDtStart should be defined', () => { 25 | expect(RecurringWithoutDtStart).toBeDefined() 26 | }) 27 | 28 | it('RecurringWithoutDtStart should inherit from Error', () => { 29 | expect((new RecurringWithoutDtStart()) instanceof Error).toBeTruthy() 30 | }) 31 | -------------------------------------------------------------------------------- /tests/unit/errors/modificationNotAllowedError.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import ModificationNotAllowedError from '../../../src/errors/modificationNotAllowedError.js'; 23 | 24 | it('ModificationNotAllowedError should be defined', () => { 25 | expect(ModificationNotAllowedError).toBeDefined() 26 | }) 27 | 28 | it('ModificationNotAllowedError should inherit from Error', () => { 29 | expect((new ModificationNotAllowedError()) instanceof Error).toBeTruthy() 30 | }) 31 | -------------------------------------------------------------------------------- /tests/assets/empty-trigger.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.14.5//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:CEST 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:CET 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20190614T173801Z 24 | UID:AFBFC240-DC5C-49B2-9719-253AF67F7C13 25 | DTEND;TZID=Europe/Berlin:20190611T100000 26 | TRANSP:OPAQUE 27 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 28 | SUMMARY:test 1 29 | LAST-MODIFIED:20190614T192313Z 30 | DTSTAMP:20190614T173803Z 31 | DTSTART;TZID=Europe/Berlin:20190611T090000 32 | SEQUENCE:0 33 | BEGIN:VALARM 34 | UID:C922BD12-7629-48C6-BED6-470472B4B830 35 | TRIGGER:-PT10M 36 | ATTACH;VALUE=URI:Chord 37 | ACTION:AUDIO 38 | END:VALARM 39 | BEGIN:VALARM 40 | UID:C922BD12-7629-48C6-BED6-470472B4B831 41 | TRIGGER:P 42 | ATTACH;VALUE=URI:Chord 43 | ACTION:AUDIO 44 | END:VALARM 45 | BEGIN:VALARM 46 | UID:C922BD12-7629-48C6-BED6-470472B4B832 47 | TRIGGER:-P 48 | ATTACH;VALUE=URI:Chord 49 | ACTION:AUDIO 50 | END:VALARM 51 | END:VEVENT 52 | END:VCALENDAR 53 | -------------------------------------------------------------------------------- /tests/assets/empty-trigger-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.14.5//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:CEST 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:CET 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20190614T173801Z 24 | UID:AFBFC240-DC5C-49B2-9719-253AF67F7C13 25 | DTEND;TZID=Europe/Berlin:20190611T100000 26 | TRANSP:OPAQUE 27 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 28 | SUMMARY:test 1 29 | LAST-MODIFIED:20190614T192313Z 30 | DTSTAMP:20190614T173803Z 31 | DTSTART;TZID=Europe/Berlin:20190611T090000 32 | SEQUENCE:0 33 | BEGIN:VALARM 34 | UID:C922BD12-7629-48C6-BED6-470472B4B830 35 | TRIGGER:-PT10M 36 | ATTACH;VALUE=URI:Chord 37 | ACTION:AUDIO 38 | END:VALARM 39 | BEGIN:VALARM 40 | UID:C922BD12-7629-48C6-BED6-470472B4B831 41 | TRIGGER:P0D 42 | ATTACH;VALUE=URI:Chord 43 | ACTION:AUDIO 44 | END:VALARM 45 | BEGIN:VALARM 46 | UID:C922BD12-7629-48C6-BED6-470472B4B832 47 | TRIGGER:P0D 48 | ATTACH;VALUE=URI:Chord 49 | ACTION:AUDIO 50 | END:VALARM 51 | END:VEVENT 52 | END:VCALENDAR 53 | -------------------------------------------------------------------------------- /tests/unit/recurrence/recurrenceManager.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import RecurrenceManager from '../../../src/recurrence/recurrenceManager.js'; 23 | 24 | it('RecurrenceManager should be defined', () => { 25 | expect(RecurrenceManager).toBeDefined() 26 | }) 27 | 28 | it('RecurrenceManager should expect one parameter', () => { 29 | const masterItem = {} 30 | const recurrenceManager = new RecurrenceManager(masterItem) 31 | 32 | expect(recurrenceManager instanceof RecurrenceManager).toEqual(true) 33 | }) 34 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarIllegalCreatedRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2020 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../abstractRepairStep.js' 23 | 24 | export default class ICalendarIllegalCreatedRepairStep extends AbstractRepairStep { 25 | 26 | /** 27 | * Please see the corresponding test file for an example of broken calendar-data 28 | * 29 | * @inheritDoc 30 | */ 31 | repair(ics) { 32 | return ics 33 | .replace(/^CREATED:00001231T000000Z$/gm, 'CREATED:19700101T000000Z') 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /tests/assets/recurring-recurrence-id-only.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | METHOD:PUBLISH 3 | VERSION:2.0 4 | PRODID:-//Apple Inc.//Mac OS X 10.14.5//EN 5 | CALSCALE:GREGORIAN 6 | BEGIN:VTIMEZONE 7 | TZID:Europe/Berlin 8 | BEGIN:DAYLIGHT 9 | TZOFFSETFROM:+0100 10 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 11 | DTSTART:19810329T020000 12 | TZNAME:CEST 13 | TZOFFSETTO:+0200 14 | END:DAYLIGHT 15 | BEGIN:STANDARD 16 | TZOFFSETFROM:+0200 17 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 18 | DTSTART:19961027T030000 19 | TZNAME:CET 20 | TZOFFSETTO:+0100 21 | END:STANDARD 22 | END:VTIMEZONE 23 | BEGIN:VEVENT 24 | CREATED:20190701T131123Z 25 | UID:24C45485-7943-4A1A-9551-12AD83DF1F6D 26 | DTEND;TZID=Europe/Berlin:20190721T100000 27 | TRANSP:OPAQUE 28 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 29 | SUMMARY:recurring event exception 1 30 | LAST-MODIFIED:20190701T131139Z 31 | DTSTAMP:20190701T131130Z 32 | DTSTART;TZID=Europe/Berlin:20190721T090000 33 | SEQUENCE:0 34 | RECURRENCE-ID;TZID=Europe/Berlin:20190720T090000 35 | END:VEVENT 36 | BEGIN:VEVENT 37 | CREATED:20190703T172822Z 38 | UID:41CBE812-F77C-471A-A481-D6A18CCAAA99 39 | DTEND;TZID=Europe/Berlin:20190719T230000 40 | TRANSP:OPAQUE 41 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 42 | SUMMARY:recurring event exception 2 43 | LAST-MODIFIED:20190703T172914Z 44 | DTSTAMP:20190703T172836Z 45 | DTSTART:20190824T110000Z 46 | SEQUENCE:0 47 | RECURRENCE-ID:20190824T070000Z 48 | END:VEVENT 49 | END:VCALENDAR 50 | -------------------------------------------------------------------------------- /tests/unit/components/nested/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import { getConstructorForComponentName } from '../../../../src/components/nested'; 23 | import AlarmComponent from '../../../../src/components/nested/alarmComponent.js'; 24 | import AbstractComponent from '../../../../src/components/abstractComponent.js'; 25 | 26 | it('should provide a constructor for a given component name', () => { 27 | expect(getConstructorForComponentName('valarm')).toEqual(AlarmComponent) 28 | expect(getConstructorForComponentName('vtodo')).toEqual(AbstractComponent) 29 | }) 30 | -------------------------------------------------------------------------------- /src/errors/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2021 Richard Steinmetz 3 | * 4 | * @author Richard Steinmetz 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import ExpectedICalJSError from './expectedICalJSError.js' 24 | import IllegalValueError from './illegalValueError.js' 25 | import ModificationNotAllowedError from './modificationNotAllowedError.js' 26 | import RecurringWithoutDtStartError from './recurringWithoutDtStartError.js' 27 | import UnknownICALTypeError from './unknownICALTypeError.js' 28 | 29 | export { 30 | ExpectedICalJSError, 31 | IllegalValueError, 32 | ModificationNotAllowedError, 33 | RecurringWithoutDtStartError, 34 | UnknownICALTypeError, 35 | } 36 | -------------------------------------------------------------------------------- /tests/unit/config.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import { deleteConfig, setConfig, getConfig, hasConfig } from '../../src/config.js'; 23 | 24 | it('should provide a global config mechanism', () => { 25 | expect(hasConfig('key1')).toEqual(false) 26 | expect(getConfig('key1', 'defaultValue123')).toEqual('defaultValue123') 27 | 28 | setConfig('key1', 'FOO BAR') 29 | 30 | expect(hasConfig('key1')).toEqual(true) 31 | expect(getConfig('key1', 'defaultValue123')).toEqual('FOO BAR') 32 | 33 | deleteConfig('key1') 34 | 35 | expect(hasConfig('key1')).toEqual(false) 36 | expect(getConfig('key1', 'defaultValue123')).toEqual('defaultValue123') 37 | }) 38 | -------------------------------------------------------------------------------- /tests/assets/weekly-recurring-with-exception.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | METHOD:PUBLISH 3 | VERSION:2.0 4 | X-WR-CALNAME:test 456 5 | PRODID:-//Apple Inc.//Mac OS X 10.14.5//EN 6 | X-APPLE-CALENDAR-COLOR:#30D33B 7 | X-WR-TIMEZONE:Europe/Berlin 8 | CALSCALE:GREGORIAN 9 | BEGIN:VTIMEZONE 10 | TZID:Europe/Berlin 11 | BEGIN:DAYLIGHT 12 | TZOFFSETFROM:+0100 13 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 14 | DTSTART:19810329T020000 15 | TZNAME:CEST 16 | TZOFFSETTO:+0200 17 | END:DAYLIGHT 18 | BEGIN:STANDARD 19 | TZOFFSETFROM:+0200 20 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 21 | DTSTART:19961027T030000 22 | TZNAME:CET 23 | TZOFFSETTO:+0100 24 | END:STANDARD 25 | END:VTIMEZONE 26 | BEGIN:VEVENT 27 | TRANSP:OPAQUE 28 | DTEND;TZID=Europe/Berlin:20190706T100000 29 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 30 | UID:24C45485-7943-4A1A-9551-12AD83DF1F6D 31 | DTSTAMP:20190701T131130Z 32 | SEQUENCE:0 33 | SUMMARY:Recurring event 34 | LAST-MODIFIED:20190701T131135Z 35 | DTSTART;TZID=Europe/Berlin:20190706T090000 36 | CREATED:20190701T131123Z 37 | RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=42 38 | END:VEVENT 39 | BEGIN:VEVENT 40 | CREATED:20190701T131123Z 41 | UID:24C45485-7943-4A1A-9551-12AD83DF1F6D 42 | DTEND;TZID=Europe/Berlin:20190725T100000 43 | TRANSP:OPAQUE 44 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 45 | SUMMARY:Recurring event 123 this is modified 46 | LAST-MODIFIED:20190701T131139Z 47 | DTSTAMP:20190701T131130Z 48 | DTSTART;TZID=Europe/Berlin:20190721T090000 49 | SEQUENCE:0 50 | RECURRENCE-ID;TZID=Europe/Berlin:20190720T090000 51 | END:VEVENT 52 | END:VCALENDAR 53 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarEmptyTriggerRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../abstractRepairStep.js' 23 | 24 | /** 25 | * @class ICalendarEmptyTriggerRepairStep 26 | * @classdesc This repair step fixes malformed TRIGGER properties 27 | */ 28 | export default class ICalendarEmptyTriggerRepairStep extends AbstractRepairStep { 29 | 30 | /** 31 | * Please see the corresponding test file for an example of broken calendar-data 32 | * 33 | * @inheritDoc 34 | */ 35 | repair(ics) { 36 | return ics 37 | .replace(/^TRIGGER(:|;.*)-P$/gm, 'TRIGGER$1P0D') 38 | .replace(/^TRIGGER(:|;.*)P$/gm, 'TRIGGER$1P0D') 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarRemoveXNCGroupIdRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../abstractRepairStep.js' 23 | 24 | /** 25 | * @class ICalendarRemoveXNCGroupIdRepairStep 26 | * @classdesc This repair step removes the X-NC-GroupID parameter used in previous versions of Nextcloud 27 | */ 28 | export default class ICalendarRemoveXNCGroupIdRepairStep extends AbstractRepairStep { 29 | 30 | /** 31 | * Please see the corresponding test file for an example of broken calendar-data 32 | * 33 | * @inheritDoc 34 | */ 35 | repair(ics) { 36 | return ics 37 | .replace(/(^.*)(;X-NC-GROUP-ID=\d+)(:.*$)/gm, '$1$3') 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarRemoveUnicodeSpecialNoncharactersRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2024 Sanskar Soni 3 | * 4 | * @author Sanskar Soni 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../abstractRepairStep.js' 23 | 24 | /** 25 | * @class ICalendarRemoveUnicodeSpecialNoncharactersRepairStep 26 | * @classdesc This repair step removes Unicode specials non-characters i.e. U+FFFE & U+FFFF 27 | */ 28 | export default class ICalendarRemoveUnicodeSpecialNoncharactersRepairStep extends AbstractRepairStep { 29 | 30 | /** 31 | * Please see the corresponding test file for an example of broken calendar-data 32 | * 33 | * @inheritDoc 34 | */ 35 | repair(ics) { 36 | return ics 37 | .replace(/(\uFFFF|\uFFFE)/g, '') 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /tests/assets/vtodo-recurring-with-recurrence-exceptions.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | METHOD:PUBLISH 3 | VERSION:2.0 4 | PRODID:-//Nextcloud Tasks 0.11.3 5 | X-WR-CALNAME:test 456 6 | PRODID:-//Nextcloud Tasks 0.11.3 7 | X-APPLE-CALENDAR-COLOR:#30D33B 8 | X-WR-TIMEZONE:Europe/Berlin 9 | CALSCALE:GREGORIAN 10 | BEGIN:VTIMEZONE 11 | TZID:Europe/Berlin 12 | BEGIN:DAYLIGHT 13 | TZOFFSETFROM:+0100 14 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 15 | DTSTART:19810329T020000 16 | TZNAME:CEST 17 | TZOFFSETTO:+0200 18 | END:DAYLIGHT 19 | BEGIN:STANDARD 20 | TZOFFSETFROM:+0200 21 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 22 | DTSTART:19961027T030000 23 | TZNAME:CET 24 | TZOFFSETTO:+0100 25 | END:STANDARD 26 | END:VTIMEZONE 27 | BEGIN:VTODO 28 | TRANSP:OPAQUE 29 | DUE;TZID=Europe/Berlin:20190706T100000 30 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 31 | UID:24C45485-7943-4A1A-9551-12AD83DF1F6D 32 | DTSTAMP:20190701T131130Z 33 | SEQUENCE:0 34 | SUMMARY:Recurring task 35 | LAST-MODIFIED:20190701T131135Z 36 | DTSTART;TZID=Europe/Berlin:20190706T100000 37 | CREATED:20190701T131123Z 38 | RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=42 39 | END:VTODO 40 | BEGIN:VTODO 41 | CREATED:20190701T131123Z 42 | UID:24C45485-7943-4A1A-9551-12AD83DF1F6D 43 | DUE;TZID=Europe/Berlin:20190725T100000 44 | TRANSP:OPAQUE 45 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 46 | SUMMARY:Recurring task 123 this is modified 47 | LAST-MODIFIED:20190701T131139Z 48 | DTSTAMP:20190701T131130Z 49 | DTSTART;TZID=Europe/Berlin:20190725T100000 50 | SEQUENCE:0 51 | RECURRENCE-ID;TZID=Europe/Berlin:20190720T100000 52 | END:VTODO 53 | END:VCALENDAR 54 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarAddMissingValueDateDoubleColonRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../abstractRepairStep.js' 23 | 24 | /** 25 | * @class ICalendarAddMissingValueDateDoubleColonRepairStep 26 | */ 27 | export default class ICalendarAddMissingValueDateDoubleColonRepairStep extends AbstractRepairStep { 28 | 29 | /** 30 | * Please see the corresponding test file for an example of broken calendar-data 31 | * 32 | * @inheritDoc 33 | */ 34 | repair(ics) { 35 | return ics 36 | .replace(/^(DTSTART|DTEND)(.*):([0-9]{8})T(::)$/gm, (match, propName, parameters, date) => { 37 | return propName + ';VALUE=DATE:' + date 38 | }) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarAddMissingValueDateRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../abstractRepairStep.js' 23 | 24 | /** 25 | * @class ICalendarAddMissingValueDateRepairStep 26 | */ 27 | export default class ICalendarAddMissingValueDateRepairStep extends AbstractRepairStep { 28 | 29 | /** 30 | * Please see the corresponding test file for an example of broken calendar-data 31 | * 32 | * @inheritDoc 33 | */ 34 | repair(ics) { 35 | return ics 36 | .replace(/^(DTSTART|DTEND|EXDATE)(((?!VALUE=DATE).)*):([0-9]{8})$/gm, (match, propName, parameters, _, date) => { 37 | return propName + parameters + ';VALUE=DATE:' + date 38 | }) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/components/nested/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractComponent from '../abstractComponent.js' 23 | import AlarmComponent from './alarmComponent.js' 24 | import { uc } from '../../helpers/stringHelper.js' 25 | 26 | /** 27 | * Gets the constructor for a component name 28 | * This will only return a constructor for components, 29 | * that can be nested inside other ones 30 | * 31 | * @param {string} compName - Component name to get default constructor for 32 | * @return {AlarmComponent|AbstractComponent} 33 | */ 34 | export function getConstructorForComponentName(compName) { 35 | switch (uc(compName)) { 36 | case 'VALARM': 37 | return AlarmComponent 38 | 39 | default: 40 | return AbstractComponent 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/abstractRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | /** 24 | * @class AbstractRepairStep 25 | * @classdesc A repair step is used to fix calendar-data before it is parsed 26 | */ 27 | export default class AbstractRepairStep { 28 | 29 | /** 30 | * @class 31 | */ 32 | constructor() { 33 | if (new.target === AbstractRepairStep) { 34 | throw new TypeError('Cannot instantiate abstract class AbstractRepairStep') 35 | } 36 | } 37 | 38 | /** 39 | * @param {string} input String representation of the data to repair 40 | */ 41 | repair(input) { 42 | throw new TypeError('Abstract method not implemented by subclass') 43 | } 44 | 45 | /** 46 | * @return {number} 47 | */ 48 | static priority() { 49 | return 0 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /tests/assets/multiple-vcalendar-blocks-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.14.5//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:CEST 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:CET 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20190614T173801Z 24 | UID:AFBFC240-DC5C-49B2-9719-253AF67F7C13 25 | DTEND;TZID=Europe/Berlin:20190611T100000 26 | TRANSP:OPAQUE 27 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 28 | SUMMARY:test 1 29 | LAST-MODIFIED:20190614T173803Z 30 | DTSTAMP:20190614T173803Z 31 | DTSTART;TZID=Europe/Berlin:20190611T090000 32 | SEQUENCE:0 33 | END:VEVENT 34 | BEGIN:VEVENT 35 | CREATED:20190614T173804Z 36 | UID:36E1C951-CC6A-4E20-A17B-4411B9B81F94 37 | DTEND;TZID=Europe/Berlin:20190619T100000 38 | TRANSP:OPAQUE 39 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 40 | SUMMARY:test 2 41 | LAST-MODIFIED:20190614T173806Z 42 | DTSTAMP:20190614T173806Z 43 | DTSTART;TZID=Europe/Berlin:20190619T090000 44 | SEQUENCE:0 45 | END:VEVENT 46 | BEGIN:VEVENT 47 | CREATED:20190614T173807Z 48 | UID:294D1235-35EC-4689-BE92-00C9486F2274 49 | DTEND;TZID=Europe/Berlin:20190627T100000 50 | TRANSP:OPAQUE 51 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 52 | SUMMARY:test 3 53 | LAST-MODIFIED:20190614T173808Z 54 | DTSTAMP:20190614T173808Z 55 | DTSTART;TZID=Europe/Berlin:20190627T090000 56 | SEQUENCE:0 57 | END:VEVENT 58 | END:VCALENDAR 59 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-approve-merge.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | # 6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-License-Identifier: MIT 8 | 9 | name: Dependabot 10 | 11 | on: 12 | pull_request_target: 13 | branches: 14 | - main 15 | - master 16 | - stable* 17 | 18 | permissions: 19 | contents: read 20 | 21 | concurrency: 22 | group: dependabot-approve-merge-${{ github.head_ref || github.run_id }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | auto-approve-merge: 27 | if: github.actor == 'dependabot[bot]' || github.actor == 'renovate[bot]' 28 | runs-on: ubuntu-latest-low 29 | permissions: 30 | # for hmarr/auto-approve-action to approve PRs 31 | pull-requests: write 32 | 33 | steps: 34 | - name: Disabled on forks 35 | if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} 36 | run: | 37 | echo 'Can not approve PRs from forks' 38 | exit 1 39 | 40 | # GitHub actions bot approve 41 | - uses: hmarr/auto-approve-action@b40d6c9ed2fa10c9a2749eca7eb004418a705501 # v2 42 | with: 43 | github-token: ${{ secrets.GITHUB_TOKEN }} 44 | 45 | # Nextcloud bot approve and merge request 46 | - uses: ahmadnassri/action-dependabot-auto-merge@45fc124d949b19b6b8bf6645b6c9d55f4f9ac61a # v2 47 | with: 48 | target: minor 49 | github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }} 50 | -------------------------------------------------------------------------------- /tests/assets/unicode-non-character-fffe-after.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | CALSCALE:GREGORIAN 4 | PRODID:-//SabreDAV//SabreDAV//EN 5 | X-WR-CALNAME:41424_Sigharting (Webportal Plugin) 6 | REFRESH-INTERVAL;VALUE=DURATION:PT4H 7 | X-PUBLISHED-TTL:PT4H 8 | BEGIN:VTIMEZONE 9 | TZID:Europe/Vienna 10 | BEGIN:DAYLIGHT 11 | TZOFFSETFROM:+0100 12 | TZOFFSETTO:+0200 13 | TZNAME:CEST 14 | DTSTART:19700329T020000 15 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 16 | END:DAYLIGHT 17 | BEGIN:STANDARD 18 | TZOFFSETFROM:+0200 19 | TZOFFSETTO:+0100 20 | TZNAME:CET 21 | DTSTART:19701025T030000 22 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 23 | END:STANDARD 24 | END:VTIMEZONE 25 | BEGIN:VEVENT 26 | CREATED:20230405T070540Z 27 | DTSTAMP:20230614T183802Z 28 | LAST-MODIFIED:20230614T183802Z 29 | SEQUENCE:6 30 | UID:0e85e9f1-8e56-4581-b4d6-04d865152898 31 | DTSTART;TZID=Europe/Vienna:20230613T083000 32 | DTEND;TZID=Europe/Vienna:20230613T113000 33 | STATUS:CONFIRMED 34 | SUMMARY:TEST1 35 | LOCATION:Church 36 | DESCRIPTION:test spacew 37 | allfahrt  desende. 38 | END:VEVENT 39 | BEGIN:VEVENT 40 | CREATED:20240603T130712Z 41 | DTSTAMP:20240603T130749Z 42 | LAST-MODIFIED:20240603T130749Z 43 | SEQUENCE:3 44 | UID:1af6c9e2-76be-4e2d-80f5-90bc1b41a84e 45 | DTSTART;VALUE=DATE:20241020 46 | DTEND;VALUE=DATE:20241021 47 | STATUS:CONFIRMED 48 | DESCRIPTION:Mozarts "Die Zauberflöte" erlebt ... enntnisse. Diim text neme 49 | lt liebenswerten ääääts überzeugt mit ergreidddszinie 50 | ltert durch ihre zeitlosen Themen von Liebe\, Freundschaft \nu 51 | nd Selbstfindung\, wodurch sie zu einer der beliebtesten \nund meistgespie 52 | lten Opern aller Zeiten avanciert ist.\n 53 | SUMMARY:Zauberflöte 54 | END:VEVENT 55 | END:VCALENDAR 56 | -------------------------------------------------------------------------------- /tests/assets/unicode-non-character-ffff-after.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | CALSCALE:GREGORIAN 4 | PRODID:-//SabreDAV//SabreDAV//EN 5 | X-WR-CALNAME:41424_Sigharting (Webportal Plugin) 6 | REFRESH-INTERVAL;VALUE=DURATION:PT4H 7 | X-PUBLISHED-TTL:PT4H 8 | BEGIN:VTIMEZONE 9 | TZID:Europe/Vienna 10 | BEGIN:DAYLIGHT 11 | TZOFFSETFROM:+0100 12 | TZOFFSETTO:+0200 13 | TZNAME:CEST 14 | DTSTART:19700329T020000 15 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 16 | END:DAYLIGHT 17 | BEGIN:STANDARD 18 | TZOFFSETFROM:+0200 19 | TZOFFSETTO:+0100 20 | TZNAME:CET 21 | DTSTART:19701025T030000 22 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 23 | END:STANDARD 24 | END:VTIMEZONE 25 | BEGIN:VEVENT 26 | CREATED:20230405T070540Z 27 | DTSTAMP:20230614T183802Z 28 | LAST-MODIFIED:20230614T183802Z 29 | SEQUENCE:6 30 | UID:0e85e9f1-8e56-4581-b4d6-04d865152898 31 | DTSTART;TZID=Europe/Vienna:20230613T083000 32 | DTEND;TZID=Europe/Vienna:20230613T113000 33 | STATUS:CONFIRMED 34 | SUMMARY:TEST1 35 | LOCATION:Church 36 | DESCRIPTION:test spacew 37 | allfahrt  desende. 38 | END:VEVENT 39 | BEGIN:VEVENT 40 | CREATED:20240603T130712Z 41 | DTSTAMP:20240603T130749Z 42 | LAST-MODIFIED:20240603T130749Z 43 | SEQUENCE:3 44 | UID:1af6c9e2-76be-4e2d-80f5-90bc1b41a84e 45 | DTSTART;VALUE=DATE:20241020 46 | DTEND;VALUE=DATE:20241021 47 | STATUS:CONFIRMED 48 | DESCRIPTION:Mozarts "Die Zauberflöte" erlebt ... enntnisse. Diim text neme 49 | lt liebenswerten ääääts überzeugt mit ergreidddszinie 50 | ltert durch ihre zeitlosen Themen von Liebe\, Freundschaft \nu 51 | nd Selbstfindung\, wodurch sie zu einer der beliebtesten \nund meistgespie 52 | lten Opern aller Zeiten avanciert ist.\n 53 | SUMMARY:Zauberflöte 54 | END:VEVENT 55 | END:VCALENDAR 56 | -------------------------------------------------------------------------------- /tests/assets/unicode-non-character-fffe-before.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | CALSCALE:GREGORIAN 4 | PRODID:-//SabreDAV//SabreDAV//EN 5 | X-WR-CALNAME:41424_Sigharting (Webportal Plugin) 6 | REFRESH-INTERVAL;VALUE=DURATION:PT4H 7 | X-PUBLISHED-TTL:PT4H 8 | BEGIN:VTIMEZONE 9 | TZID:Europe/Vienna 10 | BEGIN:DAYLIGHT 11 | TZOFFSETFROM:+0100 12 | TZOFFSETTO:+0200 13 | TZNAME:CEST 14 | DTSTART:19700329T020000 15 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 16 | END:DAYLIGHT 17 | BEGIN:STANDARD 18 | TZOFFSETFROM:+0200 19 | TZOFFSETTO:+0100 20 | TZNAME:CET 21 | DTSTART:19701025T030000 22 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 23 | END:STANDARD 24 | END:VTIMEZONE 25 | BEGIN:VEVENT 26 | CREATED:20230405T070540Z 27 | DTSTAMP:20230614T183802Z 28 | LAST-MODIFIED:20230614T183802Z 29 | SEQUENCE:6 30 | UID:0e85e9f1-8e56-4581-b4d6-04d865152898 31 | DTSTART;TZID=Europe/Vienna:20230613T083000 32 | DTEND;TZID=Europe/Vienna:20230613T113000 33 | STATUS:CONFIRMED 34 | SUMMARY:TEST1 35 | LOCATION:Church 36 | DESCRIPTION:test spacew 37 | allfahrt  desende. 38 | END:VEVENT 39 | BEGIN:VEVENT 40 | CREATED:20240603T130712Z 41 | DTSTAMP:20240603T130749Z 42 | LAST-MODIFIED:20240603T130749Z 43 | SEQUENCE:3 44 | UID:1af6c9e2-76be-4e2d-80f5-90bc1b41a84e 45 | DTSTART;VALUE=DATE:20241020 46 | DTEND;VALUE=DATE:20241021 47 | STATUS:CONFIRMED 48 | DESCRIPTION:Mozarts "Die Zauberflöte" erlebt ... enntnis￾se. Diim text neme 49 | lt lie￾benswerten ääääts über￾zeugt mit ergreidddszi￾nie 50 | ltert durch ihre zeitlosen Themen von Liebe\, Freundschaft \nu 51 | nd Selbstfindung\, wodurch sie zu einer der beliebtesten \nund meistgespie 52 | lten Opern aller Zeiten avanciert ist.\n 53 | SUMMARY:Zauberflöte 54 | END:VEVENT 55 | END:VCALENDAR 56 | -------------------------------------------------------------------------------- /tests/assets/unicode-non-character-ffff-before.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | CALSCALE:GREGORIAN 4 | PRODID:-//SabreDAV//SabreDAV//EN 5 | X-WR-CALNAME:41424_Sigharting (Webportal Plugin) 6 | REFRESH-INTERVAL;VALUE=DURATION:PT4H 7 | X-PUBLISHED-TTL:PT4H 8 | BEGIN:VTIMEZONE 9 | TZID:Europe/Vienna 10 | BEGIN:DAYLIGHT 11 | TZOFFSETFROM:+0100 12 | TZOFFSETTO:+0200 13 | TZNAME:CEST 14 | DTSTART:19700329T020000 15 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 16 | END:DAYLIGHT 17 | BEGIN:STANDARD 18 | TZOFFSETFROM:+0200 19 | TZOFFSETTO:+0100 20 | TZNAME:CET 21 | DTSTART:19701025T030000 22 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 23 | END:STANDARD 24 | END:VTIMEZONE 25 | BEGIN:VEVENT 26 | CREATED:20230405T070540Z 27 | DTSTAMP:20230614T183802Z 28 | LAST-MODIFIED:20230614T183802Z 29 | SEQUENCE:6 30 | UID:0e85e9f1-8e56-4581-b4d6-04d865152898 31 | DTSTART;TZID=Europe/Vienna:20230613T083000 32 | DTEND;TZID=Europe/Vienna:20230613T113000 33 | STATUS:CONFIRMED 34 | SUMMARY:TEST1 35 | LOCATION:Church 36 | DESCRIPTION:test spacew 37 | allfahrt  desende. 38 | END:VEVENT 39 | BEGIN:VEVENT 40 | CREATED:20240603T130712Z 41 | DTSTAMP:20240603T130749Z 42 | LAST-MODIFIED:20240603T130749Z 43 | SEQUENCE:3 44 | UID:1af6c9e2-76be-4e2d-80f5-90bc1b41a84e 45 | DTSTART;VALUE=DATE:20241020 46 | DTEND;VALUE=DATE:20241021 47 | STATUS:CONFIRMED 48 | DESCRIPTION:Mozarts "Die Zauberflöte" erlebt ... enntnis￿se. Diim text neme 49 | lt lie￿benswerten ääääts über￿zeugt mit ergreidddszi￿nie 50 | ltert durch ihre zeitlosen Themen von Liebe\, Freundschaft \nu 51 | nd Selbstfindung\, wodurch sie zu einer der beliebtesten \nund meistgespie 52 | lten Opern aller Zeiten avanciert ist.\n 53 | SUMMARY:Zauberflöte 54 | END:VEVENT 55 | END:VCALENDAR 56 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/abstractRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../../../../src/parsers/repairsteps/abstractRepairStep.js'; 23 | 24 | it('AbstractRepairStep should not be instantiable', () => { 25 | expect(() => { 26 | new AbstractRepairStep({}) 27 | }).toThrow(TypeError, 'Cannot instantiate abstract class AbstractValue'); 28 | }) 29 | 30 | it('AbstractRepairStep should force subclasses to implement repair', () => { 31 | class TestRepairStep extends AbstractRepairStep {} 32 | 33 | const test = new TestRepairStep() 34 | expect(() => { 35 | test.repair('') 36 | }).toThrow(TypeError, 'Abstract method not implemented by subclass'); 37 | }) 38 | 39 | it('AbstractRepairStep should include a default priority of zero', () => { 40 | expect(AbstractRepairStep.priority()).toEqual(0) 41 | }) 42 | -------------------------------------------------------------------------------- /tests/integration/parser/isAllDay.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2020 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import { parseICSAndGetAllOccurrencesBetween } from '../../../src/index.js'; 24 | import DateTimeValue from '../../../src/values/dateTimeValue.js' 25 | 26 | jest.mock('../../../src/factories/dateFactory.js') 27 | 28 | it('parseICSAndGetAllOccurrencesBetween should work for events with DTSTART only', () => { 29 | // This test makes sure we don't break https://github.com/nextcloud/calendar/issues/1899 again 30 | // It was caused by an infinite loop of isAllDay and get endDate 31 | const ics = getAsset('dtstart-only-is-all-day') 32 | const start = DateTimeValue.fromJSDate(new Date(Date.UTC(2010, 0, 1, 0, 0, 0))) 33 | const end = DateTimeValue.fromJSDate(new Date(Date.UTC(2022, 11, 31, 23, 59, 59))) 34 | 35 | // Just verify that it doesn't error 36 | parseICSAndGetAllOccurrencesBetween(ics, start, end) 37 | }) 38 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2021 Richard Steinmetz 3 | * 4 | * @author Richard Steinmetz 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import AbstractComponent from './abstractComponent.js' 24 | import CalendarComponent from './calendarComponent.js' 25 | import AlarmComponent from './nested/alarmComponent.js' 26 | import AbstractRecurringComponent from './root/abstractRecurringComponent.js' 27 | import EventComponent from './root/eventComponent.js' 28 | import FreeBusyComponent from './root/freeBusyComponent.js' 29 | import JournalComponent from './root/journalComponent.js' 30 | import TimezoneComponent from './root/timezoneComponent.js' 31 | import ToDoComponent from './root/toDoComponent.js' 32 | 33 | export { 34 | AbstractComponent, 35 | CalendarComponent, 36 | AlarmComponent, 37 | AbstractRecurringComponent, 38 | EventComponent, 39 | FreeBusyComponent, 40 | JournalComponent, 41 | TimezoneComponent, 42 | ToDoComponent, 43 | } 44 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarAddMissingUIDRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../abstractRepairStep.js' 23 | import { randomUUID } from '../../../helpers/cryptoHelper.js' 24 | 25 | /** 26 | * @class ICalendarAddMissingUIDRepairStep 27 | */ 28 | export default class ICalendarAddMissingUIDRepairStep extends AbstractRepairStep { 29 | 30 | /** 31 | * Please see the corresponding test file for an example of broken calendar-data 32 | * 33 | * @inheritDoc 34 | */ 35 | repair(ics) { 36 | return ics 37 | .replace(/^BEGIN:(VEVENT|VTODO|VJOURNAL)$(((?!^END:(VEVENT|VTODO|VJOURNAL)$)(?!^UID.*$)(.|\n))*)^END:(VEVENT|VTODO|VJOURNAL)$\n/gm, (match, vobjectName, vObjectBlock) => { 38 | return 'BEGIN:' + vobjectName + '\r\n' 39 | + 'UID:' + randomUUID() 40 | + vObjectBlock 41 | + 'END:' + vobjectName + '\r\n' 42 | }) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /tests/assets/empty-trigger-with-parameters.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | METHOD:REQUEST 3 | PRODID:Microsoft Exchange Server 2010 4 | VERSION:2.0 5 | BEGIN:VTIMEZONE 6 | TZID:W. Europe Standard Time 7 | BEGIN:STANDARD 8 | DTSTART:16010101T030000 9 | TZOFFSETFROM:+0200 10 | TZOFFSETTO:+0100 11 | RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 12 | END:STANDARD 13 | BEGIN:DAYLIGHT 14 | DTSTART:16010101T020000 15 | TZOFFSETFROM:+0100 16 | TZOFFSETTO:+0200 17 | RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 18 | END:DAYLIGHT 19 | END:VTIMEZONE 20 | BEGIN:VEVENT 21 | ORGANIZER;CN=Bob:mailto:bob@example.org 22 | ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=alice@example.org:mailto:alice@example.org 23 | DESCRIPTION;LANGUAGE=en-US:\n 24 | UID:100020003000 25 | SUMMARY;LANGUAGE=en-US:Remind me test 26 | DTSTART;TZID=W. Europe Standard Time:20250219T080000 27 | DTEND;TZID=W. Europe Standard Time:20250219T083000 28 | CLASS:PUBLIC 29 | PRIORITY:5 30 | DTSTAMP:20250211T161355Z 31 | TRANSP:OPAQUE 32 | STATUS:CONFIRMED 33 | SEQUENCE:0 34 | LOCATION;LANGUAGE=en-US: 35 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 36 | X-MICROSOFT-CDO-OWNERAPPTID:2123520737 37 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 38 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 39 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 40 | X-MICROSOFT-CDO-IMPORTANCE:1 41 | X-MICROSOFT-CDO-INSTTYPE:0 42 | X-MICROSOFT-DONOTFORWARDMEETING:FALSE 43 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 44 | X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT 45 | X-MICROSOFT-ISRESPONSEREQUESTED:TRUE 46 | X-MICROSOFT-LOCATIONS:[] 47 | BEGIN:VALARM 48 | DESCRIPTION:REMINDER 49 | TRIGGER;RELATED=START:P 50 | ACTION:DISPLAY 51 | END:VALARM 52 | BEGIN:VALARM 53 | DESCRIPTION:REMINDER 54 | TRIGGER;RELATED=START:-P 55 | ACTION:DISPLAY 56 | END:VALARM 57 | END:VEVENT 58 | END:VCALENDAR 59 | -------------------------------------------------------------------------------- /tests/assets/empty-trigger-with-parameters-sanitized.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | METHOD:REQUEST 3 | PRODID:Microsoft Exchange Server 2010 4 | VERSION:2.0 5 | BEGIN:VTIMEZONE 6 | TZID:W. Europe Standard Time 7 | BEGIN:STANDARD 8 | DTSTART:16010101T030000 9 | TZOFFSETFROM:+0200 10 | TZOFFSETTO:+0100 11 | RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10 12 | END:STANDARD 13 | BEGIN:DAYLIGHT 14 | DTSTART:16010101T020000 15 | TZOFFSETFROM:+0100 16 | TZOFFSETTO:+0200 17 | RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 18 | END:DAYLIGHT 19 | END:VTIMEZONE 20 | BEGIN:VEVENT 21 | ORGANIZER;CN=Bob:mailto:bob@example.org 22 | ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=alice@example.org:mailto:alice@example.org 23 | DESCRIPTION;LANGUAGE=en-US:\n 24 | UID:100020003000 25 | SUMMARY;LANGUAGE=en-US:Remind me test 26 | DTSTART;TZID=W. Europe Standard Time:20250219T080000 27 | DTEND;TZID=W. Europe Standard Time:20250219T083000 28 | CLASS:PUBLIC 29 | PRIORITY:5 30 | DTSTAMP:20250211T161355Z 31 | TRANSP:OPAQUE 32 | STATUS:CONFIRMED 33 | SEQUENCE:0 34 | LOCATION;LANGUAGE=en-US: 35 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 36 | X-MICROSOFT-CDO-OWNERAPPTID:2123520737 37 | X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE 38 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 39 | X-MICROSOFT-CDO-ALLDAYEVENT:FALSE 40 | X-MICROSOFT-CDO-IMPORTANCE:1 41 | X-MICROSOFT-CDO-INSTTYPE:0 42 | X-MICROSOFT-DONOTFORWARDMEETING:FALSE 43 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 44 | X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT 45 | X-MICROSOFT-ISRESPONSEREQUESTED:TRUE 46 | X-MICROSOFT-LOCATIONS:[] 47 | BEGIN:VALARM 48 | DESCRIPTION:REMINDER 49 | TRIGGER;RELATED=START:P0D 50 | ACTION:DISPLAY 51 | END:VALARM 52 | BEGIN:VALARM 53 | DESCRIPTION:REMINDER 54 | TRIGGER;RELATED=START:P0D 55 | ACTION:DISPLAY 56 | END:VALARM 57 | END:VEVENT 58 | END:VCALENDAR 59 | -------------------------------------------------------------------------------- /tests/assets/import-vevent.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | UID:19970901T130000Z-123401@example.com 4 | DTSTAMP:19970901T130000Z 5 | DTSTART:19970903T163000Z 6 | DTEND:19970903T190000Z 7 | SUMMARY:Annual Employee Review 8 | CLASS:PRIVATE 9 | CATEGORIES:BUSINESS,HUMAN RESOURCES 10 | END:VEVENT 11 | BEGIN:VEVENT 12 | UID:20070423T123432Z-541111@example.com 13 | DTSTAMP:20070423T123432Z 14 | DTSTART;VALUE=DATE:20070628 15 | DTEND;VALUE=DATE:20070709 16 | SUMMARY:Festival International de Jazz de Montreal 17 | TRANSP:TRANSPARENT 18 | END:VEVENT 19 | BEGIN:VEVENT 20 | DTSTAMP:19980309T231000Z 21 | UID:guid-1.example.com 22 | ORGANIZER:mailto:mrbig@example.com 23 | ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP: 24 | mailto:employee-A@example.com 25 | DESCRIPTION:Project XYZ Review Meeting 26 | CATEGORIES:MEETING 27 | CLASS:PUBLIC 28 | CREATED:19980309T130000Z 29 | SUMMARY:XYZ Project Review 30 | DTSTART;TZID=America/New_York:19980312T083000 31 | DTEND;TZID=America/Los_Angeles:19980312T093000 32 | LOCATION:1CP Conference Room 4350 33 | BEGIN:VALARM 34 | TRIGGER;VALUE=DATE-TIME:19970317T133000Z 35 | REPEAT:4 36 | DURATION:PT15M 37 | ACTION:AUDIO 38 | ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud 39 | END:VALARM 40 | END:VEVENT 41 | BEGIN:VEVENT 42 | DTSTAMP:19970324T120000Z 43 | SEQUENCE:0 44 | UID:uid3@example.com 45 | ORGANIZER:mailto:jdoe@example.com 46 | ATTENDEE;RSVP=TRUE:mailto:jsmith@example.com 47 | DTSTART:19970324T123000Z 48 | DTEND:19970324T210000Z 49 | CATEGORIES:MEETING,PROJECT 50 | CLASS:PUBLIC 51 | SUMMARY:Calendaring Interoperability Planning Meeting 52 | DESCRIPTION:Discuss how we can test c&s interoperability\n 53 | using iCalendar and other IETF standards. 54 | LOCATION:LDB Lobby 55 | ATTACH;FMTTYPE=application/postscript:ftp://example.com/pub/ 56 | conf/bkgrnd.ps 57 | END:VEVENT 58 | END:VCALENDAR 59 | -------------------------------------------------------------------------------- /tests/unit/traits/lockable.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import lockableTrait from '../../../src/traits/lockable.js'; 23 | import ModificationNotAllowedError from '../../../src/errors/modificationNotAllowedError.js'; 24 | 25 | it('should provide a locking interface', () => { 26 | const c = new (lockableTrait(class {}))() 27 | 28 | expect(c.isLocked()).toEqual(false) 29 | expect(() => { 30 | c._modify() 31 | }).not.toThrow(ModificationNotAllowedError) 32 | expect(() => { 33 | c._modifyContent() 34 | }).not.toThrow(ModificationNotAllowedError) 35 | 36 | c.lock() 37 | 38 | expect(c.isLocked()).toEqual(true) 39 | expect(() => { 40 | c._modify() 41 | }).toThrow(ModificationNotAllowedError) 42 | expect(() => { 43 | c._modifyContent() 44 | }).toThrow(ModificationNotAllowedError) 45 | 46 | c.unlock() 47 | 48 | expect(() => { 49 | c._modify() 50 | }).not.toThrow(ModificationNotAllowedError) 51 | expect(() => { 52 | c._modifyContent() 53 | }).not.toThrow(ModificationNotAllowedError) 54 | }) 55 | -------------------------------------------------------------------------------- /src/properties/textProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import Property from './property.js' 23 | 24 | /** 25 | * @class TextProperty 26 | * @classdesc 27 | */ 28 | export default class TextProperty extends Property { 29 | 30 | /** 31 | * Gets the alternate text 32 | * 33 | * @return {string} 34 | */ 35 | get alternateText() { 36 | return this.getParameterFirstValue('ALTREP') 37 | } 38 | 39 | /** 40 | * Sets the alternate text 41 | * 42 | * @param {string} altRep The alternative text 43 | */ 44 | set alternateText(altRep) { 45 | this.updateParameterIfExist('ALTREP', altRep) 46 | } 47 | 48 | /** 49 | * Gets language of this property 50 | * 51 | * @return {string} 52 | */ 53 | get language() { 54 | return this.getParameterFirstValue('LANGUAGE') 55 | } 56 | 57 | /** 58 | * Sets language of this property 59 | * 60 | * @param {string} language The language of the text 61 | */ 62 | set language(language) { 63 | this.updateParameterIfExist('LANGUAGE', language) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /tests/unit/components/root/eventComponent.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | it('EventComponent should be defined', () => { 24 | 25 | }) 26 | 27 | it('EventComponent should inherit from AbstractComponent', () => { 28 | 29 | }) 30 | 31 | it('EventComponent should ...', () => { 32 | 33 | }) 34 | 35 | it('EventComponent should ...', () => { 36 | 37 | }) 38 | 39 | it('EventComponent should ...', () => { 40 | 41 | }) 42 | 43 | it('EventComponent should ...', () => { 44 | 45 | }) 46 | 47 | it('EventComponent should ...', () => { 48 | 49 | }) 50 | 51 | it('EventComponent should ...', () => { 52 | 53 | }) 54 | 55 | it('EventComponent should ...', () => { 56 | 57 | }) 58 | 59 | it('EventComponent should ...', () => { 60 | 61 | }) 62 | 63 | it('EventComponent should ...', () => { 64 | 65 | }) 66 | 67 | it('EventComponent should ...', () => { 68 | 69 | }) 70 | 71 | it('EventComponent should ...', () => { 72 | 73 | }) 74 | 75 | it('EventComponent should ...', () => { 76 | 77 | }) 78 | 79 | it('EventComponent should ...', () => { 80 | 81 | }) 82 | -------------------------------------------------------------------------------- /tests/unit/components/root/freebusyComponent.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | it('FreeBusyComponent should be defined', () => { 24 | 25 | }) 26 | 27 | it('FreeBusyComponent should inherit from AbstractComponent', () => { 28 | 29 | }) 30 | 31 | it('FreeBusyComponent should ...', () => { 32 | 33 | }) 34 | 35 | it('FreeBusyComponent should ...', () => { 36 | 37 | }) 38 | 39 | it('FreeBusyComponent should ...', () => { 40 | 41 | }) 42 | 43 | it('FreeBusyComponent should ...', () => { 44 | 45 | }) 46 | 47 | it('FreeBusyComponent should ...', () => { 48 | 49 | }) 50 | 51 | it('FreeBusyComponent should ...', () => { 52 | 53 | }) 54 | 55 | it('FreeBusyComponent should ...', () => { 56 | 57 | }) 58 | 59 | it('FreeBusyComponent should ...', () => { 60 | 61 | }) 62 | 63 | it('FreeBusyComponent should ...', () => { 64 | 65 | }) 66 | 67 | it('FreeBusyComponent should ...', () => { 68 | 69 | }) 70 | 71 | it('FreeBusyComponent should ...', () => { 72 | 73 | }) 74 | 75 | it('FreeBusyComponent should ...', () => { 76 | 77 | }) 78 | 79 | it('FreeBusyComponent should ...', () => { 80 | 81 | }) 82 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | /** 24 | * @type {Map} 25 | */ 26 | const GLOBAL_CONFIG = new Map() 27 | 28 | /** 29 | * Sets a new config key 30 | * 31 | * @param {string} key The config-key to set 32 | * @param {*} value The value to set for given config-key 33 | */ 34 | export function setConfig(key, value) { 35 | GLOBAL_CONFIG.set(key, value) 36 | } 37 | 38 | /** 39 | * Checks if a config for a certain key is present 40 | * 41 | * @param {string} key The config-key to check 42 | * @return {boolean} 43 | */ 44 | export function hasConfig(key) { 45 | return GLOBAL_CONFIG.has(key) 46 | } 47 | 48 | /** 49 | * gets value of a config key 50 | * 51 | * @param {string} key The config-key to get 52 | * @param {*} defaultValue Default value of config does not exist 53 | * @return {*} 54 | */ 55 | export function getConfig(key, defaultValue) { 56 | return GLOBAL_CONFIG.get(key) || defaultValue 57 | } 58 | 59 | /** 60 | * deletes a config key 61 | * 62 | * @param {string} key The config-key to delete 63 | */ 64 | export function deleteConfig(key) { 65 | GLOBAL_CONFIG.delete(key) 66 | } 67 | -------------------------------------------------------------------------------- /src/components/root/timezoneComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractComponent, { 23 | advertiseSingleOccurrenceProperty, 24 | } from '../abstractComponent.js' 25 | import { Timezone } from '@nextcloud/timezones' 26 | 27 | /** 28 | * @class TimezoneComponent 29 | * @classdesc 30 | * 31 | * There are no advertised properties / components for the TimezoneComponent, 32 | * since we don't care about it. 33 | * Editing / accessing the timezone information directly is not a use-case 34 | * All the timezone-handling is done by the underlying ICAL.JS 35 | * 36 | * @url https://tools.ietf.org/html/rfc5545#section-3.6.5 37 | */ 38 | export default class TimezoneComponent extends AbstractComponent { 39 | 40 | /** 41 | * Returns a calendar-js Timezone object 42 | * 43 | * @return {Timezone} 44 | */ 45 | toTimezone() { 46 | return new Timezone(this.toICALJs()) 47 | } 48 | 49 | } 50 | 51 | /** 52 | * The timezoneId of this timezone-component 53 | * 54 | * @name TimezoneComponent#timezoneId 55 | * @type {string} 56 | */ 57 | advertiseSingleOccurrenceProperty(TimezoneComponent.prototype, { 58 | name: 'timezoneId', 59 | iCalendarName: 'tzid', 60 | }) 61 | -------------------------------------------------------------------------------- /src/values/abstractValue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import lockableTrait from '../traits/lockable.js' 23 | import observerTrait from '../traits/observer.js' 24 | 25 | /** 26 | * @class AbstractValue 27 | * @classdesc BaseClass for all values 28 | */ 29 | export default class AbstractValue extends observerTrait(lockableTrait(class {})) { 30 | 31 | /** 32 | * Constructor 33 | * 34 | * @param {ICAL.Binary|ICAL.Duration|ICAL.Period|ICAL.Recur|ICAL.Time|ICAL.UtcOffset} icalValue The ICAL.JS object to wrap 35 | */ 36 | constructor(icalValue) { 37 | if (new.target === AbstractValue) { 38 | throw new TypeError('Cannot instantiate abstract class AbstractValue') 39 | } 40 | super() 41 | 42 | /** 43 | * Wrapped ICAL.js value 44 | * 45 | * @type {ICAL.Binary|ICAL.Duration|ICAL.Period|ICAL.Recur|ICAL.Time|ICAL.UtcOffset} 46 | */ 47 | this._innerValue = icalValue 48 | } 49 | 50 | /** 51 | * Gets wrapped ICAL.JS object 52 | * 53 | * @return {*} 54 | */ 55 | toICALJs() { 56 | return this._innerValue 57 | } 58 | 59 | /** 60 | * @inheritDoc 61 | */ 62 | _modifyContent() { 63 | super._modifyContent() 64 | this._notifySubscribers() 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /tests/unit/components/root/abstractRecurringComponent.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | it('AbstractRecurringComponent should be defined', () => { 24 | 25 | }) 26 | 27 | it('AbstractRecurringComponent should inherit from AbstractComponent', () => { 28 | 29 | }) 30 | 31 | it('AbstractRecurringComponent should ...', () => { 32 | 33 | }) 34 | 35 | it('AbstractRecurringComponent should ...', () => { 36 | 37 | }) 38 | 39 | it('AbstractRecurringComponent should ...', () => { 40 | 41 | }) 42 | 43 | it('AbstractRecurringComponent should ...', () => { 44 | 45 | }) 46 | 47 | it('AbstractRecurringComponent should ...', () => { 48 | 49 | }) 50 | 51 | it('AbstractRecurringComponent should ...', () => { 52 | 53 | }) 54 | 55 | it('AbstractRecurringComponent should ...', () => { 56 | 57 | }) 58 | 59 | it('AbstractRecurringComponent should ...', () => { 60 | 61 | }) 62 | 63 | it('AbstractRecurringComponent should ...', () => { 64 | 65 | }) 66 | 67 | it('AbstractRecurringComponent should ...', () => { 68 | 69 | }) 70 | 71 | it('AbstractRecurringComponent should ...', () => { 72 | 73 | }) 74 | 75 | it('AbstractRecurringComponent should ...', () => { 76 | 77 | }) 78 | 79 | it('AbstractRecurringComponent should ...', () => { 80 | 81 | }) 82 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarIllegalCreatedRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2020 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import ICalendarIllegalCreatedRepairStep 23 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarIllegalCreatedRepairStep.js'; 24 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js'; 25 | 26 | it('The repair step should inherit from AbstractRepairStep', () => { 27 | expect((new ICalendarIllegalCreatedRepairStep() instanceof AbstractRepairStep)).toEqual(true) 28 | }) 29 | 30 | it('The repair step should have a priority', () => { 31 | expect(ICalendarIllegalCreatedRepairStep.priority()).toEqual(0) 32 | }) 33 | 34 | it('The repair step should repair broken calendar data', () => { 35 | const repairStep = new ICalendarIllegalCreatedRepairStep() 36 | const brokenICS = getAsset('illegal-created') 37 | const fixedICS = getAsset('illegal-created-sanitized') 38 | 39 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 40 | }) 41 | 42 | it('The repair step should not change valid calendar data', () => { 43 | const repairStep = new ICalendarIllegalCreatedRepairStep() 44 | const ics = getAsset('simple-date-time-europe-berlin-dtstart-dtend') 45 | 46 | expect(repairStep.repair(ics)).toEqual(ics) 47 | }) 48 | -------------------------------------------------------------------------------- /src/components/root/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractComponent from '../abstractComponent.js' 23 | import EventComponent from './eventComponent.js' 24 | import FreeBusyComponent from './freeBusyComponent.js' 25 | import JournalComponent from './journalComponent.js' 26 | import TimezoneComponent from './timezoneComponent.js' 27 | import ToDoComponent from './toDoComponent.js' 28 | import { uc } from '../../helpers/stringHelper.js' 29 | 30 | /** 31 | * Gets the constructor for a component name 32 | * This will only return a constructor for components, 33 | * that can be used in the root of a calendar-document 34 | * 35 | * @param {string} compName Name of the component to get constructor for 36 | * @return {AbstractComponent|ToDoComponent|JournalComponent|FreeBusyComponent|TimezoneComponent|EventComponent} 37 | */ 38 | export function getConstructorForComponentName(compName) { 39 | switch (uc(compName)) { 40 | case 'VEVENT': 41 | return EventComponent 42 | 43 | case 'VFREEBUSY': 44 | return FreeBusyComponent 45 | 46 | case 'VJOURNAL': 47 | return JournalComponent 48 | 49 | case 'VTIMEZONE': 50 | return TimezoneComponent 51 | 52 | case 'VTODO': 53 | return ToDoComponent 54 | 55 | default: 56 | return AbstractComponent 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarRemoveXNCGroupIdRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js'; 23 | import ICalendarRemoveXNCGroupIdRepairStep 24 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarRemoveXNCGroupIdRepairStep.js'; 25 | 26 | it('The repair step should inherit from AbstractRepairStep', () => { 27 | expect((new ICalendarRemoveXNCGroupIdRepairStep() instanceof AbstractRepairStep)).toEqual(true) 28 | }) 29 | 30 | it('The repair step should have a priority', () => { 31 | expect(ICalendarRemoveXNCGroupIdRepairStep.priority()).toEqual(0) 32 | }) 33 | 34 | it('The repair step should repair broken calendar data', () => { 35 | const repairStep = new ICalendarRemoveXNCGroupIdRepairStep() 36 | const brokenICS = getAsset('x-nc-group-id') 37 | const fixedICS = getAsset('x-nc-group-id-sanitized') 38 | 39 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 40 | }) 41 | 42 | it('The repair step should not change valid calendar data', () => { 43 | const repairStep = new ICalendarRemoveXNCGroupIdRepairStep() 44 | const ics = getAsset('simple-date-time-europe-berlin-dtstart-dtend') 45 | 46 | expect(repairStep.repair(ics)).toEqual(ics) 47 | }) 48 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarAddMissingValueDateRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js'; 23 | import ICalendarAddMissingValueDateRepairStep 24 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarAddMissingValueDateRepairStep.js'; 25 | 26 | it('The repair step should inherit from AbstractRepairStep', () => { 27 | expect((new ICalendarAddMissingValueDateRepairStep() instanceof AbstractRepairStep)).toEqual(true) 28 | }) 29 | 30 | it('The repair step should have a priority', () => { 31 | expect(ICalendarAddMissingValueDateRepairStep.priority()).toEqual(0) 32 | }) 33 | 34 | it('The repair step should repair broken calendar data', () => { 35 | const repairStep = new ICalendarAddMissingValueDateRepairStep() 36 | const brokenICS = getAsset('missing-value-date') 37 | const fixedICS = getAsset('missing-value-date-sanitized') 38 | 39 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 40 | }) 41 | 42 | it('The repair step should not change valid calendar data', () => { 43 | const repairStep = new ICalendarAddMissingValueDateRepairStep() 44 | const ics = getAsset('simple-date-time-europe-berlin-dtstart-dtend') 45 | 46 | expect(repairStep.repair(ics)).toEqual(ics) 47 | }) 48 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarMultipleVCalendarBlocksRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import ICalendarMultipleVCalendarBlocksRepairStep 23 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarMultipleVCalendarBlocksRepairStep.js'; 24 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js'; 25 | 26 | it('The repair step should inherit from AbstractRepairStep', () => { 27 | expect((new ICalendarMultipleVCalendarBlocksRepairStep() instanceof AbstractRepairStep)).toEqual(true) 28 | }) 29 | 30 | it('The repair step should have a priority', () => { 31 | expect(ICalendarMultipleVCalendarBlocksRepairStep.priority()).toEqual(0) 32 | }) 33 | 34 | it('The repair step should repair broken calendar data', () => { 35 | const repairStep = new ICalendarMultipleVCalendarBlocksRepairStep() 36 | const brokenICS = getAsset('multiple-vcalendar-blocks') 37 | const fixedICS = getAsset('multiple-vcalendar-blocks-sanitized') 38 | 39 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 40 | }) 41 | 42 | it('The repair step should not change valid calendar data', () => { 43 | const repairStep = new ICalendarMultipleVCalendarBlocksRepairStep() 44 | const ics = getAsset('simple-date-time-europe-berlin-dtstart-dtend') 45 | 46 | expect(repairStep.repair(ics)).toEqual(ics) 47 | }) 48 | -------------------------------------------------------------------------------- /src/parsers/jcalendarParser.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // /** 3 | // * @copyright Copyright (c) 2019 Georg Ehrke 4 | // * 5 | // * @author Georg Ehrke 6 | // * 7 | // * @license AGPL-3.0-or-later 8 | // * 9 | // * This program is free software: you can redistribute it and/or modify 10 | // * it under the terms of the GNU Affero General Public License as 11 | // * published by the Free Software Foundation, either version 3 of the 12 | // * License, or (at your option) any later version. 13 | // * 14 | // * This program is distributed in the hope that it will be useful, 15 | // * but WITHOUT ANY WARRANTY without even the implied warranty of 16 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | // * GNU Affero General Public License for more details. 18 | // * 19 | // * You should have received a copy of the GNU Affero General Public License 20 | // * along with this program. If not, see . 21 | // * 22 | // */ 23 | // import ICalendarParser from './icalendarParser.js'; 24 | // import CalendarComponent from '../components/calendarComponent.js'; 25 | // 26 | // /** 27 | // * @class JCalendarParser 28 | // */ 29 | // export default class JCalendarParser extends ICalendarParser { 30 | // 31 | // /** 32 | // * Parses the actual calendar-data 33 | // * 34 | // * @param {String|Object} json 35 | // */ 36 | // parse(json) { 37 | // if (typeof json === 'string') { 38 | // json = JSON.parse(json) 39 | // } 40 | // 41 | // this._createCalendarComponent(json) 42 | // 43 | // if (this._getOption('extractGlobalProperties', false)) { 44 | // this._extractProperties() 45 | // } 46 | // 47 | // this._extractTimezones() 48 | // this._registerTimezones() 49 | // this._processVObjects() 50 | // } 51 | // 52 | // /** 53 | // * 54 | // * @param {Object} json 55 | // * @private 56 | // */ 57 | // _createCalendarComponent(json) { 58 | // const icalComp = new ICAL.Component(json) 59 | // this._calendarComponent = CalendarComponent.fromICALJs(icalComp) 60 | // } 61 | // 62 | // /** 63 | // * @inheritDoc 64 | // */ 65 | // static getMimeTypes() { 66 | // return ['application/calendar+json'] 67 | // } 68 | // } 69 | -------------------------------------------------------------------------------- /tests/unit/helpers/stringHelper.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import { lc, strcasecmp, uc, startStringWith, ucFirst } from '../../../src/helpers/stringHelper.js'; 24 | 25 | it('lc should turn a string lowercase', () => { 26 | expect(lc('ABC')).toEqual('abc') 27 | expect(lc('aBc')).toEqual('abc') 28 | expect(lc('abc')).toEqual('abc') 29 | expect(lc('AbC123')).toEqual('abc123') 30 | }) 31 | 32 | it('uc should turn a string uppercase', () => { 33 | expect(uc('ABC')).toEqual('ABC') 34 | expect(uc('aBc')).toEqual('ABC') 35 | expect(uc('abc')).toEqual('ABC') 36 | expect(uc('AbC123')).toEqual('ABC123') 37 | }) 38 | 39 | it('ucFirst should turn the first character uppercase', () => { 40 | expect(ucFirst('ABC')).toEqual('ABC') 41 | expect(ucFirst('aBc')).toEqual('ABc') 42 | expect(ucFirst('abc')).toEqual('Abc') 43 | expect(ucFirst('AbC123')).toEqual('AbC123') 44 | }) 45 | 46 | it('startStringWith should make sure a string starts with a certain string', () => { 47 | expect(startStringWith('abc:123', 'abc:')).toEqual('abc:123') 48 | expect(startStringWith('123', 'abc:')).toEqual('abc:123') 49 | expect(startStringWith('123:abc', 'abc:')).toEqual('abc:123:abc') 50 | }) 51 | 52 | it('strcasecmp should compare strings ignoring their case', () => { 53 | expect(strcasecmp('abc', 'abc')).toEqual(true) 54 | expect(strcasecmp('abc', 'ABC')).toEqual(true) 55 | }) 56 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarAddMissingUIDRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import ICalendarAddMissingUIDRepairStep 23 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarAddMissingUIDRepairStep.js'; 24 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js'; 25 | 26 | jest.mock('../../../../../src/helpers/cryptoHelper.js') 27 | 28 | it('The repair step should inherit from AbstractRepairStep', () => { 29 | expect((new ICalendarAddMissingUIDRepairStep() instanceof AbstractRepairStep)).toEqual(true) 30 | }) 31 | 32 | it('The repair step should have a priority', () => { 33 | expect(ICalendarAddMissingUIDRepairStep.priority()).toEqual(0) 34 | }) 35 | 36 | it('The repair step should repair broken calendar data', () => { 37 | const repairStep = new ICalendarAddMissingUIDRepairStep() 38 | const brokenICS = getAsset('missing-uid') 39 | const fixedICS = getAsset('missing-uid-sanitized') 40 | 41 | expect(repairStep.repair(brokenICS).replace(/(\r\n|\n|\r)/gm,"\r\n")).toEqual(fixedICS.replace(/(\r\n|\n|\r)/gm,"\r\n")) 42 | }) 43 | 44 | it('The repair step should not change valid calendar data', () => { 45 | const repairStep = new ICalendarAddMissingUIDRepairStep() 46 | const ics = getAsset('simple-date-time-europe-berlin-dtstart-dtend') 47 | 48 | expect(repairStep.repair(ics)).toEqual(ics) 49 | }) 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :date: @nextcloud/calendar-js - Heart of the [Nextcloud calendar app](https://github.com/nextcloud/calendar) 2 | 3 | [![npm](https://img.shields.io/npm/v/%40nextcloud%2Fcalendar-js?style=flat-square)](https://www.npmjs.com/package/@nextcloud/calendar-js) [![Build statis](https://img.shields.io/github/actions/workflow/status/nextcloud/nextcloud-calendar-js/node-test.yml?style=flat-square)](https://github.com/nextcloud/calendar-js/actions/workflows/node-test.yml) [![Codecov branch](https://img.shields.io/codecov/c/gh/nextcloud/calendar-js/main?style=flat-square)](https://codecov.io/gh/nextcloud/calendar-js) 4 | 5 | This library is a wrapper for [ICAL.js](https://github.com/mozilla-comm/ical.js/) that provides more convenient ways for editing. 6 | Together with [cdav-library](https://github.com/nextcloud/cdav-library), it's the heart of the Nextcloud calendar app. 7 | 8 | ## Maintainers 9 | 10 | * [@GVodyanov](https://github.com/GVodyanov) 11 | * [@SebastianKrupinski](https://github.com/SebastianKrupinski) 12 | 13 | ## Developing 14 | 15 | Please take note that this library has been developed solely for the purpose of using it in the Nextcloud calendar app. 16 | Feel free to use it in your project, but don't expect any support / bugfixes / features. 17 | 18 | ### Setup 19 | ```bash 20 | npm ci 21 | ``` 22 | 23 | ### Tests 24 | 25 | ```bash 26 | npm run test 27 | ``` 28 | 29 | ### Linting 30 | 31 | ```bash 32 | npm run lint 33 | ``` 34 | 35 | ## Releases 36 | 37 | This repository follows the concept of [conventional commits](https://www.conventionalcommits.org/en/v1.0.0). A github action workflow automates the release. However, an authorized [maintainer](#maintainers) has to approve the workflow before it can run. 38 | 39 | 1) Go to https://github.com/nextcloud/calendar-js/actions/workflows/release.yml 40 | 2) Click *Run workflow* 41 | 1) Select *Branch: main* 42 | 2) Click *Run workflow* 43 | 3) Reload the page 44 | 4) Click on the waiting *Release* workflow (yellow clock icon) and approve it if you have permission or ask one of the [maintainers](#maintainers) 45 | 46 | ## License 47 | 48 | Calendar-js is licensed under the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.en.html), version 3 or later. 49 | -------------------------------------------------------------------------------- /src/properties/freeBusyProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import Property from './property.js' 23 | 24 | /** 25 | * @class FreeBusyProperty 26 | * @classdesc 27 | * 28 | * @url https://tools.ietf.org/html/rfc5545#section-3.8.2.6 29 | */ 30 | export default class FreeBusyProperty extends Property { 31 | 32 | /** 33 | * Gets the type of this FreeBusyProperty 34 | * 35 | * @return {string} 36 | */ 37 | get type() { 38 | const allowed = ['FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE'] 39 | const defaultValue = 'BUSY' 40 | 41 | if (this.hasParameter('FBTYPE')) { 42 | const value = this.getParameterFirstValue('FBTYPE') 43 | if (allowed.includes(value)) { 44 | return value 45 | } 46 | } 47 | 48 | return defaultValue 49 | } 50 | 51 | /** 52 | * Sets the type of this FreeBusyProperty 53 | * 54 | * @param {string} type The type of information (e.g. FREE, BUSY, etc.) 55 | */ 56 | set type(type) { 57 | this.updateParameterIfExist('FBTYPE', type) 58 | } 59 | 60 | /** 61 | * Creates a new FreeBusyProperty based on period and type 62 | * 63 | * @param {PeriodValue} period The period for FreeBusy Information 64 | * @param {string} type The type of the period 65 | * @return {FreeBusyProperty} 66 | */ 67 | static fromPeriodAndType(period, type) { 68 | return new FreeBusyProperty('FREEBUSY', period, [['fbtype', type]]) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarAddMissingValueDateDoubleColonRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js'; 23 | import ICalendarAddMissingValueDateDoubleColonRepairStep 24 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarAddMissingValueDateDoubleColonRepairStep.js'; 25 | 26 | it('The repair step should inherit from AbstractRepairStep', () => { 27 | expect((new ICalendarAddMissingValueDateDoubleColonRepairStep() instanceof AbstractRepairStep)).toEqual(true) 28 | }) 29 | 30 | it('The repair step should have a priority', () => { 31 | expect(ICalendarAddMissingValueDateDoubleColonRepairStep.priority()).toEqual(0) 32 | }) 33 | 34 | it('The repair step should repair broken calendar data', () => { 35 | const repairStep = new ICalendarAddMissingValueDateDoubleColonRepairStep() 36 | const brokenICS = getAsset('double-colon-missing-date') 37 | const fixedICS = getAsset('double-colon-missing-date-sanitized') 38 | 39 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 40 | }) 41 | 42 | it('The repair step should not change valid calendar data', () => { 43 | const repairStep = new ICalendarAddMissingValueDateDoubleColonRepairStep() 44 | const ics = getAsset('simple-date-time-europe-berlin-dtstart-dtend') 45 | 46 | expect(repairStep.repair(ics)).toEqual(ics) 47 | }) 48 | -------------------------------------------------------------------------------- /src/traits/observer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | /** 24 | * 25 | * @param baseClass 26 | */ 27 | export default function observerTrait(baseClass) { 28 | 29 | /** 30 | * @class ObserverTrait 31 | */ 32 | return class extends baseClass { 33 | 34 | /** 35 | * Constructor 36 | * 37 | * @param {...any} args 38 | */ 39 | constructor(...args) { 40 | super(...args) 41 | 42 | /** 43 | * List of subscribers 44 | * 45 | * @type {Function[]} 46 | * @private 47 | */ 48 | this._subscribers = [] 49 | } 50 | 51 | /** 52 | * Adds a new subscriber 53 | * 54 | * @param {Function} handler - Handler to be called when modification happens 55 | */ 56 | subscribe(handler) { 57 | this._subscribers.push(handler) 58 | } 59 | 60 | /** 61 | * Removes a subscriber 62 | * 63 | * @param {Function} handler - Handler to be no longer called when modification happens 64 | */ 65 | unsubscribe(handler) { 66 | const index = this._subscribers.indexOf(handler) 67 | if (index === -1) { 68 | return 69 | } 70 | 71 | this._subscribers.splice(index, 1) 72 | } 73 | 74 | /** 75 | * Notify all subscribed handlers 76 | * 77 | * @param {...any} args 78 | * @protected 79 | */ 80 | _notifySubscribers(...args) { 81 | for (const handler of this._subscribers) { 82 | handler(...args) 83 | } 84 | } 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/factories/icalFactory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import { lc } from '../helpers/stringHelper.js' 24 | import { getConfig } from '../config.js' 25 | import ICAL from 'ical.js' 26 | 27 | /** 28 | * creates a new ICAL.Component object 29 | * 30 | * @param {string} componentName The name of the component to create 31 | * @return {ICAL.Component} 32 | */ 33 | export function createComponent(componentName) { 34 | return new ICAL.Component(lc(componentName)) 35 | } 36 | 37 | /** 38 | * creates a new ICAL.Property object 39 | * 40 | * @param {string} propertyName The name of the property to create 41 | * @return {ICAL.Property} 42 | */ 43 | export function createProperty(propertyName) { 44 | return new ICAL.Property(lc(propertyName)) 45 | } 46 | 47 | /** 48 | * creates a new calendar component 49 | * 50 | * @param {string=} method Name of the method to include in VCALENDAR component 51 | * @return {ICAL.Component} 52 | */ 53 | export function createCalendarComponent(method = null) { 54 | const calendarComp = createComponent('VCALENDAR') 55 | 56 | calendarComp.addPropertyWithValue('prodid', getConfig('PRODID', '-//IDN georgehrke.com//calendar-js//EN')) 57 | calendarComp.addPropertyWithValue('calscale', 'GREGORIAN') 58 | calendarComp.addPropertyWithValue('version', '2.0') 59 | 60 | if (method) { 61 | calendarComp.addPropertyWithValue('method', method) 62 | } 63 | 64 | return calendarComp 65 | } 66 | -------------------------------------------------------------------------------- /src/values/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @author Richard Steinmetz 7 | * 8 | * @license AGPL-3.0-or-later 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU Affero General Public License as 12 | * published by the Free Software Foundation, either version 3 of the 13 | * License, or (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU Affero General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU Affero General Public License 21 | * along with this program. If not, see . 22 | * 23 | */ 24 | import AbstractValue from './abstractValue.js' 25 | import BinaryValue from './binaryValue.js' 26 | import DurationValue from './durationValue.js' 27 | import PeriodValue from './periodValue.js' 28 | import RecurValue from './recurValue.js' 29 | import DateTimeValue from './dateTimeValue.js' 30 | import UTCOffsetValue from './utcOffsetValue.js' 31 | import UnknownICALTypeError from '../errors/unknownICALTypeError.js' 32 | import { lc } from '../helpers/stringHelper.js' 33 | 34 | /** 35 | * 36 | * @param {string} icaltype The icaltype to get a Value constructor for 37 | * @return {RecurValue|PeriodValue|BinaryValue|DurationValue|UTCOffsetValue|DateTimeValue} 38 | */ 39 | export function getConstructorForICALType(icaltype) { 40 | switch (lc(icaltype)) { 41 | case 'binary': 42 | return BinaryValue 43 | 44 | case 'date': 45 | case 'date-time': 46 | return DateTimeValue 47 | 48 | case 'duration': 49 | return DurationValue 50 | 51 | case 'period': 52 | return PeriodValue 53 | 54 | case 'recur': 55 | return RecurValue 56 | 57 | case 'utc-offset': 58 | return UTCOffsetValue 59 | 60 | default: 61 | throw new UnknownICALTypeError() 62 | } 63 | } 64 | 65 | export { 66 | AbstractValue, 67 | BinaryValue, 68 | DateTimeValue, 69 | DurationValue, 70 | PeriodValue, 71 | RecurValue, 72 | UTCOffsetValue, 73 | } 74 | -------------------------------------------------------------------------------- /tests/unit/traits/observer.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import observerTrait from '../../../src/traits/observer.js'; 23 | 24 | it('should provide an observer interface', () => { 25 | const c = new (observerTrait(class {}))() 26 | 27 | const handler1 = jest.fn() 28 | const handler2 = jest.fn() 29 | const handler3 = jest.fn() 30 | 31 | c.subscribe(handler1) 32 | c.subscribe(handler2) 33 | c.subscribe(handler3) 34 | 35 | expect(handler1).not.toHaveBeenCalled() 36 | expect(handler2).not.toHaveBeenCalled() 37 | expect(handler3).not.toHaveBeenCalled() 38 | 39 | c._notifySubscribers('foo', 'bar', 123) 40 | 41 | c.unsubscribe(handler2) 42 | 43 | // Make sure unknown handlers don't cause errors 44 | c.unsubscribe(handler2) 45 | c.unsubscribe(() => {}) 46 | 47 | c._notifySubscribers('bar', 'foo', 456) 48 | 49 | expect(handler1).toHaveBeenCalled() 50 | expect(handler2).toHaveBeenCalled() 51 | expect(handler3).toHaveBeenCalled() 52 | 53 | expect(handler1.mock.calls.length).toEqual(2) 54 | expect(handler2.mock.calls.length).toEqual(1) 55 | expect(handler3.mock.calls.length).toEqual(2) 56 | 57 | expect(handler1.mock.calls[0]).toEqual(['foo', 'bar', 123]) 58 | expect(handler1.mock.calls[1]).toEqual(['bar', 'foo', 456]) 59 | 60 | expect(handler2.mock.calls[0]).toEqual(['foo', 'bar', 123]) 61 | 62 | expect(handler3.mock.calls[0]).toEqual(['foo', 'bar', 123]) 63 | expect(handler3.mock.calls[1]).toEqual(['bar', 'foo', 456]) 64 | }) 65 | -------------------------------------------------------------------------------- /tests/unit/components/root/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import { getConstructorForComponentName } from '../../../../src/components/root'; 23 | import AbstractComponent from '../../../../src/components/abstractComponent.js'; 24 | import EventComponent from '../../../../src/components/root/eventComponent.js'; 25 | import FreeBusyComponent from '../../../../src/components/root/freeBusyComponent.js'; 26 | import JournalComponent from '../../../../src/components/root/journalComponent.js'; 27 | import TimezoneComponent from '../../../../src/components/root/timezoneComponent.js'; 28 | import ToDoComponent from '../../../../src/components/root/toDoComponent.js'; 29 | 30 | it('should provide a constructor for a given component name', () => { 31 | expect(getConstructorForComponentName('valarm')).toEqual(AbstractComponent) 32 | 33 | expect(getConstructorForComponentName('vevent')).toEqual(EventComponent) 34 | expect(getConstructorForComponentName('VEVENT')).toEqual(EventComponent) 35 | expect(getConstructorForComponentName('vEvEnT')).toEqual(EventComponent) 36 | expect(getConstructorForComponentName('vfreebusy')).toEqual(FreeBusyComponent) 37 | expect(getConstructorForComponentName('vjournal')).toEqual(JournalComponent) 38 | expect(getConstructorForComponentName('vtimezone')).toEqual(TimezoneComponent) 39 | expect(getConstructorForComponentName('vtodo')).toEqual(ToDoComponent) 40 | expect(getConstructorForComponentName('vwhatever')).toEqual(AbstractComponent) 41 | }) 42 | -------------------------------------------------------------------------------- /src/helpers/stringHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | /** 24 | * Turns the entire string lowercase 25 | * 26 | * @param {string} str The string to turn lowercase 27 | * @return {string} 28 | */ 29 | export function lc(str) { 30 | return str.toLowerCase() 31 | } 32 | 33 | /** 34 | * Compares two strings, It is case-insensitive. 35 | * 36 | * @param {string} str1 String 1 to compare 37 | * @param {string} str2 String 2 to compare 38 | * @return {boolean} 39 | */ 40 | export function strcasecmp(str1, str2) { 41 | return uc(str1) === uc(str2) 42 | } 43 | 44 | /** 45 | * Turns the entire string uppercase 46 | * 47 | * @param {string} str The string to turn uppercase 48 | * @return {string} 49 | */ 50 | export function uc(str) { 51 | return str.toUpperCase() 52 | } 53 | 54 | /** 55 | * Capitalizes the string 56 | * 57 | * @param {string} str The string of which the first character will be turned uppercase 58 | * @return {string} 59 | */ 60 | export function ucFirst(str) { 61 | return str.charAt(0).toUpperCase() + str.slice(1) 62 | } 63 | 64 | /** 65 | * Makes sure that a string starts with a certain other string 66 | * This is mostly used in the attendeeProperty to assure the uri starts with mailto: 67 | * 68 | * @param {string} str The string to check for the prefix and prepend if necessary 69 | * @param {string} startWith The prefix to be added if necessary 70 | * @return {string} 71 | */ 72 | export function startStringWith(str, startWith) { 73 | if (!str.startsWith(startWith)) { 74 | str = startWith + str 75 | } 76 | 77 | return str 78 | } 79 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import ICalendarAddMissingUIDRepairStep from './icalendarAddMissingUIDRepairStep.js' 23 | import ICalendarAddMissingValueDateDoubleColonRepairStep from './icalendarAddMissingValueDateDoubleColonRepairStep.js' 24 | import ICalendarAddMissingValueDateRepairStep from './icalendarAddMissingValueDateRepairStep.js' 25 | import ICalendarEmptyTriggerRepairStep from './icalendarEmptyTriggerRepairStep.js' 26 | import ICalendarIllegalCreatedRepairStep from './icalendarIllegalCreatedRepairStep.js' 27 | import ICalendarMultipleVCalendarBlocksRepairStep from './icalendarMultipleVCalendarBlocksRepairStep.js' 28 | import ICalendarRemoveXNCGroupIdRepairStep from './icalendarRemoveXNCGroupIdRepairStep.js' 29 | import ICalendarRemoveUnicodeSpecialNoncharactersRepairStep from './icalendarRemoveUnicodeSpecialNoncharactersRepairStep.js' 30 | import ICalendarConvertInvalidDateTimeValuesRepairStep from './icalendarConvertInvalidDateTimeValuesRepairStep.js' 31 | 32 | /** 33 | * Get an iterator over all repair steps for iCalendar documents 34 | */ 35 | export function * getRepairSteps() { 36 | yield ICalendarAddMissingUIDRepairStep 37 | yield ICalendarAddMissingValueDateDoubleColonRepairStep 38 | yield ICalendarAddMissingValueDateRepairStep 39 | yield ICalendarEmptyTriggerRepairStep 40 | yield ICalendarIllegalCreatedRepairStep 41 | yield ICalendarMultipleVCalendarBlocksRepairStep 42 | yield ICalendarRemoveXNCGroupIdRepairStep 43 | yield ICalendarRemoveUnicodeSpecialNoncharactersRepairStep 44 | yield ICalendarConvertInvalidDateTimeValuesRepairStep 45 | } 46 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarEmptyTriggerRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js'; 23 | import ICalendarEmptyTriggerRepairStep 24 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarEmptyTriggerRepairStep.js'; 25 | 26 | it('The repair step should inherit from AbstractRepairStep', () => { 27 | expect((new ICalendarEmptyTriggerRepairStep() instanceof AbstractRepairStep)).toEqual(true) 28 | }) 29 | 30 | it('The repair step should have a priority', () => { 31 | expect(ICalendarEmptyTriggerRepairStep.priority()).toEqual(0) 32 | }) 33 | 34 | it('The repair step should repair broken calendar data', () => { 35 | const repairStep = new ICalendarEmptyTriggerRepairStep() 36 | const brokenICS = getAsset('empty-trigger') 37 | const fixedICS = getAsset('empty-trigger-sanitized') 38 | 39 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 40 | }) 41 | 42 | it('The repair step should repair broken calendar data with trigger parameters', () => { 43 | const repairStep = new ICalendarEmptyTriggerRepairStep() 44 | const brokenICS = getAsset('empty-trigger-with-parameters') 45 | const fixedICS = getAsset('empty-trigger-with-parameters-sanitized') 46 | 47 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 48 | }) 49 | 50 | it('The repair step should not change valid calendar data', () => { 51 | const repairStep = new ICalendarEmptyTriggerRepairStep() 52 | const ics = getAsset('simple-date-time-europe-berlin-dtstart-dtend') 53 | 54 | expect(repairStep.repair(ics)).toEqual(ics) 55 | }) 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nextcloud/calendar-js", 3 | "version": "8.1.6", 4 | "description": "Small library that wraps ICAL.js and provide more convenient means for editing", 5 | "type": "module", 6 | "main": "dist/index.cjs", 7 | "exports": { 8 | ".": { 9 | "import": "./dist/index.mjs", 10 | "require": "./dist/index.cjs" 11 | } 12 | }, 13 | "files": [ 14 | "README.md", 15 | "LICENSE", 16 | "dist" 17 | ], 18 | "scripts": { 19 | "build": "vite build --mode=production", 20 | "dev": "vite build --mode=development", 21 | "watch": "vite build --mode=development --watch", 22 | "lint": "eslint --ext .js src", 23 | "lint:fix": "eslint --ext .js src --fix", 24 | "test": "TZ=UTC jest", 25 | "test:watch": "TZ=UTC jest --watch", 26 | "test:coverage": "TZ=UTC jest --coverage" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/nextcloud/calendar-js.git" 31 | }, 32 | "keywords": [ 33 | "ical.js", 34 | "rfc5545", 35 | "rfc7986" 36 | ], 37 | "author": "Christoph Wurst", 38 | "license": "AGPL-3.0-or-later", 39 | "bugs": { 40 | "url": "https://github.com/nextcloud/calendar-js/issues" 41 | }, 42 | "homepage": "https://github.com/nextcloud/calendar-js#readme", 43 | "engines": { 44 | "node": "^24.0.0", 45 | "npm": "^11.3.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/core": "^7.28.5", 49 | "@nextcloud/babel-config": "^1.2.0", 50 | "@nextcloud/browserslist-config": "^3.0.1", 51 | "@nextcloud/eslint-config": "^8.4.2", 52 | "@nextcloud/eslint-plugin": "^2.2.1", 53 | "@nextcloud/vite-config": "^2.4.0", 54 | "babel-jest": "^29.7.0", 55 | "jest": "^29.7.0", 56 | "vite": "^6.4.1" 57 | }, 58 | "peerDependencies": { 59 | "@nextcloud/timezones": "^1.0.2", 60 | "ical.js": "^2.2.1" 61 | }, 62 | "babel": { 63 | "presets": [ 64 | [ 65 | "@babel/preset-env", 66 | { 67 | "targets": { 68 | "node": "current" 69 | } 70 | } 71 | ] 72 | ] 73 | }, 74 | "jest": { 75 | "verbose": true, 76 | "collectCoverage": true, 77 | "collectCoverageFrom": [ 78 | "src/**/*.{js,jsx}" 79 | ], 80 | "coverageDirectory": "./coverage", 81 | "clearMocks": true, 82 | "setupFiles": [ 83 | "./tests/assets/loader.js" 84 | ] 85 | }, 86 | "browserslist": [ 87 | "extends @nextcloud/browserslist-config" 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /tests/assets/complex-recurrence-id-modifications.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Tests// 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | BEGIN:DAYLIGHT 8 | TZOFFSETFROM:+0100 9 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 10 | DTSTART:19810329T020000 11 | TZNAME:GMT+2 12 | TZOFFSETTO:+0200 13 | END:DAYLIGHT 14 | BEGIN:STANDARD 15 | TZOFFSETFROM:+0200 16 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 17 | DTSTART:19961027T030000 18 | TZNAME:GMT+1 19 | TZOFFSETTO:+0100 20 | END:STANDARD 21 | END:VTIMEZONE 22 | BEGIN:VEVENT 23 | CREATED:20160809T163629Z 24 | DTSTAMP:20160809T163629Z 25 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 26 | SUMMARY:TEST 27 | RRULE:FREQ=WEEKLY 28 | DTSTART;TZID=Europe/Berlin:20200301T150000 29 | DTEND;TZID=Europe/Berlin:20200301T160000 30 | END:VEVENT 31 | BEGIN:VEVENT 32 | CREATED:20160809T163629Z 33 | DTSTAMP:20160809T163629Z 34 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 35 | SUMMARY:TEST EX 1 36 | RECURRENCE-ID;TZID=Europe/Berlin:20200308T150000 37 | DTSTART;TZID=Europe/Berlin:20200401T150000 38 | DTEND;TZID=Europe/Berlin:20200401T160000 39 | END:VEVENT 40 | BEGIN:VEVENT 41 | CREATED:20160809T163629Z 42 | DTSTAMP:20160809T163629Z 43 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 44 | SUMMARY:TEST EX 2 45 | RECURRENCE-ID;TZID=Europe/Berlin:20200315T150000 46 | DTSTART;TZID=Europe/Berlin:20201101T150000 47 | DTEND;TZID=Europe/Berlin:20201101T160000 48 | END:VEVENT 49 | BEGIN:VEVENT 50 | CREATED:20160809T163629Z 51 | DTSTAMP:20160809T163629Z 52 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 53 | SUMMARY:TEST EX 3 54 | RECURRENCE-ID;TZID=Europe/Berlin:20200405T150000 55 | DTSTART;TZID=Europe/Berlin:20200406T150000 56 | DTEND;TZID=Europe/Berlin:20200406T160000 57 | END:VEVENT 58 | BEGIN:VEVENT 59 | CREATED:20160809T163629Z 60 | DTSTAMP:20160809T163629Z 61 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 62 | SUMMARY:TEST EX 4 63 | RECURRENCE-ID;TZID=Europe/Berlin:20200412T150000 64 | DTSTART;TZID=Europe/Berlin:20201201T150000 65 | DTEND;TZID=Europe/Berlin:20201201T160000 66 | END:VEVENT 67 | BEGIN:VEVENT 68 | CREATED:20160809T163629Z 69 | DTSTAMP:20160809T163629Z 70 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 71 | SUMMARY:TEST EX 5 72 | RECURRENCE-ID;TZID=Europe/Berlin:20200426T150000 73 | DTSTART;TZID=Europe/Berlin:20200410T150000 74 | DTEND;TZID=Europe/Berlin:20200410T160000 75 | END:VEVENT 76 | BEGIN:VEVENT 77 | CREATED:20160809T163629Z 78 | DTSTAMP:20160809T163629Z 79 | UID:0AD16F58-01B3-463B-A215-FD09FC729A02 80 | SUMMARY:INVALID RECURRENCE-ID 81 | RECURRENCE-ID;TZID=Europe/Berlin:20200427T150000 82 | DTSTART;TZID=Europe/Berlin:20200420T150000 83 | DTEND;TZID=Europe/Berlin:20200420T160000 84 | END:VEVENT 85 | END:VCALENDAR 86 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/icalendarRemoveUnicodeSpecialNoncharactersRepairStep.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Richard Steinmetz 3 | * 4 | * @author 2024 Richard Steinmetz 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import AbstractRepairStep from '../../../../../src/parsers/repairsteps/abstractRepairStep.js'; 24 | import ICalendarRemoveUnicodeSpecialNoncharactersRepairStep 25 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarRemoveUnicodeSpecialNoncharactersRepairStep.js'; 26 | 27 | it('The repair step should inherit from AbstractRepairStep', () => { 28 | expect((new ICalendarRemoveUnicodeSpecialNoncharactersRepairStep() instanceof AbstractRepairStep)).toEqual(true) 29 | }) 30 | 31 | it('The repair step should have a priority', () => { 32 | expect(ICalendarRemoveUnicodeSpecialNoncharactersRepairStep.priority()).toEqual(0) 33 | }) 34 | 35 | it('The repair step should remove U+FFFE non-characters', () => { 36 | const repairStep = new ICalendarRemoveUnicodeSpecialNoncharactersRepairStep() 37 | const brokenICS = getAsset('unicode-non-character-fffe-before') 38 | const fixedICS = getAsset('unicode-non-character-fffe-after') 39 | 40 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 41 | }) 42 | 43 | it('The repair step should remove U+FFFF non-characters', () => { 44 | const repairStep = new ICalendarRemoveUnicodeSpecialNoncharactersRepairStep() 45 | const brokenICS = getAsset('unicode-non-character-ffff-before') 46 | const fixedICS = getAsset('unicode-non-character-ffff-after') 47 | 48 | expect(repairStep.repair(brokenICS)).toEqual(fixedICS) 49 | }) 50 | 51 | it('The repair step should not change valid calendar data', () => { 52 | const repairStep = new ICalendarRemoveUnicodeSpecialNoncharactersRepairStep() 53 | const ics = getAsset('simple-date-time-europe-berlin-dtstart-dtend') 54 | 55 | expect(repairStep.repair(ics)).toEqual(ics) 56 | }) 57 | -------------------------------------------------------------------------------- /src/properties/relationProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import Property from './property.js' 23 | 24 | /** 25 | * @class RelationProperty 26 | * @classdesc 27 | * 28 | * @url https://tools.ietf.org/html/rfc5545#section-3.8.4.5 29 | */ 30 | export default class RelationProperty extends Property { 31 | 32 | /** 33 | * Get's the relation-type of this related-to property 34 | * 35 | * @return {string} 36 | */ 37 | get relationType() { 38 | const allowed = ['PARENT', 'CHILD', 'SIBLING'] 39 | const defaultValue = 'PARENT' 40 | 41 | if (!this.hasParameter('RELTYPE')) { 42 | return defaultValue 43 | } else { 44 | const value = this.getParameterFirstValue('RELTYPE') 45 | if (allowed.includes(value)) { 46 | return value 47 | } 48 | 49 | return defaultValue 50 | } 51 | } 52 | 53 | /** 54 | * Sets a new relation type 55 | * 56 | * @param {string} relationType The type of relation (e.g. SIBLING, PARENT, etc.) 57 | */ 58 | set relationType(relationType) { 59 | this.updateParameterIfExist('RELTYPE', relationType) 60 | } 61 | 62 | /** 63 | * Gets Id of related object 64 | * 65 | * @return {string} 66 | */ 67 | get relatedId() { 68 | return this.value 69 | } 70 | 71 | /** 72 | * Sets a new related id 73 | * 74 | * @param {string} relatedId The Id of the related document 75 | */ 76 | set relatedId(relatedId) { 77 | this.value = relatedId 78 | } 79 | 80 | /** 81 | * Creates a new RELATED-TO property based on a relation-type and id 82 | * 83 | * @param {string} relType The type of the relation (e.g. SIBLING, CHILD) 84 | * @param {string} relId The Id of the related document 85 | * @return {RelationProperty} 86 | */ 87 | static fromRelTypeAndId(relType, relId) { 88 | return new RelationProperty('RELATED-TO', relId, [['RELTYPE', relType]]) 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/traits/lockable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import ModificationNotAllowedError from '../errors/modificationNotAllowedError.js' 23 | 24 | /** 25 | * 26 | * @param baseClass 27 | */ 28 | export default function lockableTrait(baseClass) { 29 | 30 | /** 31 | * @class LockableTrait 32 | */ 33 | return class extends baseClass { 34 | 35 | /** 36 | * Constructor 37 | * 38 | * @param {...any} args 39 | */ 40 | constructor(...args) { 41 | super(...args) 42 | 43 | /** 44 | * Indicator whether this value was locked for changes 45 | * 46 | * @type {boolean} 47 | * @private 48 | */ 49 | this._mutable = true 50 | } 51 | 52 | /** 53 | * Returns whether or not this object is locked 54 | * 55 | * @return {boolean} 56 | */ 57 | isLocked() { 58 | return !this._mutable 59 | } 60 | 61 | /** 62 | * Marks this object is immutable 63 | * locks it against further modification 64 | */ 65 | lock() { 66 | this._mutable = false 67 | } 68 | 69 | /** 70 | * Marks this object as mutable 71 | * allowing further modification 72 | */ 73 | unlock() { 74 | this._mutable = true 75 | } 76 | 77 | /** 78 | * Check if modifications are allowed 79 | * 80 | * @throws {ModificationNotAllowedError} if this object is locked for modification 81 | * @protected 82 | */ 83 | _modify() { 84 | if (!this._mutable) { 85 | throw new ModificationNotAllowedError() 86 | } 87 | } 88 | 89 | /** 90 | * Check if modification of content is allowed 91 | * 92 | * @throws {ModificationNotAllowedError} if this object is locked for modification 93 | * @protected 94 | */ 95 | _modifyContent() { 96 | this._modify() 97 | } 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "helpers:pinGitHubActionDigests", 6 | ":dependencyDashboard", 7 | ":semanticCommits", 8 | ":gitSignOff" 9 | ], 10 | "timezone": "Europe/Berlin", 11 | "schedule": [ 12 | "before 5am on tuesday" 13 | ], 14 | "labels": [ 15 | "dependencies", 16 | "3. to review" 17 | ], 18 | "commitMessageAction": "Bump", 19 | "commitMessageTopic": "{{depName}}", 20 | "commitMessageExtra": "from {{currentVersion}} to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{prettyNewMajor}}{{else}}{{#if isSingleVersion}}{{prettyNewVersion}}{{else}}{{#if newValue}}{{{newValue}}}{{else}}{{{newDigestShort}}}{{/if}}{{/if}}{{/if}}{{/if}}", 21 | "rangeStrategy": "bump", 22 | "rebaseWhen": "conflicted", 23 | "ignoreUnstable": false, 24 | "baseBranches": [ 25 | "main" 26 | ], 27 | "enabledManagers": [ 28 | "github-actions", 29 | "npm" 30 | ], 31 | "ignoreDeps": [ 32 | "node", 33 | "npm", 34 | "postcss-loader" 35 | ], 36 | "packageRules": [ 37 | { 38 | "description": "Request JavaScript reviews", 39 | "matchManagers": [ 40 | "npm" 41 | ], 42 | "reviewers": [ 43 | "GVodyanov", 44 | "SebastianKrupinski" 45 | ] 46 | }, 47 | { 48 | "description": "Bump Github actions monthly and request reviews", 49 | "matchManagers": [ 50 | "github-actions" 51 | ], 52 | "extends": [ 53 | "schedule:monthly" 54 | ], 55 | "reviewers": [ 56 | "GVodyanov", 57 | "SebastianKrupinski" 58 | ] 59 | }, 60 | { 61 | "matchUpdateTypes": [ 62 | "minor", 63 | "patch" 64 | ], 65 | "matchCurrentVersion": "!/^0/", 66 | "automerge": true, 67 | "automergeType": "pr", 68 | "platformAutomerge": true, 69 | "labels": [ 70 | "dependencies", 71 | "4. to release" 72 | ], 73 | "reviewers": [] 74 | }, 75 | { 76 | "matchBaseBranches": [ 77 | "main" 78 | ], 79 | "matchDepTypes": [ 80 | "devDependencies" 81 | ], 82 | "extends": [ 83 | "schedule:monthly" 84 | ] 85 | }, 86 | { 87 | "groupName": "Jest family", 88 | "matchPackageNames": [ 89 | "jest", 90 | "jest-environment-jsdom", 91 | "babel-jest", 92 | "@vue/vue2-jest", 93 | "@types/jest" 94 | ], 95 | "automerge": true 96 | } 97 | ], 98 | "vulnerabilityAlerts": { 99 | "enabled": true, 100 | "semanticCommitType": "fix", 101 | "schedule": "before 7am every weekday", 102 | "dependencyDashboardApproval": false, 103 | "commitMessageSuffix": "" 104 | }, 105 | "osvVulnerabilityAlerts": true 106 | } 107 | -------------------------------------------------------------------------------- /src/components/root/journalComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRecurringComponent from './abstractRecurringComponent.js' 23 | import { advertiseMultipleOccurrenceProperty } from '../abstractComponent.js' 24 | import TextProperty from '../../properties/textProperty.js' 25 | 26 | /** 27 | * @class JournalComponent 28 | * @classdesc 29 | * 30 | * @url https://tools.ietf.org/html/rfc5545#section-3.6.3 31 | */ 32 | export default class JournalComponent extends AbstractRecurringComponent { 33 | 34 | /** 35 | * Adds a new description property 36 | * 37 | * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.5 38 | * 39 | * @param {string} description The description text 40 | */ 41 | addDescription(description) { 42 | this.addProperty(new TextProperty('DESCRIPTION', description)) 43 | } 44 | 45 | } 46 | 47 | /** 48 | * Gets an iterator over all description properties 49 | * 50 | * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.5 51 | * 52 | * @name JournalComponent#getDescriptionIterator 53 | * @function 54 | * @return {IterableIterator} 55 | */ 56 | 57 | /** 58 | * Gets a list of all description properties 59 | * 60 | * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.5 61 | * 62 | * @name JournalComponent#getDescriptionList 63 | * @function 64 | * @return {ConferenceProperty[]} 65 | */ 66 | 67 | /** 68 | * Removes a description from this event 69 | * 70 | * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.5 71 | * 72 | * @name JournalComponent#removeDescription 73 | * @function 74 | * @param {ConferenceProperty} conference 75 | */ 76 | 77 | /** 78 | * Removes all descriptions from this event 79 | * 80 | * @url https://tools.ietf.org/html/rfc5545#section-3.8.1.5 81 | * 82 | * @name JournalComponent#clearAllDescriptions 83 | * @function 84 | */ 85 | advertiseMultipleOccurrenceProperty(JournalComponent.prototype, 'description') 86 | -------------------------------------------------------------------------------- /tests/integration/creating/createFreeBusyRequest.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import { createFreeBusyRequest } from '../../../src'; 24 | import DateTimeValue from '../../../src/values/dateTimeValue.js'; 25 | import AttendeeProperty from '../../../src/properties/attendeeProperty.js'; 26 | 27 | jest.mock('../../../src/factories/dateFactory.js') 28 | jest.mock('../../../src/helpers/cryptoHelper.js') 29 | 30 | it('should create a freeBusy request', () => { 31 | const start = DateTimeValue.fromData({ 32 | year: 2019, 33 | month: 8, 34 | day: 18, 35 | hour: 2, 36 | minute: 0, 37 | second: 0, 38 | isDate: false 39 | }) 40 | const end = DateTimeValue.fromData({ 41 | year: 2019, 42 | month: 8, 43 | day: 25, 44 | hour: 2, 45 | minute: 0, 46 | second: 0, 47 | isDate: false 48 | }) 49 | 50 | const organizer = AttendeeProperty.fromNameAndEMail('Foo', 'mailto:foo@bar.com', true) 51 | const attendee1 = AttendeeProperty.fromNameAndEMail('Foo 2', 'mailto:foo2@bar.com') 52 | const attendee2 = AttendeeProperty.fromNameAndEMail('Foo 2', 'mailto:foo3@bar.com') 53 | const attendee3 = AttendeeProperty.fromNameAndEMail('Foo 2', 'mailto:foo4@bar.com') 54 | 55 | const calendar = createFreeBusyRequest(start, end, organizer, [attendee1, attendee2, attendee3]) 56 | expect(calendar.toICS()).toEqual('BEGIN:VCALENDAR\r\n' + 57 | 'PRODID:-//IDN georgehrke.com//calendar-js//EN\r\n' + 58 | 'CALSCALE:GREGORIAN\r\n' + 59 | 'VERSION:2.0\r\n' + 60 | 'METHOD:REQUEST\r\n' + 61 | 'BEGIN:VFREEBUSY\r\n' + 62 | 'DTSTAMP:20300101T000000Z\r\n' + 63 | 'UID:RANDOM UUID 123\r\n' + 64 | 'DTSTART:20190818T020000Z\r\n' + 65 | 'DTEND:20190825T020000Z\r\n' + 66 | 'ORGANIZER;CN=Foo:mailto:foo@bar.com\r\n' + 67 | 'ATTENDEE;CN=Foo 2:mailto:foo2@bar.com\r\n' + 68 | 'ATTENDEE;CN=Foo 2:mailto:foo3@bar.com\r\n' + 69 | 'ATTENDEE;CN=Foo 2:mailto:foo4@bar.com\r\n' + 70 | 'END:VFREEBUSY\r\n' + 71 | 'END:VCALENDAR') 72 | }) 73 | -------------------------------------------------------------------------------- /src/helpers/birthdayHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | /** 24 | * Gets kind of birthday event 25 | * returns "BDAY", "DEATHDATE", "ANNIVERSARY" 26 | * or null if this is not a birthday event 27 | * 28 | * @param {EventComponent} eventComponent The eventComponent of the birthday event 29 | * @return {null|string} 30 | */ 31 | export function getTypeOfBirthdayEvent(eventComponent) { 32 | return eventComponent.getFirstPropertyFirstValue('X-NEXTCLOUD-BC-FIELD-TYPE') 33 | } 34 | 35 | /** 36 | * Gets icon for the birthday type 37 | * 38 | * @param {EventComponent} eventComponent The eventComponent of the birthday event 39 | * @return {string|null} 40 | */ 41 | export function getIconForBirthday(eventComponent) { 42 | const birthdayType = getTypeOfBirthdayEvent(eventComponent) 43 | switch (birthdayType) { 44 | case 'BDAY': 45 | return '🎂' 46 | 47 | case 'DEATHDATE': 48 | return '⚰️' 49 | 50 | case 'ANNIVERSARY': 51 | return '💍' 52 | 53 | default: 54 | return null 55 | } 56 | } 57 | 58 | /** 59 | * Returns the age of the birthday person or null of no birth-year given 60 | * 61 | * @param {EventComponent} eventComponent The eventComponent of the birthday event 62 | * @param {number} yearOfOccurrence The year to calculate the age for 63 | * @return {null|number} 64 | */ 65 | export function getAgeOfBirthday(eventComponent, yearOfOccurrence) { 66 | if (!eventComponent.hasProperty('X-NEXTCLOUD-BC-YEAR')) { 67 | return null 68 | } 69 | 70 | const yearOfBirth = eventComponent.getFirstPropertyFirstValue('X-NEXTCLOUD-BC-YEAR') 71 | return parseInt(yearOfOccurrence, 10) - parseInt(yearOfBirth, 10) 72 | } 73 | 74 | /** 75 | * Returns the name of the birthday person or null if not set 76 | * 77 | * @param {EventComponent} eventComponent The eventComponent of the birthday event 78 | * @return {null|string} 79 | */ 80 | export function getNameForBirthday(eventComponent) { 81 | return eventComponent.getFirstPropertyFirstValue('X-NEXTCLOUD-BC-NAME') 82 | } 83 | -------------------------------------------------------------------------------- /src/properties/imageProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AttachmentProperty from './attachmentProperty.js' 23 | import BinaryValue from '../values/binaryValue.js' 24 | 25 | /** 26 | * @class ImageProperty 27 | * 28 | * @url https://tools.ietf.org/html/rfc7986#section-5.10 29 | */ 30 | export default class ImageProperty extends AttachmentProperty { 31 | 32 | /** 33 | * Gets the image-type 34 | */ 35 | get display() { 36 | return this.getParameterFirstValue('DISPLAY') || 'BADGE' 37 | } 38 | 39 | /** 40 | * Gets the image-type 41 | * 42 | * @param {string} display The display-type image is optimized for 43 | */ 44 | set display(display) { 45 | this.updateParameterIfExist('DISPLAY', display) 46 | } 47 | 48 | /** 49 | * Creates a new ImageProperty based on data 50 | * 51 | * @param {string} data The data of the image 52 | * @param {string=} display The display-type it's optimized for 53 | * @param {string=} formatType The mime-type of the image 54 | * @return {ImageProperty} 55 | */ 56 | static fromData(data, display = null, formatType = null) { 57 | const binaryValue = BinaryValue.fromDecodedValue(data) 58 | const property = new ImageProperty('IMAGE', binaryValue) 59 | 60 | if (display) { 61 | property.display = display 62 | } 63 | 64 | if (formatType) { 65 | property.formatType = formatType 66 | } 67 | 68 | return property 69 | } 70 | 71 | /** 72 | * Creates a new ImageProperty based on a link 73 | * 74 | * @param {string} uri The uri of the image 75 | * @param {string=} display The display-type it's optimized for 76 | * @param {string=} formatType The mime-type of the image 77 | * @return {ImageProperty} 78 | */ 79 | static fromLink(uri, display = null, formatType = null) { 80 | const property = new ImageProperty('IMAGE', uri) 81 | 82 | if (display) { 83 | property.display = display 84 | } 85 | 86 | if (formatType) { 87 | property.formatType = formatType 88 | } 89 | 90 | return property 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /tests/unit/components/root/timezoneComponent.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import ICAL from 'ical.js' 24 | import TimezoneComponent from '../../../../src/components/root/timezoneComponent.js'; 25 | import AbstractComponent from '../../../../src/components/abstractComponent.js'; 26 | import ModificationNotAllowedError from '../../../../src/errors/modificationNotAllowedError.js'; 27 | import { Timezone } from '@nextcloud/timezones'; 28 | 29 | it('TimezoneComponent should be defined', () => { 30 | expect(TimezoneComponent).toBeDefined() 31 | }) 32 | 33 | it('TimezoneComponent should inherit from AbstractComponent', () => { 34 | const component = new TimezoneComponent('VTIMEZONE') 35 | expect(component instanceof AbstractComponent).toEqual(true) 36 | }) 37 | 38 | it('TimezoneComponent should expose easy getter/setter for timezoneId', () => { 39 | const component = new TimezoneComponent('VTIMEZONE', [['TZID', 'Europe/Berlin']]) 40 | 41 | expect(component.timezoneId).toEqual('Europe/Berlin') 42 | 43 | component.timezoneId = 'America/New_York' 44 | expect(component.timezoneId).toEqual('America/New_York') 45 | 46 | component.lock() 47 | expect(component.isLocked()).toEqual(true) 48 | 49 | expect(() => { 50 | component.timezoneId = 'America/Los_Angeles' 51 | }).toThrow(ModificationNotAllowedError); 52 | expect(component.timezoneId).toEqual('America/New_York') 53 | 54 | component.unlock() 55 | 56 | component.timezoneId = 'America/Los_Angeles' 57 | expect(component.timezoneId).toEqual('America/Los_Angeles') 58 | }) 59 | 60 | it('TimezoneComponent should be creatable from an ical.js timezone', () => { 61 | const ics = getAsset('timezone-europe-berlin') 62 | const icalValue = new ICAL.Component(ICAL.parse(ics)) 63 | 64 | const timezoneComponent = TimezoneComponent.fromICALJs(icalValue) 65 | expect(timezoneComponent instanceof TimezoneComponent).toEqual(true) 66 | expect(timezoneComponent.toTimezone() instanceof Timezone) 67 | expect(timezoneComponent.toTimezone().timezoneId).toEqual('Europe/Berlin') 68 | }) 69 | -------------------------------------------------------------------------------- /src/parsers/repairsteps/icalendar/icalendarMultipleVCalendarBlocksRepairStep.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import AbstractRepairStep from '../abstractRepairStep.js' 23 | import { uc } from '../../../helpers/stringHelper.js' 24 | 25 | /** 26 | * @class ICalendarMultipleVCalendarBlocksRepairStep 27 | * @classdesc This repair step merges multiple BEGIN:VCALENDAR...END:VCALENDAR blocks 28 | */ 29 | export default class ICalendarMultipleVCalendarBlocksRepairStep extends AbstractRepairStep { 30 | 31 | /** 32 | * Please see the corresponding test file for an example of broken calendar-data 33 | * 34 | * @inheritDoc 35 | */ 36 | repair(ics) { 37 | let containsProdId = false 38 | let containsVersion = false 39 | let containsCalscale = false 40 | const includedTimezones = new Set() 41 | 42 | return ics 43 | .replace(/^END:VCALENDAR$(((?!^BEGIN:)(.|\n))*)^BEGIN:VCALENDAR$\n/gm, '') 44 | .replace(/^PRODID:(.*)$\n/gm, (match) => { 45 | if (containsProdId) { 46 | return '' 47 | } 48 | 49 | containsProdId = true 50 | return match 51 | }) 52 | .replace(/^VERSION:(.*)$\n/gm, (match) => { 53 | if (containsVersion) { 54 | return '' 55 | } 56 | 57 | containsVersion = true 58 | return match 59 | }) 60 | .replace(/^CALSCALE:(.*)$\n/gm, (match) => { 61 | if (containsCalscale) { 62 | return '' 63 | } 64 | 65 | containsCalscale = true 66 | return match 67 | }) 68 | .replace(/^BEGIN:VTIMEZONE$(((?!^END:VTIMEZONE$)(.|\n))*)^END:VTIMEZONE$\n/gm, (match) => { 69 | const tzidMatcher = match.match(/^TZID:(.*)$/gm) 70 | 71 | // If this Timezone definition contains no TZID for some reason, 72 | // just remove it, because we can't use it anyway 73 | if (tzidMatcher === null) { 74 | return '' 75 | } 76 | 77 | const tzid = uc(tzidMatcher[0].slice(5)) 78 | if (includedTimezones.has(tzid)) { 79 | // If we already included this timezone, just skip 80 | return '' 81 | } 82 | 83 | includedTimezones.add(tzid) 84 | return match 85 | }) 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /tests/unit/values/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import { getConstructorForICALType } from '../../../src/values'; 23 | import BinaryValue from '../../../src/values/binaryValue.js'; 24 | import DateTimeValue from '../../../src/values/dateTimeValue.js'; 25 | import DurationValue from '../../../src/values/durationValue.js'; 26 | import PeriodValue from '../../../src/values/periodValue.js'; 27 | import RecurValue from '../../../src/values/recurValue.js'; 28 | import UTCOffsetValue from '../../../src/values/utcOffsetValue.js'; 29 | import UnknownICALTypeError from '../../../src/errors/unknownICALTypeError.js'; 30 | 31 | it('should provide a constructor for different ical-types - binary', () => { 32 | expect(getConstructorForICALType('binary')).toEqual(BinaryValue) 33 | expect(getConstructorForICALType('BINARY')).toEqual(BinaryValue) 34 | expect(getConstructorForICALType('bInArY')).toEqual(BinaryValue) 35 | }) 36 | 37 | it('should provide a constructor for different ical-tyoes - date', () => { 38 | expect(getConstructorForICALType('date')).toEqual(DateTimeValue) 39 | }) 40 | 41 | it('should provide a constructor for different ical-tyoes - date-time', () => { 42 | expect(getConstructorForICALType('date-time')).toEqual(DateTimeValue) 43 | }) 44 | 45 | it('should provide a constructor for different ical-tyoes - duration', () => { 46 | expect(getConstructorForICALType('duration')).toEqual(DurationValue) 47 | }) 48 | 49 | it('should provide a constructor for different ical-tyoes - period', () => { 50 | expect(getConstructorForICALType('period')).toEqual(PeriodValue) 51 | }) 52 | 53 | it('should provide a constructor for different ical-tyoes - recur', () => { 54 | expect(getConstructorForICALType('recur')).toEqual(RecurValue) 55 | }) 56 | 57 | it('should provide a constructor for different ical-tyoes - utc-offset', () => { 58 | expect(getConstructorForICALType('utc-offset')).toEqual(UTCOffsetValue) 59 | }) 60 | 61 | it('should provide a constructor for different ical-tyoes - unknown', () => { 62 | expect(() => { 63 | getConstructorForICALType('other') 64 | }).toThrow(UnknownICALTypeError) 65 | }) 66 | -------------------------------------------------------------------------------- /src/properties/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @author Richard Steinmetz 7 | * 8 | * @license AGPL-3.0-or-later 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU Affero General Public License as 12 | * published by the Free Software Foundation, either version 3 of the 13 | * License, or (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU Affero General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU Affero General Public License 21 | * along with this program. If not, see . 22 | * 23 | */ 24 | import AttachmentProperty from './attachmentProperty.js' 25 | import AttendeeProperty from './attendeeProperty.js' 26 | import ConferenceProperty from './conferenceProperty.js' 27 | import FreeBusyProperty from './freeBusyProperty.js' 28 | import GeoProperty from './geoProperty.js' 29 | import ImageProperty from './imageProperty.js' 30 | import RelationProperty from './relationProperty.js' 31 | import RequestStatusProperty from './requestStatusProperty.js' 32 | import TextProperty from './textProperty.js' 33 | import TriggerProperty from './triggerProperty.js' 34 | import Property from './property.js' 35 | import { uc } from '../helpers/stringHelper.js' 36 | 37 | /** 38 | * 39 | * @param {string} propName Name of the prop to get constructor for 40 | * @return {AttendeeProperty|GeoProperty|ConferenceProperty|Property|AttachmentProperty|ImageProperty|RelationProperty|RequestStatusProperty} 41 | */ 42 | export function getConstructorForPropertyName(propName) { 43 | switch (uc(propName)) { 44 | case 'ATTACH': 45 | return AttachmentProperty 46 | 47 | case 'ATTENDEE': 48 | case 'ORGANIZER': 49 | return AttendeeProperty 50 | 51 | case 'CONFERENCE': 52 | return ConferenceProperty 53 | 54 | case 'FREEBUSY': 55 | return FreeBusyProperty 56 | 57 | case 'GEO': 58 | return GeoProperty 59 | 60 | case 'IMAGE': 61 | return ImageProperty 62 | 63 | case 'RELATED-TO': 64 | return RelationProperty 65 | 66 | case 'REQUEST-STATUS': 67 | return RequestStatusProperty 68 | 69 | case 'TRIGGER': 70 | return TriggerProperty 71 | 72 | case 'COMMENT': 73 | case 'CONTACT': 74 | case 'DESCRIPTION': 75 | case 'LOCATION': 76 | case 'SUMMARY': 77 | return TextProperty 78 | 79 | default: 80 | return Property 81 | } 82 | } 83 | 84 | export { 85 | AttachmentProperty, 86 | AttendeeProperty, 87 | ConferenceProperty, 88 | FreeBusyProperty, 89 | GeoProperty, 90 | ImageProperty, 91 | Property, 92 | RelationProperty, 93 | RequestStatusProperty, 94 | TextProperty, 95 | TriggerProperty, 96 | } 97 | -------------------------------------------------------------------------------- /.github/workflows/npm-audit-fix.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | # 6 | # SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-License-Identifier: MIT 8 | 9 | name: Npm audit fix and compile 10 | 11 | on: 12 | workflow_dispatch: 13 | schedule: 14 | # At 2:30 on Sundays 15 | - cron: '30 2 * * 0' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | branches: 28 | - ${{ github.event.repository.default_branch }} 29 | 30 | name: npm-audit-fix-${{ matrix.branches }} 31 | 32 | steps: 33 | - name: Checkout 34 | id: checkout 35 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 36 | with: 37 | persist-credentials: false 38 | ref: ${{ matrix.branches }} 39 | continue-on-error: true 40 | 41 | - name: Read package.json node and npm engines version 42 | uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 43 | id: versions 44 | with: 45 | fallbackNode: '^20' 46 | fallbackNpm: '^10' 47 | 48 | - name: Set up node ${{ steps.versions.outputs.nodeVersion }} 49 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 50 | with: 51 | node-version: ${{ steps.versions.outputs.nodeVersion }} 52 | 53 | - name: Set up npm ${{ steps.versions.outputs.npmVersion }} 54 | run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' 55 | 56 | - name: Fix npm audit 57 | id: npm-audit 58 | uses: nextcloud-libraries/npm-audit-action@1b1728b2b4a7a78d69de65608efcf4db0e3e42d0 # v0.2.0 59 | 60 | - name: Run npm ci and npm run build 61 | if: steps.checkout.outcome == 'success' 62 | env: 63 | CYPRESS_INSTALL_BINARY: 0 64 | run: | 65 | npm ci 66 | npm run build --if-present 67 | 68 | - name: Create Pull Request 69 | if: steps.checkout.outcome == 'success' 70 | uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 71 | with: 72 | token: ${{ secrets.COMMAND_BOT_PAT }} 73 | commit-message: 'fix(deps): Fix npm audit' 74 | committer: GitHub 75 | author: nextcloud-command 76 | signoff: true 77 | branch: automated/noid/${{ matrix.branches }}-fix-npm-audit 78 | title: '[${{ matrix.branches }}] Fix npm audit' 79 | body: ${{ steps.npm-audit.outputs.markdown }} 80 | labels: | 81 | dependencies 82 | 3. to review 83 | -------------------------------------------------------------------------------- /.github/workflows/lint-eslint.yml: -------------------------------------------------------------------------------- 1 | # This workflow is provided via the organization template repository 2 | # 3 | # https://github.com/nextcloud/.github 4 | # https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization 5 | # 6 | # SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors 7 | # SPDX-License-Identifier: MIT 8 | 9 | name: Lint eslint 10 | 11 | on: pull_request 12 | 13 | permissions: 14 | contents: read 15 | 16 | concurrency: 17 | group: lint-eslint-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | changes: 22 | runs-on: ubuntu-latest-low 23 | permissions: 24 | contents: read 25 | pull-requests: read 26 | 27 | outputs: 28 | src: ${{ steps.changes.outputs.src}} 29 | 30 | steps: 31 | - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 32 | id: changes 33 | continue-on-error: true 34 | with: 35 | filters: | 36 | src: 37 | - '.github/workflows/**' 38 | - 'src/**' 39 | - 'appinfo/info.xml' 40 | - 'package.json' 41 | - 'package-lock.json' 42 | - 'tsconfig.json' 43 | - '.eslintrc.*' 44 | - '.eslintignore' 45 | - '**.js' 46 | - '**.ts' 47 | - '**.vue' 48 | 49 | lint: 50 | runs-on: ubuntu-latest 51 | 52 | needs: changes 53 | if: needs.changes.outputs.src != 'false' 54 | 55 | name: NPM lint 56 | 57 | steps: 58 | - name: Checkout 59 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 60 | 61 | - name: Read package.json node and npm engines version 62 | uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 63 | id: versions 64 | with: 65 | fallbackNode: '^20' 66 | fallbackNpm: '^10' 67 | 68 | - name: Set up node ${{ steps.versions.outputs.nodeVersion }} 69 | uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 70 | with: 71 | node-version: ${{ steps.versions.outputs.nodeVersion }} 72 | 73 | - name: Set up npm ${{ steps.versions.outputs.npmVersion }} 74 | run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}' 75 | 76 | - name: Install dependencies 77 | env: 78 | CYPRESS_INSTALL_BINARY: 0 79 | PUPPETEER_SKIP_DOWNLOAD: true 80 | run: npm ci 81 | 82 | - name: Lint 83 | run: npm run lint 84 | 85 | summary: 86 | permissions: 87 | contents: none 88 | runs-on: ubuntu-latest-low 89 | needs: [changes, lint] 90 | 91 | if: always() 92 | 93 | # This is the summary, we just avoid to rename it so that branch protection rules still match 94 | name: eslint 95 | 96 | steps: 97 | - name: Summary status 98 | run: if ${{ needs.changes.outputs.src != 'false' && needs.lint.result != 'success' }}; then exit 1; fi 99 | -------------------------------------------------------------------------------- /tests/assets/multiple-vcalendar-blocks.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//Apple Inc.//Mac OS X 10.14.5//EN 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | BEGIN:DAYLIGHT 7 | TZOFFSETFROM:+0100 8 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 9 | DTSTART:19810329T020000 10 | TZNAME:CEST 11 | TZOFFSETTO:+0200 12 | END:DAYLIGHT 13 | BEGIN:STANDARD 14 | TZOFFSETFROM:+0200 15 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 16 | DTSTART:19961027T030000 17 | TZNAME:CET 18 | TZOFFSETTO:+0100 19 | END:STANDARD 20 | END:VTIMEZONE 21 | BEGIN:VTIMEZONE 22 | TZID:Europe/Berlin 23 | BEGIN:DAYLIGHT 24 | TZOFFSETFROM:+0100 25 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 26 | DTSTART:19810329T020000 27 | TZNAME:CEST 28 | TZOFFSETTO:+0200 29 | END:DAYLIGHT 30 | BEGIN:STANDARD 31 | TZOFFSETFROM:+0200 32 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 33 | DTSTART:19961027T030000 34 | TZNAME:CET 35 | TZOFFSETTO:+0100 36 | END:STANDARD 37 | END:VTIMEZONE 38 | BEGIN:VEVENT 39 | CREATED:20190614T173801Z 40 | UID:AFBFC240-DC5C-49B2-9719-253AF67F7C13 41 | DTEND;TZID=Europe/Berlin:20190611T100000 42 | TRANSP:OPAQUE 43 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 44 | SUMMARY:test 1 45 | LAST-MODIFIED:20190614T173803Z 46 | DTSTAMP:20190614T173803Z 47 | DTSTART;TZID=Europe/Berlin:20190611T090000 48 | SEQUENCE:0 49 | END:VEVENT 50 | END:VCALENDAR 51 | 52 | 53 | BEGIN:VCALENDAR 54 | VERSION:2.0 55 | PRODID:-//Apple Inc.//Mac OS X 10.14.5//EN 56 | CALSCALE:GREGORIAN 57 | BEGIN:VTIMEZONE 58 | TZID:Europe/Berlin 59 | BEGIN:DAYLIGHT 60 | TZOFFSETFROM:+0100 61 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 62 | DTSTART:19810329T020000 63 | TZNAME:CEST 64 | TZOFFSETTO:+0200 65 | END:DAYLIGHT 66 | BEGIN:STANDARD 67 | TZOFFSETFROM:+0200 68 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 69 | DTSTART:19961027T030000 70 | TZNAME:CET 71 | TZOFFSETTO:+0100 72 | END:STANDARD 73 | END:VTIMEZONE 74 | BEGIN:VEVENT 75 | CREATED:20190614T173804Z 76 | UID:36E1C951-CC6A-4E20-A17B-4411B9B81F94 77 | DTEND;TZID=Europe/Berlin:20190619T100000 78 | TRANSP:OPAQUE 79 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 80 | SUMMARY:test 2 81 | LAST-MODIFIED:20190614T173806Z 82 | DTSTAMP:20190614T173806Z 83 | DTSTART;TZID=Europe/Berlin:20190619T090000 84 | SEQUENCE:0 85 | END:VEVENT 86 | END:VCALENDAR 87 | BEGIN:VCALENDAR 88 | VERSION:2.0 89 | PRODID:-//Apple Inc.//Mac OS X 10.14.5//EN 90 | CALSCALE:GREGORIAN 91 | BEGIN:VTIMEZONE 92 | TZID:Europe/Berlin 93 | BEGIN:DAYLIGHT 94 | TZOFFSETFROM:+0100 95 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 96 | DTSTART:19810329T020000 97 | TZNAME:CEST 98 | TZOFFSETTO:+0200 99 | END:DAYLIGHT 100 | BEGIN:STANDARD 101 | TZOFFSETFROM:+0200 102 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 103 | DTSTART:19961027T030000 104 | TZNAME:CET 105 | TZOFFSETTO:+0100 106 | END:STANDARD 107 | END:VTIMEZONE 108 | BEGIN:VEVENT 109 | CREATED:20190614T173807Z 110 | UID:294D1235-35EC-4689-BE92-00C9486F2274 111 | DTEND;TZID=Europe/Berlin:20190627T100000 112 | TRANSP:OPAQUE 113 | X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC 114 | SUMMARY:test 3 115 | LAST-MODIFIED:20190614T173808Z 116 | DTSTAMP:20190614T173808Z 117 | DTSTART;TZID=Europe/Berlin:20190627T090000 118 | SEQUENCE:0 119 | END:VEVENT 120 | END:VCALENDAR 121 | -------------------------------------------------------------------------------- /tests/unit/properties/freeBusyProperty.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | import ICAL from 'ical.js' 24 | import FreeBusyProperty from '../../../src/properties/freeBusyProperty.js'; 25 | import Property from '../../../src/properties/property.js'; 26 | import PeriodValue from '../../../src/values/periodValue.js'; 27 | 28 | it('FreeBusyProperty should be defined', () => { 29 | expect(FreeBusyProperty).toBeDefined() 30 | }) 31 | 32 | it('FreeBusyProperty should inherit from property', () => { 33 | const property = new FreeBusyProperty('') 34 | expect(property instanceof Property).toEqual(true) 35 | }) 36 | 37 | it('FreeBusyProperty should provide a type property', () => { 38 | const icalValue = ICAL.Property.fromString('FREEBUSY;FBTYPE=BUSY:19980415T133000Z/19980415T170000Z') 39 | const property = FreeBusyProperty.fromICALJs(icalValue) 40 | 41 | expect(property.type).toEqual('BUSY') 42 | }) 43 | 44 | it('FreeBusyProperty should return busy on unknown type', () => { 45 | const icalValue = ICAL.Property.fromString('FREEBUSY;FBTYPE=BUSY123:19980415T133000Z/19980415T170000Z') 46 | const property = FreeBusyProperty.fromICALJs(icalValue) 47 | 48 | expect(property.type).toEqual('BUSY') 49 | }) 50 | 51 | it('FreeBusyProperty should return busy on no type', () => { 52 | const icalValue = ICAL.Property.fromString('FREEBUSY:19980415T133000Z/19980415T170000Z') 53 | const property = FreeBusyProperty.fromICALJs(icalValue) 54 | 55 | expect(property.type).toEqual('BUSY') 56 | }) 57 | 58 | it('FreeBusyProperty should provide a setter for type', () => { 59 | const icalValue = ICAL.Property.fromString('FREEBUSY;FBTYPE=BUSY:19980415T133000Z/19980415T170000Z') 60 | const property = FreeBusyProperty.fromICALJs(icalValue) 61 | 62 | expect(property.type).toEqual('BUSY') 63 | property.type = 'FREE' 64 | 65 | expect(property.type).toEqual('FREE') 66 | 67 | expect(property.toICALJs().toICALString()).toEqual('FREEBUSY;FBTYPE=FREE:19980415T133000Z/19980415T170000Z') 68 | }) 69 | 70 | it('FreeBusyProperty should provide a constructor from period and type', () => { 71 | const icalValue = ICAL.Period.fromString('1997-01-01T18:00:00Z/PT5H30M') 72 | const value = PeriodValue.fromICALJs(icalValue) 73 | const type = 'BUSY-TENTATIVE' 74 | 75 | const property = FreeBusyProperty.fromPeriodAndType(value, type) 76 | expect(property.toICALJs().toICALString()).toEqual('FREEBUSY;FBTYPE=BUSY-TENTATIVE:19970101T180000Z/PT5H30M') 77 | }) 78 | -------------------------------------------------------------------------------- /tests/unit/properties/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import { getConstructorForPropertyName } from '../../../src/properties'; 23 | import AttachmentProperty from '../../../src/properties/attachmentProperty.js'; 24 | import AttendeeProperty from '../../../src/properties/attendeeProperty.js'; 25 | import ConferenceProperty from '../../../src/properties/conferenceProperty.js'; 26 | import GeoProperty from '../../../src/properties/geoProperty.js'; 27 | import ImageProperty from '../../../src/properties/imageProperty.js'; 28 | import RelationProperty from '../../../src/properties/relationProperty.js'; 29 | import RequestStatusProperty from '../../../src/properties/requestStatusProperty.js'; 30 | import Property from '../../../src/properties/property.js'; 31 | import TextProperty from '../../../src/properties/textProperty.js'; 32 | import TriggerProperty from '../../../src/properties/triggerProperty.js'; 33 | import FreeBusyProperty from '../../../src/properties/freeBusyProperty.js'; 34 | 35 | it('should provide a function to get the constructor for a certain property name', () => { 36 | expect(getConstructorForPropertyName('ATTACH')).toEqual(AttachmentProperty) 37 | expect(getConstructorForPropertyName('attach')).toEqual(AttachmentProperty) 38 | 39 | expect(getConstructorForPropertyName('ATTENDEE')).toEqual(AttendeeProperty) 40 | expect(getConstructorForPropertyName('ORGANIZER')).toEqual(AttendeeProperty) 41 | 42 | expect(getConstructorForPropertyName('CONFERENCE')).toEqual(ConferenceProperty) 43 | 44 | expect(getConstructorForPropertyName('FREEBUSY')).toEqual(FreeBusyProperty) 45 | 46 | expect(getConstructorForPropertyName('GEO')).toEqual(GeoProperty) 47 | 48 | expect(getConstructorForPropertyName('IMAGE')).toEqual(ImageProperty) 49 | 50 | expect(getConstructorForPropertyName('RELATED-TO')).toEqual(RelationProperty) 51 | 52 | expect(getConstructorForPropertyName('REQUEST-STATUS')).toEqual(RequestStatusProperty) 53 | 54 | expect(getConstructorForPropertyName('TRIGGER')).toEqual(TriggerProperty) 55 | 56 | expect(getConstructorForPropertyName('COMMENT')).toEqual(TextProperty) 57 | expect(getConstructorForPropertyName('CONTACT')).toEqual(TextProperty) 58 | expect(getConstructorForPropertyName('DESCRIPTION')).toEqual(TextProperty) 59 | expect(getConstructorForPropertyName('LOCATION')).toEqual(TextProperty) 60 | expect(getConstructorForPropertyName('SUMMARY')).toEqual(TextProperty) 61 | 62 | expect(getConstructorForPropertyName('DTSTART')).toEqual(Property) 63 | }) 64 | -------------------------------------------------------------------------------- /tests/unit/parsers/repairsteps/icalendar/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright (c) 2019 Georg Ehrke 3 | * 4 | * @author Georg Ehrke 5 | * 6 | * @license AGPL-3.0-or-later 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Affero General Public License as 10 | * published by the Free Software Foundation, either version 3 of the 11 | * License, or (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Affero General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | import { getRepairSteps } from '../../../../../src/parsers/repairsteps/icalendar'; 23 | import ICalendarAddMissingUIDRepairStep 24 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarAddMissingUIDRepairStep.js'; 25 | import ICalendarAddMissingValueDateDoubleColonRepairStep 26 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarAddMissingValueDateDoubleColonRepairStep.js'; 27 | import ICalendarAddMissingValueDateRepairStep 28 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarAddMissingValueDateRepairStep.js'; 29 | import ICalendarEmptyTriggerRepairStep 30 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarEmptyTriggerRepairStep.js'; 31 | import ICalendarIllegalCreatedRepairStep 32 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarIllegalCreatedRepairStep.js'; 33 | import ICalendarMultipleVCalendarBlocksRepairStep 34 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarMultipleVCalendarBlocksRepairStep.js'; 35 | import ICalendarRemoveXNCGroupIdRepairStep 36 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarRemoveXNCGroupIdRepairStep.js'; 37 | import ICalendarRemoveUnicodeSpecialNoncharactersRepairStep 38 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarRemoveUnicodeSpecialNoncharactersRepairStep.js'; 39 | import ICalendarConvertInvalidDateTimeValuesRepairStep 40 | from '../../../../../src/parsers/repairsteps/icalendar/icalendarConvertInvalidDateTimeValuesRepairStep.js'; 41 | 42 | it('should provide an iterator over all parsers', () => { 43 | const iterator = getRepairSteps() 44 | 45 | expect(iterator.next().value).toEqual(ICalendarAddMissingUIDRepairStep) 46 | expect(iterator.next().value).toEqual(ICalendarAddMissingValueDateDoubleColonRepairStep) 47 | expect(iterator.next().value).toEqual(ICalendarAddMissingValueDateRepairStep) 48 | expect(iterator.next().value).toEqual(ICalendarEmptyTriggerRepairStep) 49 | expect(iterator.next().value).toEqual(ICalendarIllegalCreatedRepairStep) 50 | expect(iterator.next().value).toEqual(ICalendarMultipleVCalendarBlocksRepairStep) 51 | expect(iterator.next().value).toEqual(ICalendarRemoveXNCGroupIdRepairStep) 52 | expect(iterator.next().value).toEqual(ICalendarRemoveUnicodeSpecialNoncharactersRepairStep) 53 | expect(iterator.next().value).toEqual(ICalendarConvertInvalidDateTimeValuesRepairStep) 54 | expect(iterator.next().value).toEqual(undefined) 55 | }) 56 | --------------------------------------------------------------------------------