index.js 15 KB

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