mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
FIX: display tables in posts history diff (#6032)
This commit is contained in:
@ -2,9 +2,8 @@ import ModalFunctionality from "discourse/mixins/modal-functionality";
|
|||||||
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
import { categoryBadgeHTML } from "discourse/helpers/category-link";
|
||||||
import computed from "ember-addons/ember-computed-decorators";
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
import { propertyGreaterThan, propertyLessThan } from "discourse/lib/computed";
|
import { propertyGreaterThan, propertyLessThan } from "discourse/lib/computed";
|
||||||
import { on } from "ember-addons/ember-computed-decorators";
|
import { on, observes } from "ember-addons/ember-computed-decorators";
|
||||||
import { default as WhiteLister } from "pretty-text/white-lister";
|
import { sanitizeAsync } from "discourse/lib/text";
|
||||||
import { sanitize } from "pretty-text/sanitizer";
|
|
||||||
|
|
||||||
function customTagArray(fieldName) {
|
function customTagArray(fieldName) {
|
||||||
return function() {
|
return function() {
|
||||||
@ -246,15 +245,23 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||||||
return this.get("model.title_changes." + viewMode);
|
return this.get("model.title_changes." + viewMode);
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("viewMode", "model.body_changes")
|
@observes("viewMode", "model.body_changes")
|
||||||
bodyDiff(viewMode) {
|
bodyDiffChanged() {
|
||||||
|
const viewMode = this.get("viewMode");
|
||||||
const html = this.get(`model.body_changes.${viewMode}`);
|
const html = this.get(`model.body_changes.${viewMode}`);
|
||||||
if (viewMode === "side_by_side_markdown") {
|
if (viewMode === "side_by_side_markdown") {
|
||||||
return html;
|
this.set("bodyDiff", html);
|
||||||
} else {
|
} else {
|
||||||
const whiteLister = new WhiteLister({ features: { editHistory: true } });
|
const opts = {
|
||||||
whiteLister.whiteListFeature("editHistory", { custom: () => true });
|
features: { editHistory: true },
|
||||||
return sanitize(html, whiteLister);
|
whiteListed: {
|
||||||
|
editHistory: { custom: (tag, attr) => attr === "class" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return sanitizeAsync(html, opts).then(result =>
|
||||||
|
this.set("bodyDiff", result)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -25,25 +25,39 @@ function getOpts(opts) {
|
|||||||
|
|
||||||
// Use this to easily create a pretty text instance with proper options
|
// Use this to easily create a pretty text instance with proper options
|
||||||
export function cook(text, options) {
|
export function cook(text, options) {
|
||||||
return new Handlebars.SafeString(new PrettyText(getOpts(options)).cook(text));
|
return new Handlebars.SafeString(createPrettyText(options).cook(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
// everything should eventually move to async API and this should be renamed
|
// everything should eventually move to async API and this should be renamed
|
||||||
// cook
|
// cook
|
||||||
export function cookAsync(text, options) {
|
export function cookAsync(text, options) {
|
||||||
if (Discourse.MarkdownItURL) {
|
return loadMarkdownIt().then(() => cook(text, options));
|
||||||
return loadScript(Discourse.MarkdownItURL)
|
|
||||||
.then(() => cook(text, options))
|
|
||||||
.catch(e => Ember.Logger.error(e));
|
|
||||||
} else {
|
|
||||||
return Ember.RSVP.Promise.resolve(cook(text));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sanitize(text, options) {
|
export function sanitize(text, options) {
|
||||||
return textSanitize(text, new WhiteLister(options));
|
return textSanitize(text, new WhiteLister(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sanitizeAsync(text, options) {
|
||||||
|
return new loadMarkdownIt().then(() => {
|
||||||
|
return createPrettyText(options).sanitize(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMarkdownIt() {
|
||||||
|
if (Discourse.MarkdownItURL) {
|
||||||
|
return loadScript(Discourse.MarkdownItURL).catch(e =>
|
||||||
|
Ember.Logger.error(e)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Ember.RSVP.Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPrettyText(options) {
|
||||||
|
return new PrettyText(getOpts(options));
|
||||||
|
}
|
||||||
|
|
||||||
function emojiOptions() {
|
function emojiOptions() {
|
||||||
const siteSettings = Discourse.__container__.lookup("site-settings:main");
|
const siteSettings = Discourse.__container__.lookup("site-settings:main");
|
||||||
if (!siteSettings.enable_emoji) {
|
if (!siteSettings.enable_emoji) {
|
||||||
|
@ -210,6 +210,10 @@ export function setup(opts, siteSettings, state) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Object.entries(state.whiteListed || {}).forEach(entry => {
|
||||||
|
whiteListed.push(entry);
|
||||||
|
});
|
||||||
|
|
||||||
optionCallbacks.forEach(([, callback]) => {
|
optionCallbacks.forEach(([, callback]) => {
|
||||||
callback(opts, siteSettings, state);
|
callback(opts, siteSettings, state);
|
||||||
});
|
});
|
||||||
|
@ -22,10 +22,59 @@ QUnit.test("displayEdit", function(assert) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
HistoryController.set("model.current_revision", 2);
|
HistoryController.set("model.current_revision", 2);
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
HistoryController.get("displayEdit"),
|
HistoryController.get("displayEdit"),
|
||||||
false,
|
false,
|
||||||
"it should only display the edit button on the latest revision"
|
"it should only display the edit button on the latest revision"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const html = `<div class="revision-content">
|
||||||
|
<p><img src="/uploads/default/original/1X/6b963ffc13cb0c053bbb90c92e99d4fe71b286ef.jpg" alt="" class="diff-del"><img/src=x onerror=alert(document.domain)>" width="276" height="183"></p>
|
||||||
|
</div>
|
||||||
|
<table background="javascript:alert(\"HACKEDXSS\")">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th style="text-align:left">Test</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td background="javascript:alert('HACKEDXSS')">Osama</td>
|
||||||
|
<td style="text-align:right">Testing</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
const expectedOutput = `<div class="revision-content">
|
||||||
|
<p><img src="/uploads/default/original/1X/6b963ffc13cb0c053bbb90c92e99d4fe71b286ef.jpg" alt class="diff-del">" width="276" height="183"></p>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th style="text-align:left">Test</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Osama</td>
|
||||||
|
<td style="text-align:right">Testing</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
HistoryController.setProperties({
|
||||||
|
viewMode: "side_by_side",
|
||||||
|
model: {
|
||||||
|
body_changes: {
|
||||||
|
side_by_side: html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
HistoryController.bodyDiffChanged().then(() => {
|
||||||
|
const output = HistoryController.get("bodyDiff");
|
||||||
|
assert.equal(output, expectedOutput, "it keeps safe HTML");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user