diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 242f17ba156..2416aff5b64 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -285,7 +285,7 @@ export namespace Components { */ 'leaveAnimation'?: AnimationBuilder; /** - * The main message to be displayed in the alert. + * The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'message'?: string; /** @@ -352,7 +352,7 @@ export namespace Components { */ 'leaveAnimation'?: AnimationBuilder; /** - * The main message to be displayed in the alert. + * The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'message'?: string; /** @@ -1566,7 +1566,7 @@ export namespace Components { */ 'loadingSpinner'?: SpinnerTypes | null; /** - * Optional text to display while loading. + * Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'loadingText'?: string; } @@ -1576,7 +1576,7 @@ export namespace Components { */ 'loadingSpinner'?: SpinnerTypes | null; /** - * Optional text to display while loading. + * Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'loadingText'?: string; } @@ -3418,7 +3418,7 @@ export namespace Components { */ 'pullingIcon'?: string | null; /** - * The text you want to display when you begin to pull down + * The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'pullingText'?: string; /** @@ -3426,7 +3426,7 @@ export namespace Components { */ 'refreshingSpinner'?: SpinnerTypes | null; /** - * The text you want to display when performing a refresh + * The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'refreshingText'?: string; } @@ -3436,7 +3436,7 @@ export namespace Components { */ 'pullingIcon'?: string | null; /** - * The text you want to display when you begin to pull down + * The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'pullingText'?: string; /** @@ -3444,7 +3444,7 @@ export namespace Components { */ 'refreshingSpinner'?: SpinnerTypes | null; /** - * The text you want to display when performing a refresh + * The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'refreshingText'?: string; } @@ -3732,7 +3732,7 @@ export namespace Components { */ 'mode': Mode; /** - * Set the input's placeholder. + * Set the input's placeholder. `placeholder` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'placeholder': string; /** @@ -3826,7 +3826,7 @@ export namespace Components { */ 'onIonInput'?: (event: CustomEvent) => void; /** - * Set the input's placeholder. + * Set the input's placeholder. `placeholder` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ 'placeholder'?: string; /** diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx index 422cfad0d3d..5bee010cdb7 100644 --- a/core/src/components/alert/alert.tsx +++ b/core/src/components/alert/alert.tsx @@ -2,6 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Listen, Me import { AlertButton, AlertInput, Animation, AnimationBuilder, Config, CssClassMap, Mode, OverlayEventDetail, OverlayInterface } from '../../interface'; import { BACKDROP, dismiss, eventMethod, isCancel, present } from '../../utils/overlays'; +import { sanitizeDOMString } from '../../utils/sanitization'; import { getClassMap } from '../../utils/theme'; import { iosEnterAnimation } from './animations/ios.enter'; @@ -72,6 +73,12 @@ export class Alert implements ComponentInterface, OverlayInterface { /** * The main message to be displayed in the alert. + * `message` can accept either plaintext or HTML as a string. + * To display characters normally reserved for HTML, they + * must be escaped. For example `` would become + * `<Ionic>` + * + * For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ @Prop() message?: string; @@ -444,7 +451,7 @@ export class Alert implements ComponentInterface, OverlayInterface { {this.subHeader &&

{this.subHeader}

} -
+
{this.renderAlertInputs(labelledById)} {this.renderAlertButtons()} diff --git a/core/src/components/alert/readme.md b/core/src/components/alert/readme.md index 8b94a1f129b..e70d38ce019 100644 --- a/core/src/components/alert/readme.md +++ b/core/src/components/alert/readme.md @@ -1054,21 +1054,21 @@ export default { ## Properties -| Property | Attribute | Description | Type | Default | -| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------- | -| `animated` | `animated` | If `true`, the alert will animate. | `boolean` | `true` | -| `backdropDismiss` | `backdrop-dismiss` | If `true`, the alert will be dismissed when the backdrop is clicked. | `boolean` | `true` | -| `buttons` | -- | Array of buttons to be added to the alert. | `(string \| AlertButton)[]` | `[]` | -| `cssClass` | `css-class` | Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. | `string \| string[] \| undefined` | `undefined` | -| `enterAnimation` | -- | Animation to use when the alert is presented. | `((Animation: Animation, baseEl: any, opts?: any) => Promise) \| undefined` | `undefined` | -| `header` | `header` | The main title in the heading of the alert. | `string \| undefined` | `undefined` | -| `inputs` | -- | Array of input to show in the alert. | `AlertInput[]` | `[]` | -| `keyboardClose` | `keyboard-close` | If `true`, the keyboard will be automatically dismissed when the overlay is presented. | `boolean` | `true` | -| `leaveAnimation` | -- | Animation to use when the alert is dismissed. | `((Animation: Animation, baseEl: any, opts?: any) => Promise) \| undefined` | `undefined` | -| `message` | `message` | The main message to be displayed in the alert. | `string \| undefined` | `undefined` | -| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` | -| `subHeader` | `sub-header` | The subtitle in the heading of the alert. Displayed under the title. | `string \| undefined` | `undefined` | -| `translucent` | `translucent` | If `true`, the alert will be translucent. | `boolean` | `false` | +| Property | Attribute | Description | Type | Default | +| ----------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------- | +| `animated` | `animated` | If `true`, the alert will animate. | `boolean` | `true` | +| `backdropDismiss` | `backdrop-dismiss` | If `true`, the alert will be dismissed when the backdrop is clicked. | `boolean` | `true` | +| `buttons` | -- | Array of buttons to be added to the alert. | `(string \| AlertButton)[]` | `[]` | +| `cssClass` | `css-class` | Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. | `string \| string[] \| undefined` | `undefined` | +| `enterAnimation` | -- | Animation to use when the alert is presented. | `((Animation: Animation, baseEl: any, opts?: any) => Promise) \| undefined` | `undefined` | +| `header` | `header` | The main title in the heading of the alert. | `string \| undefined` | `undefined` | +| `inputs` | -- | Array of input to show in the alert. | `AlertInput[]` | `[]` | +| `keyboardClose` | `keyboard-close` | If `true`, the keyboard will be automatically dismissed when the overlay is presented. | `boolean` | `true` | +| `leaveAnimation` | -- | Animation to use when the alert is dismissed. | `((Animation: Animation, baseEl: any, opts?: any) => Promise) \| undefined` | `undefined` | +| `message` | `message` | The main message to be displayed in the alert. `message` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string \| undefined` | `undefined` | +| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` | +| `subHeader` | `sub-header` | The subtitle in the heading of the alert. Displayed under the title. | `string \| undefined` | `undefined` | +| `translucent` | `translucent` | If `true`, the alert will be translucent. | `boolean` | `false` | ## Events diff --git a/core/src/components/infinite-scroll-content/infinite-scroll-content.tsx b/core/src/components/infinite-scroll-content/infinite-scroll-content.tsx index 95d6d714071..b440eb0eb4b 100644 --- a/core/src/components/infinite-scroll-content/infinite-scroll-content.tsx +++ b/core/src/components/infinite-scroll-content/infinite-scroll-content.tsx @@ -1,6 +1,7 @@ import { Component, ComponentInterface, Prop } from '@stencil/core'; import { Config, Mode, SpinnerTypes } from '../../interface'; +import { sanitizeDOMString } from '../../utils/sanitization'; @Component({ tag: 'ion-infinite-scroll-content', @@ -22,6 +23,12 @@ export class InfiniteScrollContent implements ComponentInterface { /** * Optional text to display while loading. + * `loadingText` can accept either plaintext or HTML as a string. + * To display characters normally reserved for HTML, they + * must be escaped. For example `` would become + * `<Ionic>` + * + * For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ @Prop() loadingText?: string; @@ -54,7 +61,7 @@ export class InfiniteScrollContent implements ComponentInterface { )} {this.loadingText && ( -
+
)}
); diff --git a/core/src/components/infinite-scroll-content/readme.md b/core/src/components/infinite-scroll-content/readme.md index 04b3649ec83..f0f8d980a1d 100644 --- a/core/src/components/infinite-scroll-content/readme.md +++ b/core/src/components/infinite-scroll-content/readme.md @@ -76,10 +76,10 @@ export default Example ## Properties -| Property | Attribute | Description | Type | Default | -| ---------------- | ----------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------- | -| `loadingSpinner` | `loading-spinner` | An animated SVG spinner that shows while loading. | `"bubbles" \| "circles" \| "crescent" \| "dots" \| "lines" \| "lines-small" \| null \| undefined` | `undefined` | -| `loadingText` | `loading-text` | Optional text to display while loading. | `string \| undefined` | `undefined` | +| Property | Attribute | Description | Type | Default | +| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------- | +| `loadingSpinner` | `loading-spinner` | An animated SVG spinner that shows while loading. | `"bubbles" \| "circles" \| "crescent" \| "dots" \| "lines" \| "lines-small" \| null \| undefined` | `undefined` | +| `loadingText` | `loading-text` | Optional text to display while loading. `loadingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string \| undefined` | `undefined` | ---------------------------------------------- diff --git a/core/src/components/refresher-content/readme.md b/core/src/components/refresher-content/readme.md index d936d71b151..2d8c0fef801 100644 --- a/core/src/components/refresher-content/readme.md +++ b/core/src/components/refresher-content/readme.md @@ -9,12 +9,12 @@ The refresher content contains the text, icon and spinner to display during a pu ## Properties -| Property | Attribute | Description | Type | Default | -| ------------------- | -------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------- | -| `pullingIcon` | `pulling-icon` | A static icon to display when you begin to pull down | `null \| string \| undefined` | `undefined` | -| `pullingText` | `pulling-text` | The text you want to display when you begin to pull down | `string \| undefined` | `undefined` | -| `refreshingSpinner` | `refreshing-spinner` | An animated SVG spinner that shows when refreshing begins | `"bubbles" \| "circles" \| "crescent" \| "dots" \| "lines" \| "lines-small" \| null \| undefined` | `undefined` | -| `refreshingText` | `refreshing-text` | The text you want to display when performing a refresh | `string \| undefined` | `undefined` | +| Property | Attribute | Description | Type | Default | +| ------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------- | +| `pullingIcon` | `pulling-icon` | A static icon to display when you begin to pull down | `null \| string \| undefined` | `undefined` | +| `pullingText` | `pulling-text` | The text you want to display when you begin to pull down. `pullingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string \| undefined` | `undefined` | +| `refreshingSpinner` | `refreshing-spinner` | An animated SVG spinner that shows when refreshing begins | `"bubbles" \| "circles" \| "crescent" \| "dots" \| "lines" \| "lines-small" \| null \| undefined` | `undefined` | +| `refreshingText` | `refreshing-text` | The text you want to display when performing a refresh. `refreshingText` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string \| undefined` | `undefined` | ---------------------------------------------- diff --git a/core/src/components/refresher-content/refresher-content.tsx b/core/src/components/refresher-content/refresher-content.tsx index 2cbbd3ff17f..8e08642899c 100644 --- a/core/src/components/refresher-content/refresher-content.tsx +++ b/core/src/components/refresher-content/refresher-content.tsx @@ -1,6 +1,7 @@ import { Component, ComponentInterface, Prop } from '@stencil/core'; import { Config, Mode, SpinnerTypes } from '../../interface'; +import { sanitizeDOMString } from '../../utils/sanitization'; @Component({ tag: 'ion-refresher-content' @@ -17,7 +18,13 @@ export class RefresherContent implements ComponentInterface { @Prop({ mutable: true }) pullingIcon?: string | null; /** - * The text you want to display when you begin to pull down + * The text you want to display when you begin to pull down. + * `pullingText` can accept either plaintext or HTML as a string. + * To display characters normally reserved for HTML, they + * must be escaped. For example `` would become + * `<Ionic>` + * + * For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ @Prop() pullingText?: string; @@ -27,7 +34,13 @@ export class RefresherContent implements ComponentInterface { @Prop({ mutable: true }) refreshingSpinner?: SpinnerTypes | null; /** - * The text you want to display when performing a refresh + * The text you want to display when performing a refresh. + * `refreshingText` can accept either plaintext or HTML as a string. + * To display characters normally reserved for HTML, they + * must be escaped. For example `` would become + * `<Ionic>` + * + * For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ @Prop() refreshingText?: string; @@ -60,7 +73,7 @@ export class RefresherContent implements ComponentInterface {
} {this.pullingText && -
+
} ,
@@ -70,7 +83,7 @@ export class RefresherContent implements ComponentInterface {
} {this.refreshingText && -
+
} ]; diff --git a/core/src/components/searchbar/readme.md b/core/src/components/searchbar/readme.md index d59a3411aa2..a08c430ca6e 100644 --- a/core/src/components/searchbar/readme.md +++ b/core/src/components/searchbar/readme.md @@ -174,24 +174,24 @@ export default Example; ## Properties -| Property | Attribute | Description | Type | Default | -| ------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------- | -| `animated` | `animated` | If `true`, enable searchbar animation. | `boolean` | `false` | -| `autocomplete` | `autocomplete` | Set the input's autocomplete property. | `"off" \| "on"` | `'off'` | -| `autocorrect` | `autocorrect` | Set the input's autocorrect property. | `"off" \| "on"` | `'off'` | -| `cancelButtonIcon` | `cancel-button-icon` | Set the cancel button icon. Only applies to `md` mode. | `string` | `'md-arrow-back'` | -| `cancelButtonText` | `cancel-button-text` | Set the the cancel button text. Only applies to `ios` mode. | `string` | `'Cancel'` | -| `clearIcon` | `clear-icon` | Set the clear icon. Defaults to `"close-circle"` for `ios` and `"close"` for `md`. | `string \| undefined` | `undefined` | -| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` | -| `debounce` | `debounce` | Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. | `number` | `250` | -| `disabled` | `disabled` | If `true`, the user cannot interact with the input. | `boolean` | `false` | -| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` | -| `placeholder` | `placeholder` | Set the input's placeholder. | `string` | `'Search'` | -| `searchIcon` | `search-icon` | The icon to use as the search icon. | `string` | `'search'` | -| `showCancelButton` | `show-cancel-button` | If `true`, show the cancel button. | `boolean` | `false` | -| `spellcheck` | `spellcheck` | If `true`, enable spellcheck on the input. | `boolean` | `false` | -| `type` | `type` | Set the type of the input. | `"email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "url"` | `'search'` | -| `value` | `value` | the value of the searchbar. | `null \| string \| undefined` | `''` | +| Property | Attribute | Description | Type | Default | +| ------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------- | +| `animated` | `animated` | If `true`, enable searchbar animation. | `boolean` | `false` | +| `autocomplete` | `autocomplete` | Set the input's autocomplete property. | `"off" \| "on"` | `'off'` | +| `autocorrect` | `autocorrect` | Set the input's autocorrect property. | `"off" \| "on"` | `'off'` | +| `cancelButtonIcon` | `cancel-button-icon` | Set the cancel button icon. Only applies to `md` mode. | `string` | `'md-arrow-back'` | +| `cancelButtonText` | `cancel-button-text` | Set the the cancel button text. Only applies to `ios` mode. | `string` | `'Cancel'` | +| `clearIcon` | `clear-icon` | Set the clear icon. Defaults to `"close-circle"` for `ios` and `"close"` for `md`. | `string \| undefined` | `undefined` | +| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` | +| `debounce` | `debounce` | Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke. | `number` | `250` | +| `disabled` | `disabled` | If `true`, the user cannot interact with the input. | `boolean` | `false` | +| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` | +| `placeholder` | `placeholder` | Set the input's placeholder. `placeholder` can accept either plaintext or HTML as a string. To display characters normally reserved for HTML, they must be escaped. For example `` would become `<Ionic>` For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) | `string` | `'Search'` | +| `searchIcon` | `search-icon` | The icon to use as the search icon. | `string` | `'search'` | +| `showCancelButton` | `show-cancel-button` | If `true`, show the cancel button. | `boolean` | `false` | +| `spellcheck` | `spellcheck` | If `true`, enable spellcheck on the input. | `boolean` | `false` | +| `type` | `type` | Set the type of the input. | `"email" \| "number" \| "password" \| "search" \| "tel" \| "text" \| "url"` | `'search'` | +| `value` | `value` | the value of the searchbar. | `null \| string \| undefined` | `''` | ## Events diff --git a/core/src/components/searchbar/searchbar.tsx b/core/src/components/searchbar/searchbar.tsx index 868cafac0a6..22c11c9d82a 100644 --- a/core/src/components/searchbar/searchbar.tsx +++ b/core/src/components/searchbar/searchbar.tsx @@ -2,6 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Pr import { Color, Config, Mode, SearchbarChangeEventDetail } from '../../interface'; import { debounceEvent } from '../../utils/helpers'; +import { sanitizeDOMString } from '../../utils/sanitization'; import { createColorClasses } from '../../utils/theme'; @Component({ @@ -85,6 +86,12 @@ export class Searchbar implements ComponentInterface { /** * Set the input's placeholder. + * `placeholder` can accept either plaintext or HTML as a string. + * To display characters normally reserved for HTML, they + * must be escaped. For example `` would become + * `<Ionic>` + * + * For more information: [Security Documentation](https://ionicframework.com/docs/faq/security) */ @Prop() placeholder = 'Search'; @@ -293,7 +300,7 @@ export class Searchbar implements ComponentInterface { // Create a dummy span to get the placeholder width const doc = this.doc; const tempSpan = doc.createElement('span'); - tempSpan.innerHTML = this.placeholder; + tempSpan.innerHTML = sanitizeDOMString(this.placeholder) || ''; doc.body.appendChild(tempSpan); // Get the width of the span then remove it diff --git a/core/src/utils/sanitization/index.ts b/core/src/utils/sanitization/index.ts new file mode 100644 index 00000000000..3019250f0da --- /dev/null +++ b/core/src/utils/sanitization/index.ts @@ -0,0 +1,126 @@ +/** + * Does a simple sanitization of all elements + * in an untrusted string + */ + +export const sanitizeDOMString = (untrustedString: string | undefined): string | undefined => { + try { + if (typeof untrustedString !== 'string' || untrustedString === '') { return untrustedString; } + + /** + * Create a document fragment + * separate from the main DOM, + * create a div to do our work in + */ + const documentFragment = document.createDocumentFragment(); + const workingDiv = document.createElement('div'); + documentFragment.appendChild(workingDiv); + workingDiv.innerHTML = untrustedString; + + /** + * Remove any elements + * that are blocked + */ + blockedTags.forEach(blockedTag => { + + const getElementsToRemove = documentFragment.querySelectorAll(blockedTag); + for (let elementIndex = getElementsToRemove.length - 1; elementIndex >= 0; elementIndex--) { + const element = getElementsToRemove[elementIndex]; + if (element.parentNode) { + element.parentNode.removeChild(element); + } else { + documentFragment.removeChild(element); + } + + /** + * We still need to sanitize + * the children of this element + * as they are left behind + */ + const childElements = getElementChildren(element); + + /* tslint:disable-next-line */ + for (let childIndex = 0; childIndex < childElements.length; childIndex++) { + sanitizeElement(childElements[childIndex]); + } + } + }); + + /** + * Go through remaining elements and remove + * non-allowed attribs + */ + + // IE does not support .children on document fragments, only .childNodes + const documentFragmentChildren = getElementChildren(documentFragment); + + /* tslint:disable-next-line */ + for (let childIndex = 0; childIndex < documentFragmentChildren.length; childIndex++) { + sanitizeElement(documentFragmentChildren[childIndex]); + } + + // Append document fragment to div + const fragmentDiv = document.createElement('div'); + fragmentDiv.appendChild(documentFragment); + + // First child is always the div we did our work in + const getInnerDiv = fragmentDiv.querySelector('div'); + return (getInnerDiv !== null) ? getInnerDiv.innerHTML : fragmentDiv.innerHTML; + + } catch (err) { + console.error(err); + + return ''; + } +}; + +/** + * Clean up current element based on allowed attributes + * and then recursively dig down into any child elements to + * clean those up as well + */ +const sanitizeElement = (element: any) => { + // IE uses childNodes, so ignore nodes that are not elements + if (element.nodeType && element.nodeType !== 1) { return; } + + for (let i = element.attributes.length - 1; i >= 0; i--) { + const attribute = element.attributes[i]; + const attributeName = attribute.name; + + // remove non-allowed attribs + if (!allowedAttributes.includes(attributeName.toLowerCase())) { + element.removeAttribute(attributeName); + continue; + } + + // clean up any allowed attribs + // that attempt to do any JS funny-business + const attributeValue = attribute.value; + + /* tslint:disable-next-line */ + if (attributeValue != null && attributeValue.toLowerCase().includes('javascript:')) { + element.removeAttribute(attributeName); + } + } + + /** + * Sanitize any nested children + */ + const childElements = getElementChildren(element); + + /* tslint:disable-next-line */ + for (let i = 0; i < childElements.length; i++) { + sanitizeElement(childElements[i]); + } +}; + +/** + * IE doesn't always support .children + * so we revert to .childNodes instead + */ +const getElementChildren = (element: any) => { + return (element.children != null) ? element.children : element.childNodes; +}; + +const allowedAttributes = ['class', 'id', 'href', 'src']; +const blockedTags = ['script', 'style', 'iframe', 'meta', 'link', 'object', 'embed']; diff --git a/core/src/utils/sanitization/test/e2e.ts b/core/src/utils/sanitization/test/e2e.ts new file mode 100644 index 00000000000..513946f5734 --- /dev/null +++ b/core/src/utils/sanitization/test/e2e.ts @@ -0,0 +1,26 @@ +import { newE2EPage } from '@stencil/core/testing'; + +test('sanitization:', async done => { + + const page = await newE2EPage({ + url: '/src/utils/sanitization/test?ionic:_testing=true' + }); + + page.on('pageerror', (err: any) => { + if (err.message.includes('sanitizeFailed')) { + done.fail(new Error('Failed to properly sanitize')); + } + }); + + await page.click('#testA'); + await page.click('#testB'); + await page.click('#testC'); + await page.click('#testD'); + await page.click('#testE'); + await page.click('#testF'); + await page.click('#testG'); + await page.click('#testH'); + + done(); + +}); diff --git a/core/src/utils/sanitization/test/index.html b/core/src/utils/sanitization/test/index.html new file mode 100644 index 00000000000..fed2f3b7a53 --- /dev/null +++ b/core/src/utils/sanitization/test/index.html @@ -0,0 +1,113 @@ + + + + + Sanitization + + + + + + + + + + + + + + + + Sanitization + + + + +
Results will appear here
+ + Test A + Test B + Test C + Test D + Test E + Test F + Test G + Test H +
+ +
+ + + + +