Skip to content

Commit

Permalink
Quote (or don't quote) parameter property values individually before …
Browse files Browse the repository at this point in the history
…joining

Instead of adding quotes to the delimiting comma and around the outside, quote the individual members separately if necessary.
This prevents the whole string of multiple values being quoted because of the presence of a delimiting comma.

When quotes are required (multiValueSeparateDQuote is true) only URIs are valid, which will get quoted due to the colon.
When not required only specific strings (WORK, VOICE, PARCEL, etc.) are valid, none of which need quotes.
  • Loading branch information
darktrojan committed Nov 13, 2022
1 parent 3f0838c commit 9819520
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 12 deletions.
2 changes: 1 addition & 1 deletion lib/ical/design.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
26 changes: 15 additions & 11 deletions lib/ical/stringify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)) {

Expand Down
46 changes: 46 additions & 0 deletions test/stringify_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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");
Expand Down

0 comments on commit 9819520

Please sign in to comment.