From 260cb392fc1ee91d0b749cff08d1c8d54b230bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 13 May 2024 15:57:41 +0000 Subject: [PATCH] Upgrade Trix to 2.1.1 to fix [CVE-2024-34341][1] [1]: https://github.com/basecamp/trix/security/advisories/GHSA-qjqp-xr96-cj99 --- actiontext/CHANGELOG.md | 5 + actiontext/app/assets/javascripts/trix.js | 122 ++++++++++++++++----- actiontext/app/assets/stylesheets/trix.css | 4 +- 3 files changed, 100 insertions(+), 31 deletions(-) diff --git a/actiontext/CHANGELOG.md b/actiontext/CHANGELOG.md index ad6ae49ddf40..c095ea658cf2 100644 --- a/actiontext/CHANGELOG.md +++ b/actiontext/CHANGELOG.md @@ -1,3 +1,8 @@ +* Upgrade Trix to 1.3.2 to fix [CVE-2024-34341](https://github.com/basecamp/trix/security/advisories/GHSA-qjqp-xr96-cj99). + + *Rafael Mendonça França* + + ## Rails 7.1.3.2 (February 21, 2024) ## * No changes. diff --git a/actiontext/app/assets/javascripts/trix.js b/actiontext/app/assets/javascripts/trix.js index 302962dd1331..e9457dfec2c8 100644 --- a/actiontext/app/assets/javascripts/trix.js +++ b/actiontext/app/assets/javascripts/trix.js @@ -1,6 +1,6 @@ /* -Trix 2.0.7 -Copyright © 2023 37signals, LLC +Trix 2.1.1 +Copyright © 2024 37signals, LLC */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : @@ -9,7 +9,7 @@ Copyright © 2023 37signals, LLC })(this, (function () { 'use strict'; var name = "trix"; - var version = "2.0.7"; + var version = "2.1.1"; var description = "A rich text editor for everyday writing"; var main = "dist/trix.umd.min.js"; var module = "dist/trix.esm.min.js"; @@ -17,6 +17,7 @@ Copyright © 2023 37signals, LLC var files = [ "dist/*.css", "dist/*.js", + "dist/*.map", "src/{inspector,trix}/*.js" ]; var repository = { @@ -130,6 +131,7 @@ Copyright © 2023 37signals, LLC code: { tagName: "pre", terminal: true, + htmlAttributes: ["language"], text: { plaintext: true } @@ -1215,7 +1217,7 @@ $\ no-useless-escape, */ const normalizeSpaces = string => string.replace(new RegExp("".concat(ZERO_WIDTH_SPACE), "g"), "").replace(new RegExp("".concat(NON_BREAKING_SPACE), "g"), " "); - const normalizeNewlines = string => string.replace(/\r\n/g, "\n"); + const normalizeNewlines = string => string.replace(/\r\n?/g, "\n"); const breakableWhitespacePattern = new RegExp("[^\\S".concat(NON_BREAKING_SPACE, "]")); const squishBreakableWhitespace = string => string // Replace all breakable whitespace characters with a space @@ -2144,20 +2146,28 @@ $\ } } createContainerElement(depth) { - let attributes, className; + const attributes = {}; + let className; const attributeName = this.attributes[depth]; const { - tagName + tagName, + htmlAttributes = [] } = getBlockConfig(attributeName); if (depth === 0 && this.block.isRTL()) { - attributes = { + Object.assign(attributes, { dir: "rtl" - }; + }); } if (attributeName === "attachmentGallery") { const size = this.block.getBlockBreakPosition(); className = "".concat(css$1.attachmentGallery, " ").concat(css$1.attachmentGallery, "--").concat(size); } + Object.entries(this.block.htmlAttributes).forEach(_ref => { + let [name, value] = _ref; + if (htmlAttributes.includes(name)) { + attributes[name] = value; + } + }); return makeElement({ tagName, className, @@ -5828,28 +5838,29 @@ $\ class Block extends TrixObject { static fromJSON(blockJSON) { const text = Text.fromJSON(blockJSON.text); - return new this(text, blockJSON.attributes); + return new this(text, blockJSON.attributes, blockJSON.htmlAttributes); } - constructor(text, attributes) { + constructor(text, attributes, htmlAttributes) { super(...arguments); this.text = applyBlockBreakToText(text || new Text()); this.attributes = attributes || []; + this.htmlAttributes = htmlAttributes || {}; } isEmpty() { return this.text.isBlockBreak(); } isEqualTo(block) { if (super.isEqualTo(block)) return true; - return this.text.isEqualTo(block === null || block === void 0 ? void 0 : block.text) && arraysAreEqual(this.attributes, block === null || block === void 0 ? void 0 : block.attributes); + return this.text.isEqualTo(block === null || block === void 0 ? void 0 : block.text) && arraysAreEqual(this.attributes, block === null || block === void 0 ? void 0 : block.attributes) && objectsAreEqual(this.htmlAttributes, block === null || block === void 0 ? void 0 : block.htmlAttributes); } copyWithText(text) { - return new Block(text, this.attributes); + return new Block(text, this.attributes, this.htmlAttributes); } copyWithoutText() { return this.copyWithText(null); } copyWithAttributes(attributes) { - return new Block(this.text, attributes); + return new Block(this.text, attributes, this.htmlAttributes); } copyWithoutAttributes() { return this.copyWithAttributes(null); @@ -5866,6 +5877,12 @@ $\ const attributes = this.attributes.concat(expandAttribute(attribute)); return this.copyWithAttributes(attributes); } + addHTMLAttribute(attribute, value) { + const htmlAttributes = Object.assign({}, this.htmlAttributes, { + [attribute]: value + }); + return new Block(this.text, this.attributes, htmlAttributes); + } removeAttribute(attribute) { const { listAttribute @@ -5962,7 +5979,8 @@ $\ toJSON() { return { text: this.text, - attributes: this.attributes + attributes: this.attributes, + htmlAttributes: this.htmlAttributes }; } @@ -6325,6 +6343,11 @@ $\ const range = this.getRangeOfAttachment(attachment); return this.removeAttributeAtRange(attribute, range); } + setHTMLAttributeAtPosition(position, name, value) { + const block = this.getBlockAtPosition(position); + const updatedBlock = block.addHTMLAttribute(name, value); + return this.replaceBlock(block, updatedBlock); + } insertBlockBreakAtRange(range) { let blocks; range = normalizeRange(range); @@ -6793,9 +6816,9 @@ $\ return attributes; }; - const DEFAULT_ALLOWED_ATTRIBUTES = "style href src width height class".split(" "); + const DEFAULT_ALLOWED_ATTRIBUTES = "style href src width height language class".split(" "); const DEFAULT_FORBIDDEN_PROTOCOLS = "javascript:".split(" "); - const DEFAULT_FORBIDDEN_ELEMENTS = "script iframe form".split(" "); + const DEFAULT_FORBIDDEN_ELEMENTS = "script iframe form noscript".split(" "); class HTMLSanitizer extends BasicObject { static sanitize(html, options) { const sanitizer = new this(html, options); @@ -6923,15 +6946,21 @@ $\ }; const blockForAttributes = function () { let attributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + let htmlAttributes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const text = []; return { text, - attributes + attributes, + htmlAttributes }; }; const parseTrixDataAttribute = (element, name) => { try { - return JSON.parse(element.getAttribute("data-trix-".concat(name))); + const data = JSON.parse(element.getAttribute("data-trix-".concat(name))); + if (data.contentType === "text/html" && data.content) { + data.content = HTMLSanitizer.sanitize(data.content).getHTML(); + } + return data; } catch (error) { return {}; } @@ -7027,8 +7056,9 @@ $\ } else if (element === this.containerElement || this.isBlockElement(element)) { var _this$currentBlock; const attributes = this.getBlockAttributes(element); + const htmlAttributes = this.getBlockHTMLAttributes(element); if (!arraysAreEqual(attributes, (_this$currentBlock = this.currentBlock) === null || _this$currentBlock === void 0 ? void 0 : _this$currentBlock.attributes)) { - this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element); + this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element, htmlAttributes); this.currentBlockElement = element; } } @@ -7039,9 +7069,10 @@ $\ if (elementIsBlockElement && !this.isBlockElement(element.firstChild)) { if (!this.isInsignificantTextNode(element.firstChild) || !this.isBlockElement(element.firstElementChild)) { const attributes = this.getBlockAttributes(element); + const htmlAttributes = this.getBlockHTMLAttributes(element); if (element.firstChild) { if (!(currentBlockContainsElement && arraysAreEqual(attributes, this.currentBlock.attributes))) { - this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element); + this.currentBlock = this.appendBlockForAttributesWithElement(attributes, element, htmlAttributes); this.currentBlockElement = element; } else { return this.appendStringWithAttributes("\n"); @@ -7129,8 +7160,9 @@ $\ // Document construction appendBlockForAttributesWithElement(attributes, element) { + let htmlAttributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; this.blockElements.push(element); - const block = blockForAttributes(attributes); + const block = blockForAttributes(attributes, htmlAttributes); this.blocks.push(block); return block; } @@ -7235,6 +7267,17 @@ $\ } return attributes$1.reverse(); } + getBlockHTMLAttributes(element) { + const attributes$1 = {}; + const blockConfig = Object.values(attributes).find(settings => settings.tagName === tagName(element)); + const allowedAttributes = (blockConfig === null || blockConfig === void 0 ? void 0 : blockConfig.htmlAttributes) || []; + allowedAttributes.forEach(attribute => { + if (element.hasAttribute(attribute)) { + attributes$1[attribute] = element.getAttribute(attribute); + } + }); + return attributes$1; + } findBlockElementAncestors(element) { const ancestors = []; while (element && element !== this.containerElement) { @@ -7830,6 +7873,15 @@ $\ return this.notifyDelegateOfCurrentAttributesChange(); } } + setHTMLAtributeAtPosition(position, attributeName, value) { + var _getBlockConfig; + const block = this.document.getBlockAtPosition(position); + const allowedHTMLAttributes = (_getBlockConfig = getBlockConfig(block.getLastAttribute())) === null || _getBlockConfig === void 0 ? void 0 : _getBlockConfig.htmlAttributes; + if (block && allowedHTMLAttributes !== null && allowedHTMLAttributes !== void 0 && allowedHTMLAttributes.includes(attributeName)) { + const newDocument = this.document.setHTMLAttributeAtPosition(position, attributeName, value); + this.setDocument(newDocument); + } + } setTextAttribute(attributeName, value) { const selectedRange = this.getSelectedRange(); if (!selectedRange) return; @@ -7877,10 +7929,10 @@ $\ return ((_this$getBlock = this.getBlock()) === null || _this$getBlock === void 0 ? void 0 : _this$getBlock.getNestingLevel()) > 0; } canIncreaseNestingLevel() { - var _getBlockConfig; + var _getBlockConfig2; const block = this.getBlock(); if (!block) return; - if ((_getBlockConfig = getBlockConfig(block.getLastNestableAttribute())) !== null && _getBlockConfig !== void 0 && _getBlockConfig.listAttribute) { + if ((_getBlockConfig2 = getBlockConfig(block.getLastNestableAttribute())) !== null && _getBlockConfig2 !== void 0 && _getBlockConfig2.listAttribute) { const previousBlock = this.getPreviousBlock(); if (previousBlock) { return arrayStartsWith(previousBlock.getListItemAttributes(), block.getListItemAttributes()); @@ -8521,6 +8573,11 @@ $\ return this.composition.removeCurrentAttribute(name); } + // HTML attributes + setHTMLAtributeAtPosition(position, name, value) { + this.composition.setHTMLAtributeAtPosition(position, name, value); + } + // Nesting level canDecreaseNestingLevel() { @@ -10941,8 +10998,12 @@ $\ }); }, insertReplacementText() { - return this.insertString(this.event.dataTransfer.getData("text/plain"), { - updatePosition: false + const replacement = this.event.dataTransfer.getData("text/plain"); + const domRange = this.event.getTargetRanges()[0]; + this.withTargetDOMRange(domRange, () => { + this.insertString(replacement, { + updatePosition: false + }); }); }, insertText() { @@ -11064,7 +11125,7 @@ $\ return this.toggleDialog(actionName); } else { var _this$delegate2; - return (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 ? void 0 : _this$delegate2.toolbarDidInvokeAction(actionName); + return (_this$delegate2 = this.delegate) === null || _this$delegate2 === void 0 ? void 0 : _this$delegate2.toolbarDidInvokeAction(actionName, element); } } didClickAttributeButton(event, element) { @@ -11509,8 +11570,8 @@ $\ }); } } - toolbarDidInvokeAction(actionName) { - return this.invokeAction(actionName); + toolbarDidInvokeAction(actionName, invokingElement) { + return this.invokeAction(actionName, invokingElement); } toolbarDidToggleAttribute(attributeName) { this.recordFormattingUndoEntry(attributeName); @@ -11579,10 +11640,11 @@ $\ return !!((_this$actions$actionN = this.actions[actionName]) !== null && _this$actions$actionN !== void 0 && (_this$actions$actionN = _this$actions$actionN.test) !== null && _this$actions$actionN !== void 0 && _this$actions$actionN.call(this)); } } - invokeAction(actionName) { + invokeAction(actionName, invokingElement) { if (this.actionIsExternal(actionName)) { return this.notifyEditorElement("action-invoke", { - actionName + actionName, + invokingElement }); } else { var _this$actions$actionN2; diff --git a/actiontext/app/assets/stylesheets/trix.css b/actiontext/app/assets/stylesheets/trix.css index ec555abfc7e5..b07d20644fdc 100644 --- a/actiontext/app/assets/stylesheets/trix.css +++ b/actiontext/app/assets/stylesheets/trix.css @@ -334,7 +334,9 @@ trix-editor .attachment__metadata { white-space: nowrap; } .trix-content { - line-height: 1.5; } + line-height: 1.5; + overflow-wrap: break-word; + word-break: break-word; } .trix-content * { box-sizing: border-box; margin: 0;