diff --git a/docs/timeline/index.html b/docs/timeline/index.html index 3ed706c031..3f42c06a8a 100644 --- a/docs/timeline/index.html +++ b/docs/timeline/index.html @@ -296,9 +296,9 @@

Items

selectable Boolean no - Ability to enable/disable selectability for specific items. + Ability to enable/disable selectability for specific items. Defaults to true. Does not override the timeline's - selectable configuration option. + selectable configuration option. @@ -466,7 +466,7 @@

Groups

subgroupStack Object or Boolean none - Enables stacking within individual subgroups. Example: {'subgroup0': true, 'subgroup1': false, 'subgroup2': true} + Enables stacking within individual subgroups. Example: {'subgroup0': true, 'subgroup1': false, 'subgroup2': true} For each subgroup where stacking is enabled, items will be stacked on top of each other within that subgroup such that they do no overlap. If set to true all subgroups will be stacked. If a value was specified for the order parameter in the options, that ordering will be used when stacking the items. @@ -476,8 +476,8 @@

Groups

subgroupVisibility Object none - Ability to hide/show specific subgroups. Example: {'hiddenSubgroup0': false, 'subgroup1': true, 'subgroup2': true} - If a subgroup is missing from the object, it will default as true (visible). + Ability to hide/show specific subgroups. Example: {'hiddenSubgroup0': false, 'subgroup1': true, 'subgroup2': true} + If a subgroup is missing from the object, it will default as true (visible). @@ -933,7 +933,7 @@

Configuration Options

Callback function triggered when an item is about to be added: when the user double taps an empty space in the Timeline. See section Editing Items for more information. Only applicable when both options selectable and editable.add are set true. - + onAddGroup function @@ -941,7 +941,7 @@

Configuration Options

Callback function triggered when a group is about to be added. The signature and semantics are the same as for onAdd. - + onDropObjectOnItem function @@ -957,7 +957,7 @@

Configuration Options

Callback function triggered when the timeline is initially drawn. This function fires once per timeline creation. - + onMove function @@ -965,7 +965,7 @@

Configuration Options

Callback function triggered when an item has been moved: after the user has dragged the item to an other position. See section Editing Items for more information. Only applicable when both options selectable and editable.updateTime or editable.updateGroup are set true. - + onMoveGroup function @@ -973,7 +973,7 @@

Configuration Options

Callback function triggered when a group has been moved: after the user has dragged the group to an other position. The signature and semantics are the same as for onMove. - + onMoving function @@ -981,7 +981,7 @@

Configuration Options

Callback function triggered repeatedly when an item is being moved. See section Editing Items for more information. Only applicable when both options selectable and editable.updateTime or editable.updateGroup are set true. - + onRemove function @@ -989,7 +989,7 @@

Configuration Options

Callback function triggered when an item is about to be removed: when the user tapped the delete button on the top right of a selected item. See section Editing Items for more information. Only applicable when both options selectable and editable.remove are set true. - + onRemoveGroup function @@ -997,7 +997,7 @@

Configuration Options

Callback function triggered when a group is about to be removed. The signature and semantics are the same as for onRemove. - + onUpdate function @@ -1005,7 +1005,7 @@

Configuration Options

Callback function triggered when an item is about to be updated, when the user double taps an item in the Timeline. See section Editing Items for more information. Only applicable when both options selectable and editable.updateTime or editable.updateGroup are set true. - + order function @@ -1336,6 +1336,31 @@

Configuration Options

The width of the timeline in pixels or as a percentage. + + xss + Object + none + Configure the XSS protection behavior of Timeline, which is always enabled by default. This means that most attributes and HTML elements are either removed or escaped before output. + + + xss.disabled + Boolean + undefined + Explicitly set this option to true to completely disable Timeline's XSS protection. +

Note: Please make sure you install protection against XSS vulnerabilities yourself!

+ + + + + xss.filterOptions + XSS.IFilterXSSOptions + undefined + This allows to customize whitelisting of HTMLElements, attributes and different handler functions. For more details, please refer to + the documentation of js-xss and + the related typings. + + + zoomable boolean @@ -1640,7 +1665,7 @@

Methods

- A callback function can be passed as an optional parameter. This function will be called at the end of setWindow function. + A callback function can be passed as an optional parameter. This function will be called at the end of setWindow function. diff --git a/lib/timeline/Timeline.js b/lib/timeline/Timeline.js index 2d9bbef607..ab5bdb7025 100644 --- a/lib/timeline/Timeline.js +++ b/lib/timeline/Timeline.js @@ -60,6 +60,7 @@ export default class Timeline extends Core { moment, }; this.options = util.deepExtend({}, this.defaultOptions); + options && util.setupXSSProtection(options.xss); // Create the DOM, props, and emitter this._create(container); @@ -211,7 +212,7 @@ export default class Timeline extends Core { } } - if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end) + if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end) || me.options.rollingMode)) { me.initialDrawDone = true; me.itemSet.initialDrawDone = true; @@ -310,7 +311,7 @@ export default class Timeline extends Core { */ setItems(items) { this.itemsDone = false; - + // convert to type DataSet when needed let newDataSet; if (!items) { @@ -341,14 +342,14 @@ export default class Timeline extends Core { // convert to type DataSet when needed let newDataSet; const filter = group => group.visible !== false; - + if (!groups) { newDataSet = null; } else { // If groups is array, turn to DataSet & build dataview from that if (Array.isArray(groups)) groups = new DataSet(groups); - + newDataSet = new DataView(groups,{filter}); } @@ -483,7 +484,7 @@ export default class Timeline extends Core { // The redraw shifted elements, so reset the animation to correct initialVerticalScroll = verticalScroll; startPos = me._getScrollTop() * -1; - } + } const from = startPos; const to = initialVerticalScroll.scrollOffset; @@ -512,7 +513,7 @@ export default class Timeline extends Core { // Double check we ended at the proper scroll position setFinalVerticalPosition(); - // Let the redraw settle and finalize the position. + // Let the redraw settle and finalize the position. setTimeout(setFinalVerticalPosition, 100); }; @@ -528,7 +529,7 @@ export default class Timeline extends Core { initialVerticalScroll = { shouldScroll: false, scrollOffset: -1, itemTop: -1 }; } - this.range.setRange(middle - interval / 2, middle + interval / 2, { animation }, finalVerticalCallback, verticalAnimationFrame); + this.range.setRange(middle - interval / 2, middle + interval / 2, { animation }, finalVerticalCallback, verticalAnimationFrame); } } @@ -689,7 +690,7 @@ export default class Timeline extends Core { const centerContainerRect = this.dom.centerContainer.getBoundingClientRect(); const x = this.options.rtl ? centerContainerRect.right - clientX : clientX - centerContainerRect.left; const y = clientY - centerContainerRect.top; - + const item = this.itemSet.itemFromTarget(event); const group = this.itemSet.groupFromTarget(event); const customTime = CustomTime.customTimeFromTarget(event); @@ -800,12 +801,12 @@ function getItemVerticalScroll(timeline, item) { const itemsetHeight = timeline.options.rtl ? timeline.props.rightContainer.height : timeline.props.leftContainer.height; const contentHeight = timeline.props.center.height; - + const group = item.parent; let offset = group.top; let shouldScroll = true; const orientation = timeline.timeAxis.options.orientation.axis; - + const itemTop = () => { if (orientation == "bottom") { return group.height - item.top - item.height; diff --git a/lib/timeline/optionsTimeline.js b/lib/timeline/optionsTimeline.js index 7646423136..ba59913b7c 100644 --- a/lib/timeline/optionsTimeline.js +++ b/lib/timeline/optionsTimeline.js @@ -149,7 +149,7 @@ let allOptions = { showWeekScale: { 'boolean': bool}, stack: { 'boolean': bool}, stackSubgroups: { 'boolean': bool}, - cluster: { + cluster: { maxItems: {'number': number, 'undefined': 'undefined'}, titleTemplate: {'string': string, 'undefined': 'undefined'}, clusterCriteria: { 'function': 'function', 'undefined': 'undefined'}, @@ -188,7 +188,14 @@ let allOptions = { zoomFriction: {number}, zoomMax: {number}, zoomMin: {number}, - + xss: { + disabled: { boolean: bool }, + filterOptions: { + __any__: { any }, + __type__: { object } + }, + __type__: { object } + }, __type__: {object} }; @@ -290,7 +297,8 @@ let configureOptions = { zoomable: true, zoomKey: ['ctrlKey', 'altKey', 'shiftKey', 'metaKey', ''], zoomMax: [315360000000000, 10, 315360000000000, 1], - zoomMin: [10, 10, 315360000000000, 1] + zoomMin: [10, 10, 315360000000000, 1], + xss: { disabled: false } } }; diff --git a/lib/util.js b/lib/util.js index 8c82352211..4fa161f343 100644 --- a/lib/util.js +++ b/lib/util.js @@ -6,7 +6,7 @@ import { getType, isNumber, isString } from "vis-util/esnext"; import { DataSet, createNewDataPipeFrom } from "vis-data/esnext"; import moment from "moment"; -import xss from 'xss'; +import xssFilter from 'xss'; // parse ASP.Net Date pattern, // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/' @@ -87,13 +87,13 @@ export function convert(object, type) { if (match) { // object is an ASP date return moment(Number(match[1])); // parse number - } + } match = NumericRegex.exec(object); if (match) { return moment(Number(object)); } - + return moment(object); // parse string } else { throw new TypeError( @@ -225,8 +225,46 @@ export function typeCoerceDataSet( }; } -export default { +// Configure XSS protection +const setupXSSCleaner = (options) => { + const customXSS = new xssFilter.FilterXSS(options); + return (string) => customXSS.process(string); +}; +const setupNoOpCleaner = (string) => string; + +// when nothing else is configured: filter XSS with the lib's default options +let configuredXSSProtection = setupXSSCleaner(); + +const setupXSSProtection = (options) => { + // No options? Do nothing. + if (!options) { + return; + } + + // Disable XSS protection completely on request + if (options.disabled === true) { + configuredXSSProtection = setupNoOpCleaner; + console.warn('You disabled XSS protection for vis-Timeline. I sure hope you know what you\'re doing!'); + } else { + // Configure XSS protection with some custom options. + // For a list of valid options check the lib's documentation: + // https://github.com/leizongmin/js-xss#custom-filter-rules + if (options.filterOptions) { + configuredXSSProtection = setupXSSCleaner(options.filterOptions); + } + } +}; + +const availableUtils = { ...util, convert, - xss + setupXSSProtection }; + +Object.defineProperty(availableUtils, 'xss', { + get: function() { + return configuredXSSProtection; + } +}) + +export default availableUtils; diff --git a/types/index.d.ts b/types/index.d.ts index 6c1da3e3b6..9f8dc54b4e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -212,6 +212,11 @@ export interface TimelineTooltipOption { template?: (item: TimelineItem, editedData?: TimelineItem) => string; } +export interface TimelineXSSProtectionOption { + disabled: boolean; + filterOptions?: XSS.IFilterXSSOptions; +} + export type TimelineOptionsConfigureFunction = (option: string, path: string[]) => boolean; export type TimelineOptionsConfigureType = boolean | TimelineOptionsConfigureFunction; export type TimelineOptionsDataAttributesType = boolean | string | string[]; @@ -312,6 +317,7 @@ export interface TimelineOptions { zoomFriction?: number; zoomMax?: number; zoomMin?: number; + xss?: TimelineXSSProtectionOption; } /**