index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*!
  2. * @esri/arcgis-html-sanitizer - v2.10.0 - Tue Mar 15 2022 16:38:25 GMT-0400 (Eastern Daylight Time)
  3. * Copyright (c) 2022 - Environmental Systems Research Institute, Inc.
  4. * Apache-2.0
  5. *
  6. * js-xss
  7. * Copyright (c) 2012-2017 Zongmin Lei(雷宗民) <leizongmin@gmail.com>
  8. * http://ucdok.com
  9. * MIT License, see https://github.com/leizongmin/js-xss/blob/master/LICENSE for details
  10. *
  11. * Lodash/isPlainObject
  12. * Copyright (c) JS Foundation and other contributors <https://js.foundation/>
  13. * MIT License, see https://raw.githubusercontent.com/lodash/lodash/4.17.10-npm/LICENSE for details
  14. */
  15. 'use strict';
  16. Object.defineProperty(exports, '__esModule', { value: true });
  17. var isPlainObject = require('lodash.isplainobject');
  18. var xss = require('xss');
  19. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  20. function _interopNamespace(e) {
  21. if (e && e.__esModule) return e;
  22. var n = Object.create(null);
  23. if (e) {
  24. Object.keys(e).forEach(function (k) {
  25. if (k !== 'default') {
  26. var d = Object.getOwnPropertyDescriptor(e, k);
  27. Object.defineProperty(n, k, d.get ? d : {
  28. enumerable: true,
  29. get: function () { return e[k]; }
  30. });
  31. }
  32. });
  33. }
  34. n["default"] = e;
  35. return Object.freeze(n);
  36. }
  37. var isPlainObject__default = /*#__PURE__*/_interopDefaultLegacy(isPlainObject);
  38. var xss__namespace = /*#__PURE__*/_interopNamespace(xss);
  39. /* Copyright (c) 2020 Environmental Systems Research Institute, Inc.
  40. * Apache-2.0
  41. *
  42. * js-xss
  43. * Copyright (c) 2012-2018 Zongmin Lei(雷宗民) <leizongmin@gmail.com>
  44. * http://ucdok.com
  45. * The MIT License, see
  46. * https://github.com/leizongmin/js-xss/blob/master/LICENSE for details
  47. *
  48. * Lodash/isPlainObject
  49. * Copyright (c) JS Foundation and other contributors <https://js.foundation/>
  50. * MIT License, see https://raw.githubusercontent.com/lodash/lodash/4.17.10-npm/LICENSE for details
  51. * */
  52. /**
  53. * The Sanitizer Class
  54. *
  55. * @export
  56. * @class Sanitizer
  57. */
  58. var Sanitizer = /** @class */ (function () {
  59. function Sanitizer(filterOptions, extendDefaults) {
  60. var _this = this;
  61. // Supported HTML Spec: https://doc.arcgis.com/en/arcgis-online/reference/supported-html.htm
  62. this.arcgisWhiteList = {
  63. a: ["href", "style", "target"],
  64. abbr: ["title"],
  65. audio: ["autoplay", "controls", "loop", "muted", "preload"],
  66. b: [],
  67. br: [],
  68. dd: ["style"],
  69. div: ["align", "style"],
  70. dl: ["style"],
  71. dt: ["style"],
  72. em: [],
  73. figcaption: ["style"],
  74. figure: ["style"],
  75. font: ["color", "face", "size", "style"],
  76. h1: ["style"],
  77. h2: ["style"],
  78. h3: ["style"],
  79. h4: ["style"],
  80. h5: ["style"],
  81. h6: ["style"],
  82. hr: [],
  83. i: [],
  84. img: ["alt", "border", "height", "src", "style", "width"],
  85. li: [],
  86. ol: [],
  87. p: ["style"],
  88. source: ["media", "src", "type"],
  89. span: ["style"],
  90. strong: [],
  91. sub: ["style"],
  92. sup: ["style"],
  93. table: ["border", "cellpadding", "cellspacing", "height", "style", "width"],
  94. tbody: [],
  95. tr: ["align", "height", "style", "valign"],
  96. td: [
  97. "align",
  98. "colspan",
  99. "height",
  100. "nowrap",
  101. "rowspan",
  102. "style",
  103. "valign",
  104. "width"
  105. ],
  106. th: [
  107. "align",
  108. "colspan",
  109. "height",
  110. "nowrap",
  111. "rowspan",
  112. "style",
  113. "valign",
  114. "width"
  115. ],
  116. u: [],
  117. ul: [],
  118. video: [
  119. "autoplay",
  120. "controls",
  121. "height",
  122. "loop",
  123. "muted",
  124. "poster",
  125. "preload",
  126. "width"
  127. ]
  128. };
  129. this.allowedProtocols = [
  130. "http",
  131. "https",
  132. "mailto",
  133. "iform",
  134. "tel",
  135. "flow",
  136. "lfmobile",
  137. "arcgis-navigator",
  138. "arcgis-appstudio-player",
  139. "arcgis-survey123",
  140. "arcgis-collector",
  141. "arcgis-workforce",
  142. "arcgis-explorer",
  143. "arcgis-trek2there",
  144. "arcgis-quickcapture",
  145. "mspbi",
  146. "comgooglemaps",
  147. "pdfefile",
  148. "pdfehttp",
  149. "pdfehttps",
  150. "boxapp",
  151. "boxemm",
  152. "awb",
  153. "awbs",
  154. "gropen",
  155. "radarscope"
  156. ];
  157. this.arcgisFilterOptions = {
  158. allowCommentTag: true,
  159. safeAttrValue: function (tag, name, value, cssFilter) {
  160. // Take over safe attribute filtering for `a` `href`, `img` `src`,
  161. // and `source` `src` attributes, otherwise pass onto the
  162. // default `XSS.safeAttrValue` method.
  163. if ((tag === "a" && name === "href") ||
  164. ((tag === "img" || tag === "source") && name === "src")) {
  165. return _this.sanitizeUrl(value);
  166. }
  167. return xss__namespace.safeAttrValue(tag, name, value, cssFilter);
  168. }
  169. };
  170. var xssFilterOptions;
  171. if (filterOptions && !extendDefaults) {
  172. // Override the defaults
  173. xssFilterOptions = filterOptions;
  174. }
  175. else if (filterOptions && extendDefaults) {
  176. // Extend the defaults
  177. xssFilterOptions = Object.create(this.arcgisFilterOptions);
  178. Object.keys(filterOptions).forEach(function (key) {
  179. if (key === "whiteList") {
  180. // Extend the whitelist by concatenating arrays
  181. xssFilterOptions.whiteList = _this._extendObjectOfArrays([
  182. _this.arcgisWhiteList,
  183. filterOptions.whiteList || {}
  184. ]);
  185. }
  186. else {
  187. xssFilterOptions[key] = filterOptions[key];
  188. }
  189. });
  190. }
  191. else {
  192. // Only use the defaults
  193. xssFilterOptions = Object.create(this.arcgisFilterOptions);
  194. xssFilterOptions.whiteList = this.arcgisWhiteList;
  195. }
  196. this.xssFilterOptions = xssFilterOptions;
  197. // Make this readable to tests
  198. this._xssFilter = new xss__namespace.FilterXSS(xssFilterOptions);
  199. }
  200. /**
  201. * Sanitizes value to remove invalid HTML tags.
  202. *
  203. * Note: If the value passed does not contain a valid JSON data type (String,
  204. * Number, JSON Object, Array, Boolean, or null), the value will be nullified.
  205. *
  206. * @param {any} value The value to sanitize.
  207. * @returns {any} The sanitized value.
  208. * @memberof Sanitizer
  209. */
  210. Sanitizer.prototype.sanitize = function (value, options) {
  211. if (options === void 0) { options = {}; }
  212. switch (typeof value) {
  213. case "number":
  214. if (isNaN(value) || !isFinite(value)) {
  215. return null;
  216. }
  217. return value;
  218. case "boolean":
  219. return value;
  220. case "string":
  221. return this._xssFilter.process(value);
  222. case "object":
  223. return this._iterateOverObject(value, options);
  224. default:
  225. if (options.allowUndefined && typeof value === "undefined") {
  226. return;
  227. }
  228. return null;
  229. }
  230. };
  231. /**
  232. * Sanitizes a URL string following the allowed protocols and sanitization rules.
  233. *
  234. * @param {string} value The URL to sanitize.
  235. * @param {{ isProtocolRequired: boolean }} options Configuration options for URL checking.
  236. * @returns {string} The sanitized URL if it's valid, or an empty string if the URL is invalid.
  237. */
  238. Sanitizer.prototype.sanitizeUrl = function (value, options) {
  239. var _a = (options !== null && options !== void 0 ? options : {}).isProtocolRequired, isProtocolRequired = _a === void 0 ? true : _a;
  240. var protocol = this._trim(value.substring(0, value.indexOf(":")));
  241. var isRootUrl = value === '/';
  242. var isUrlFragment = /^#/.test(value);
  243. var isValidProtocol = protocol && this.allowedProtocols.indexOf(protocol.toLowerCase()) > -1;
  244. if (isRootUrl || isUrlFragment || isValidProtocol) {
  245. return xss__namespace.escapeAttrValue(value);
  246. }
  247. if (!protocol && !isProtocolRequired) {
  248. return xss__namespace.escapeAttrValue("https://".concat(value));
  249. }
  250. return "";
  251. };
  252. /**
  253. * Sanitizes an HTML attribute value.
  254. *
  255. * @param {string} tag The tagname of the HTML element.
  256. * @param {string} attribute The attribute name of the HTML element.
  257. * @param {string} value The raw value to be used for the HTML attribute value.
  258. * @param {XSS.ICSSFilter} [cssFilter] The CSS filter to be used.
  259. * @returns {string} The sanitized attribute value.
  260. * @memberof Sanitizer
  261. */
  262. Sanitizer.prototype.sanitizeHTMLAttribute = function (tag, attribute, value, cssFilter) {
  263. // use the custom safeAttrValue function if provided
  264. if (typeof this.xssFilterOptions.safeAttrValue === 'function') {
  265. // @ts-ignore safeAttrValue does handle undefined cssFilter
  266. return this.xssFilterOptions.safeAttrValue(tag, attribute, value, cssFilter);
  267. }
  268. // otherwise use the default
  269. // @ts-ignore safeAttrValue does handle undefined cssFilter
  270. return xss__namespace.safeAttrValue(tag, attribute, value, cssFilter);
  271. };
  272. /**
  273. * Checks if a value only contains valid HTML.
  274. *
  275. * @param {any} value The value to validate.
  276. * @returns {boolean}
  277. * @memberof Sanitizer
  278. */
  279. Sanitizer.prototype.validate = function (value, options) {
  280. if (options === void 0) { options = {}; }
  281. var sanitized = this.sanitize(value, options);
  282. return {
  283. isValid: value === sanitized,
  284. sanitized: sanitized
  285. };
  286. };
  287. /**
  288. * Extends an object of arrays by by concatenating arrays of the same object
  289. * keys. If the if the previous key's value is not an array, the next key's
  290. * value will replace the previous key. This method is used for extending the
  291. * whiteList in the XSS filter options.
  292. *
  293. * @private
  294. * @param {Array<{}>} objects An array of objects.
  295. * @returns {{}} The extended object.
  296. * @memberof Sanitizer
  297. */
  298. Sanitizer.prototype._extendObjectOfArrays = function (objects) {
  299. var finalObj = {};
  300. objects.forEach(function (obj) {
  301. Object.keys(obj).forEach(function (key) {
  302. if (Array.isArray(obj[key]) && Array.isArray(finalObj[key])) {
  303. finalObj[key] = finalObj[key].concat(obj[key]);
  304. }
  305. else {
  306. finalObj[key] = obj[key];
  307. }
  308. });
  309. });
  310. return finalObj;
  311. };
  312. /**
  313. * Iterate over a plain object or array to deeply sanitize each value.
  314. *
  315. * @private
  316. * @param {object} obj The object to iterate over.
  317. * @returns {(object | null)} The sanitized object.
  318. * @memberof Sanitizer
  319. */
  320. Sanitizer.prototype._iterateOverObject = function (obj, options) {
  321. var _this = this;
  322. if (options === void 0) { options = {}; }
  323. try {
  324. var hasChanged_1 = false;
  325. var changedObj = void 0;
  326. if (Array.isArray(obj)) {
  327. changedObj = obj.reduce(function (prev, value) {
  328. var validation = _this.validate(value, options);
  329. if (validation.isValid) {
  330. return prev.concat([value]);
  331. }
  332. else {
  333. hasChanged_1 = true;
  334. return prev.concat([validation.sanitized]);
  335. }
  336. }, []);
  337. }
  338. else if (!isPlainObject__default["default"](obj)) {
  339. if (options.allowUndefined && typeof obj === "undefined") {
  340. return;
  341. }
  342. return null;
  343. }
  344. else {
  345. var keys = Object.keys(obj);
  346. changedObj = keys.reduce(function (prev, key) {
  347. var value = obj[key];
  348. var validation = _this.validate(value, options);
  349. if (validation.isValid) {
  350. prev[key] = value;
  351. }
  352. else {
  353. hasChanged_1 = true;
  354. prev[key] = validation.sanitized;
  355. }
  356. return prev;
  357. }, {});
  358. }
  359. if (hasChanged_1) {
  360. return changedObj;
  361. }
  362. return obj;
  363. }
  364. catch (err) {
  365. return null;
  366. }
  367. };
  368. /**
  369. * Trim whitespace from the start and ends of a string.
  370. * @param {string} val The string to trim.
  371. * @returns {string} The trimmed string.
  372. */
  373. Sanitizer.prototype._trim = function (val) {
  374. // @ts-ignore This is used by Jest,
  375. // but TypeScript errors since it assumes `trim` is always available.
  376. return String.prototype.trim
  377. ? val.trim()
  378. : val.replace(/(^\s*)|(\s*$)/g, "");
  379. };
  380. return Sanitizer;
  381. }());
  382. exports.Sanitizer = Sanitizer;
  383. exports["default"] = Sanitizer;