kewisch/ical.js

Fails to parse COMMA-separated list of calendar addresses in quoted-strings (e.g. `MEMBER` parameter)

st3iny opened this issue · 7 comments

st3iny commented

RFC: https://www.rfc-editor.org/rfc/rfc5545#section-3.2.11

The builtin ics parser does not handle parsing COMMA-separated list of calendar addresses in quoted-strings correctly.

Note: The ics example data (attendee property) is valid and was copied as is from the RFC.

Example

const ics = 'ATTENDEE;MEMBER="mailto:projectA@example.com","mailto:projectB@example.com":mailto:janedoe@example.com'
const prop = ICAL.Property.fromString(ics)

Expected

[
  "attendee",
  {
    "member": [
      "mailto:projectA@example.com",
      "mailto:projectB@example.com"
    ]
  },
  "cal-address",
  "mailto:janedoe@example.com"
]

Received

[
  "attendee",
  {
    "member": [
      "mailto:projectA@example.com",
      "mailto:projectB@example.com"
    ]
  },
  "cal-address",
  "projectA@example.com\",\"mailto:projectB@example.com\":mailto:janedoe@example.com"
]

The parser stops at the colon inside the first quoted-string MEMBER parameter. But for some reason both member parameters are still parsed correctly. Only the property value is not parsed correctly.

onny commented

@st3iny I'm unable to reproduce the issue. Here is my script:

const ICAL = require('ical.js');

var iCalendarData = [
    'BEGIN:VCALENDAR',
    'CALSCALE:GREGORIAN',
    'PRODID:-//Example Inc.//Example Calendar//EN',
    'VERSION:2.0',
    'BEGIN:VEVENT',
    'DTSTAMP:20080205T191224Z',
    'DTSTART:20081006',
    'SUMMARY:Planning meeting',
    'ATTENDEE;CN=MyGroup;CUTYPE=GROUP;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANTRSVP=TRUE;SCHEDULE-STATUS1.1:mailto:mygroup@localhost',
    'ATTENDEE;MEMBER="mailto:mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost";CN=user1;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;LANGUAGE=en;SCHEDULE-STATUS=1.1:mailto:user1@localhost',
    'ATTENDEE;MEMBER="mailto:mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost";CN=user2;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;LANGUAGE=en;SCHEDULE-STATUS=1.1:mailto:user2@localhost',
    'ORGANIZER;CN=admin:mailto:admin@localhost',
    'UID:4088E990AD89CB3DBB484909',
    'END:VEVENT',
    'END:VCALENDAR'
].join("\r\n");


var jcalData = ICAL.parse(iCalendarData);
var vcalendar = new ICAL.Component(jcalData);
var vevent = vcalendar.getFirstSubcomponent('vevent');
var attendees = vevent.getAllProperties('attendee');

attendees.forEach(function(attendee) {
  console.log('Attendee:', attendee);
});

Here is the result of one attendee:

Attendee: <ref *1> e {
  _parent: t {
    _hydratedPropertyCount: 3,
    _hydratedComponentCount: 0,
    _timezoneCache: null,
    jCal: [ 'vevent', [Array], [] ],
    parent: t {
      _hydratedPropertyCount: 0,
      _hydratedComponentCount: 1,
      _timezoneCache: Map(0) {},
      jCal: [Array],
      parent: null,
      _components: [Array]
    },
    _properties: [ <3 empty items>, [e], [e], [Circular *1] ]
  },
  jCal: [
    'attendee',
    {
      member: [Array],
      cn: 'user2',
      cutype: 'INDIVIDUAL',
      partstat: 'NEEDS-ACTION',
      role: 'REQ-PARTICIPANT',
      rsvp: 'TRUE',
      language: 'en',
      'schedule-status': '1.1'
    },
    'cal-address',
    'mailto:user2@localhost'
  ],
  isDecorated: false,
  isMultiValue: false,
  isStructuredValue: false
}
st3iny commented

Thanks for investigating further.

It seems to break only if the MEMBER parameter is the last one before the actual value of the ATTENDEE property.

Take a look at the following example:

const ICAL = require('ical.js');

var iCalendarData = [
    'ATTENDEE;MEMBER="mailto:mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost";CN=user1;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;LANGUAGE=en;SCHEDULE-STATUS=1.1:mailto:user1@localhost',
    'ATTENDEE;MEMBER="mailto:mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost":mailto:user2@localhost',
]

for (const attendeeIcs of iCalendarData) {
    const attendee = ICAL.Property.fromString(attendeeIcs);
    console.log('expected:', attendeeIcs)
    console.log('actual:  ', attendee.toICALString())
    console.log('equal?   ', attendee.toICALString() === attendeeIcs)
    console.log('--------------------------------------------------')
}

It will yield:

expected: ATTENDEE;MEMBER="mailto:mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost";CN=user1;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;LANGUAGE=en;SCHEDULE-STATUS=1.1:mailto:user1@localhost
actual:   ATTENDEE;MEMBER="mailto:mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost";CN=user1;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE;LANGUAGE=en;SCHEDULE-STATUS=1.1:mailto:user1@localhost
equal?    true
--------------------------------------------------
expected: ATTENDEE;MEMBER="mailto:mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost":mailto:user2@localhost
actual:   ATTENDEE;MEMBER="mailto:mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost":mygroup@localhost","mailto:mygroup2@localhost","mailto:mygroup3@localhost":mailto:user2@localhost
equal?    false
--------------------------------------------------