diff --git a/lib/ical/design.js b/lib/ical/design.js index cfd9fbd2..1778df65 100644 --- a/lib/ical/design.js +++ b/lib/ical/design.js @@ -193,7 +193,7 @@ let commonValues = { let icalParams = { // Although the syntax is DQUOTE uri DQUOTE, I don't think we should - // enfoce anything aside from it being a valid content line. + // enforce anything aside from it being a valid content line. // // At least some params require - if multi values are used - DQUOTEs // for each of its values - e.g. delegated-from="uri1","uri2" diff --git a/lib/ical/stringify.js b/lib/ical/stringify.js index 1a542862..f388cb2c 100644 --- a/lib/ical/stringify.js +++ b/lib/ical/stringify.js @@ -116,20 +116,23 @@ stringify.property = function(property, designSet, noFold) { if (designSet.propertyGroups && paramName == 'group') { continue; } - let multiValue = (paramName in designSet.param) && designSet.param[paramName].multiValue; + + let paramDesign = designSet.param[paramName]; + let multiValue = paramDesign && paramDesign.multiValue; if (multiValue && Array.isArray(value)) { - if (designSet.param[paramName].multiValueSeparateDQuote) { - multiValue = '"' + multiValue + '"'; - } - value = value.map(stringify._rfc6868Unescape); + value = value.map(function(val) { + val = stringify._rfc6868Unescape(val); + val = stringify.paramPropertyValue(val, paramDesign.multiValueSeparateDQuote); + return val; + }); value = stringify.multiValue(value, multiValue, "unknown", null, designSet); } else { value = stringify._rfc6868Unescape(value); + value = stringify.paramPropertyValue(value); } - line += ';' + paramName.toUpperCase(); - line += '=' + stringify.propertyValue(value); + line += ';' + paramName.toUpperCase() + '=' + value; } if (property.length === 3) { @@ -206,13 +209,14 @@ stringify.property = function(property, designSet, noFold) { * If any of the above are present the result is wrapped * in double quotes. * - * @function ICAL.stringify.propertyValue + * @function ICAL.stringify.paramPropertyValue * @param {String} value Raw property value + * @param {boolean} force If value should be escaped even when unnecessary * @return {String} Given or escaped value when needed */ -stringify.propertyValue = function(value) { - - if ((unescapedIndexOf(value, ',') === -1) && +stringify.paramPropertyValue = function(value, force) { + if (!force && + (unescapedIndexOf(value, ',') === -1) && (unescapedIndexOf(value, ':') === -1) && (unescapedIndexOf(value, ';') === -1)) { diff --git a/test/stringify_test.js b/test/stringify_test.js index aecfa109..40337b84 100644 --- a/test/stringify_test.js +++ b/test/stringify_test.js @@ -80,6 +80,39 @@ suite('ICAL.stringify', function() { delete ICAL.design.defaultSet.property.custom; }); + test('property with multiple parameter values', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + ICAL.design.defaultSet.param.type = { multiValue: ',' }; + let subject = new ICAL.Property('custom'); + subject.setParameter('type', ['ABC', 'XYZ']); + subject.setValue('some value'); + assert.equal(subject.toICALString(), 'CUSTOM;TYPE=ABC,XYZ:some value'); + delete ICAL.design.defaultSet.property.custom; + delete ICAL.design.defaultSet.param.type; + }); + + test('property with multiple parameter values which must be escaped', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + ICAL.design.defaultSet.param.type = { multiValue: ',' }; + let subject = new ICAL.Property('custom'); + subject.setParameter('type', ['ABC', '--"XYZ"--']); + subject.setValue('some value'); + assert.equal(subject.toICALString(), "CUSTOM;TYPE=ABC,--^'XYZ^'--:some value"); + delete ICAL.design.defaultSet.property.custom; + delete ICAL.design.defaultSet.param.type; + }); + + test('property with multiple parameter values with enabled quoting', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + ICAL.design.defaultSet.param.type = { multiValue: ',', multiValueSeparateDQuote: true }; + let subject = new ICAL.Property('custom'); + subject.setParameter('type', ['ABC', 'XYZ']); + subject.setValue('some value'); + assert.equal(subject.toICALString(), 'CUSTOM;TYPE="ABC","XYZ":some value'); + delete ICAL.design.defaultSet.property.custom; + delete ICAL.design.defaultSet.param.type; + }); + test('rfc6868 roundtrip', function() { let subject = new ICAL.Property('attendee'); let input = "caret ^ dquote \" newline \n end"; @@ -90,6 +123,19 @@ suite('ICAL.stringify', function() { assert.equal(ICAL.parse.property(expected)[1].cn, input); }); + test('roundtrip for property with multiple parameters', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + ICAL.design.defaultSet.param.type = { multiValue: ',', multiValueSeparateDQuote: true }; + let subject = new ICAL.Property('custom'); + subject.setParameter('type', ['ABC', '--"123"--']); + subject.setValue('some value'); + assert.lengthOf(ICAL.parse.property(subject.toICALString())[1].type, 2); + assert.include(ICAL.parse.property(subject.toICALString())[1].type, 'ABC'); + assert.include(ICAL.parse.property(subject.toICALString())[1].type, '--"123"--'); + delete ICAL.design.defaultSet.property.custom; + delete ICAL.design.defaultSet.param.type; + }); + test('folding', function() { let oldLength = ICAL.foldLength; let subject = new ICAL.Property("description");