diff options
Diffstat (limited to 'js/src/util')
-rw-r--r-- | js/src/util/backdrop.js | 8 | ||||
-rw-r--r-- | js/src/util/component-functions.js | 9 | ||||
-rw-r--r-- | js/src/util/config.js | 9 | ||||
-rw-r--r-- | js/src/util/focustrap.js | 8 | ||||
-rw-r--r-- | js/src/util/index.js | 68 | ||||
-rw-r--r-- | js/src/util/sanitizer.js | 82 | ||||
-rw-r--r-- | js/src/util/scrollbar.js | 8 | ||||
-rw-r--r-- | js/src/util/swipe.js | 8 | ||||
-rw-r--r-- | js/src/util/template-factory.js | 12 |
9 files changed, 89 insertions, 123 deletions
diff --git a/js/src/util/backdrop.js b/js/src/util/backdrop.js index 78279e0..0d478e9 100644 --- a/js/src/util/backdrop.js +++ b/js/src/util/backdrop.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/backdrop.js + * Bootstrap util/backdrop.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import EventHandler from '../dom/event-handler' -import { execute, executeAfterTransition, getElement, reflow } from './index' -import Config from './config' +import EventHandler from '../dom/event-handler.js' +import Config from './config.js' +import { execute, executeAfterTransition, getElement, reflow } from './index.js' /** * Constants diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js index c2f99cc..4be828f 100644 --- a/js/src/util/component-functions.js +++ b/js/src/util/component-functions.js @@ -1,12 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/component-functions.js + * Bootstrap util/component-functions.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import EventHandler from '../dom/event-handler' -import { getElementFromSelector, isDisabled } from './index' +import EventHandler from '../dom/event-handler.js' +import SelectorEngine from '../dom/selector-engine.js' +import { isDisabled } from './index.js' const enableDismissTrigger = (component, method = 'hide') => { const clickEvent = `click.dismiss${component.EVENT_KEY}` @@ -21,7 +22,7 @@ const enableDismissTrigger = (component, method = 'hide') => { return } - const target = getElementFromSelector(this) || this.closest(`.${name}`) + const target = SelectorEngine.getElementFromSelector(this) || this.closest(`.${name}`) const instance = component.getOrCreateInstance(target) // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method diff --git a/js/src/util/config.js b/js/src/util/config.js index 1205905..a2b4bfb 100644 --- a/js/src/util/config.js +++ b/js/src/util/config.js @@ -1,12 +1,12 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/config.js + * Bootstrap util/config.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { isElement, toType } from './index' -import Manipulator from '../dom/manipulator' +import Manipulator from '../dom/manipulator.js' +import { isElement, toType } from './index.js' /** * Class definition @@ -49,8 +49,7 @@ class Config { } _typeCheckConfig(config, configTypes = this.constructor.DefaultType) { - for (const property of Object.keys(configTypes)) { - const expectedTypes = configTypes[property] + for (const [property, expectedTypes] of Object.entries(configTypes)) { const value = config[property] const valueType = isElement(value) ? 'element' : toType(value) diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js index ef69166..158f3d1 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/focustrap.js + * Bootstrap util/focustrap.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import EventHandler from '../dom/event-handler' -import SelectorEngine from '../dom/selector-engine' -import Config from './config' +import EventHandler from '../dom/event-handler.js' +import SelectorEngine from '../dom/selector-engine.js' +import Config from './config.js' /** * Constants diff --git a/js/src/util/index.js b/js/src/util/index.js index 297e571..68b8d89 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/index.js + * Bootstrap util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ @@ -9,6 +9,20 @@ const MAX_UID = 1_000_000 const MILLISECONDS_MULTIPLIER = 1000 const TRANSITION_END = 'transitionend' +/** + * Properly escape IDs selectors to handle weird IDs + * @param {string} selector + * @returns {string} + */ +const parseSelector = selector => { + if (selector && window.CSS && window.CSS.escape) { + // document.querySelector needs escaping to handle IDs (html5+) containing for instance / + selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`) + } + + return selector +} + // Shout-out Angus Croll (https://goo.gl/pxwQGp) const toType = object => { if (object === null || object === undefined) { @@ -30,47 +44,6 @@ const getUID = prefix => { return prefix } -const getSelector = element => { - let selector = element.getAttribute('data-bs-target') - - if (!selector || selector === '#') { - let hrefAttribute = element.getAttribute('href') - - // The only valid content that could double as a selector are IDs or classes, - // so everything starting with `#` or `.`. If a "real" URL is used as the selector, - // `document.querySelector` will rightfully complain it is invalid. - // See https://github.com/twbs/bootstrap/issues/32273 - if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) { - return null - } - - // Just in case some CMS puts out a full URL with the anchor appended - if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { - hrefAttribute = `#${hrefAttribute.split('#')[1]}` - } - - selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null - } - - return selector -} - -const getSelectorFromElement = element => { - const selector = getSelector(element) - - if (selector) { - return document.querySelector(selector) ? selector : null - } - - return null -} - -const getElementFromSelector = element => { - const selector = getSelector(element) - - return selector ? document.querySelector(selector) : null -} - const getTransitionDurationFromElement = element => { if (!element) { return 0 @@ -117,7 +90,7 @@ const getElement = object => { } if (typeof object === 'string' && object.length > 0) { - return document.querySelector(object) + return document.querySelector(parseSelector(object)) } return null @@ -249,10 +222,8 @@ const defineJQueryPlugin = plugin => { }) } -const execute = callback => { - if (typeof callback === 'function') { - callback() - } +const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => { + return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue } const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { @@ -318,10 +289,8 @@ export { executeAfterTransition, findShadowRoot, getElement, - getElementFromSelector, getjQuery, getNextActiveElement, - getSelectorFromElement, getTransitionDurationFromElement, getUID, isDisabled, @@ -330,6 +299,7 @@ export { isVisible, noop, onDOMContentLoaded, + parseSelector, reflow, triggerTransitionEnd, toType diff --git a/js/src/util/sanitizer.js b/js/src/util/sanitizer.js index 5328691..d2b0808 100644 --- a/js/src/util/sanitizer.js +++ b/js/src/util/sanitizer.js @@ -1,53 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/sanitizer.js + * Bootstrap util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -const uriAttributes = new Set([ - 'background', - 'cite', - 'href', - 'itemtype', - 'longdesc', - 'poster', - 'src', - 'xlink:href' -]) - +// js-docs-start allow-list const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i -/** - * A pattern that recognizes a commonly useful subset of URLs that are safe. - * - * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts - */ -const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i - -/** - * A pattern that matches safe data URLs. Only matches image, video and audio types. - * - * Shout-out to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts - */ -const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i - -const allowedAttribute = (attribute, allowedAttributeList) => { - const attributeName = attribute.nodeName.toLowerCase() - - if (allowedAttributeList.includes(attributeName)) { - if (uriAttributes.has(attributeName)) { - return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue)) - } - - return true - } - - // Check if a regular expression validates the attribute. - return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp) - .some(regex => regex.test(attributeName)) -} - export const DefaultAllowlist = { // Global attributes allowed on any supplied element below. '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], @@ -81,6 +41,43 @@ export const DefaultAllowlist = { u: [], ul: [] } +// js-docs-end allow-list + +const uriAttributes = new Set([ + 'background', + 'cite', + 'href', + 'itemtype', + 'longdesc', + 'poster', + 'src', + 'xlink:href' +]) + +/** + * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation + * contexts. + * + * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 + */ +// eslint-disable-next-line unicorn/better-regex +const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i + +const allowedAttribute = (attribute, allowedAttributeList) => { + const attributeName = attribute.nodeName.toLowerCase() + + if (allowedAttributeList.includes(attributeName)) { + if (uriAttributes.has(attributeName)) { + return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)) + } + + return true + } + + // Check if a regular expression validates the attribute. + return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp) + .some(regex => regex.test(attributeName)) +} export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { if (!unsafeHtml.length) { @@ -100,7 +97,6 @@ export function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { if (!Object.keys(allowList).includes(elementName)) { element.remove() - continue } diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 5cac7b6..413f178 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/scrollBar.js + * Bootstrap util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import SelectorEngine from '../dom/selector-engine' -import Manipulator from '../dom/manipulator' -import { isElement } from './index' +import Manipulator from '../dom/manipulator.js' +import SelectorEngine from '../dom/selector-engine.js' +import { isElement } from './index.js' /** * Constants diff --git a/js/src/util/swipe.js b/js/src/util/swipe.js index 7126360..d2f7087 100644 --- a/js/src/util/swipe.js +++ b/js/src/util/swipe.js @@ -1,13 +1,13 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/swipe.js + * Bootstrap util/swipe.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import Config from './config' -import EventHandler from '../dom/event-handler' -import { execute } from './index' +import EventHandler from '../dom/event-handler.js' +import Config from './config.js' +import { execute } from './index.js' /** * Constants diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js index cf402fa..f73589b 100644 --- a/js/src/util/template-factory.js +++ b/js/src/util/template-factory.js @@ -1,14 +1,14 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.2.3): util/template-factory.js + * Bootstrap util/template-factory.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -import { DefaultAllowlist, sanitizeHtml } from './sanitizer' -import { getElement, isElement } from '../util/index' -import SelectorEngine from '../dom/selector-engine' -import Config from './config' +import SelectorEngine from '../dom/selector-engine.js' +import Config from './config.js' +import { DefaultAllowlist, sanitizeHtml } from './sanitizer.js' +import { execute, getElement, isElement } from './index.js' /** * Constants @@ -143,7 +143,7 @@ class TemplateFactory extends Config { } _resolvePossibleFunction(arg) { - return typeof arg === 'function' ? arg(this) : arg + return execute(arg, [this]) } _putElementInTemplate(element, templateElement) { |