FIX: Various watched words improvements
- Client-side censoring fixed for non-chrome browsers. (Regular expression rewritten to avoid lookback) - Regex generation is now done on the server, to reduce repeated logic, and make it easier to extend in plugins - Censor tests are moved to ruby, to ensure everything works end-to-end - If "watched words regular expressions" is enabled, warn the admin when the generated regex is invalid
This commit is contained in:
@@ -13,7 +13,7 @@ function getOpts(opts) {
|
||||
{
|
||||
getURL: Discourse.getURLWithCDN,
|
||||
currentUser: Discourse.__container__.lookup("current-user:main"),
|
||||
censoredWords: site.censored_words,
|
||||
censoredRegexp: site.censored_regexp,
|
||||
siteSettings,
|
||||
formatUsername
|
||||
},
|
||||
|
||||
@@ -97,7 +97,7 @@ const Topic = RestModel.extend({
|
||||
fancyTitle(title) {
|
||||
let fancyTitle = censor(
|
||||
emojiUnescape(title || ""),
|
||||
Discourse.Site.currentProp("censored_words")
|
||||
Discourse.Site.currentProp("censored_regexp")
|
||||
);
|
||||
|
||||
if (Discourse.SiteSettings.support_mixed_text_direction) {
|
||||
|
||||
@@ -1,75 +1,19 @@
|
||||
function escapeRegexp(text) {
|
||||
return text.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&").replace(/\*/g, "S*");
|
||||
}
|
||||
export function censorFn(regexpString, replacementLetter) {
|
||||
if (regexpString) {
|
||||
let censorRegexp = new RegExp(regexpString, "ig");
|
||||
replacementLetter = replacementLetter || "■";
|
||||
|
||||
function createCensorRegexp(patterns) {
|
||||
return new RegExp(`((?<!\\w)(?:${patterns.join("|")}))(?!\\w)`, "ig");
|
||||
}
|
||||
|
||||
export function censorFn(
|
||||
censoredWords,
|
||||
replacementLetter,
|
||||
watchedWordsRegularExpressions
|
||||
) {
|
||||
let patterns = [];
|
||||
|
||||
replacementLetter = replacementLetter || "■";
|
||||
|
||||
if (censoredWords && censoredWords.length) {
|
||||
patterns = censoredWords.split("|");
|
||||
if (!watchedWordsRegularExpressions) {
|
||||
patterns = patterns.map(t => `(${escapeRegexp(t)})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (patterns.length) {
|
||||
let censorRegexp;
|
||||
|
||||
try {
|
||||
if (watchedWordsRegularExpressions) {
|
||||
censorRegexp = new RegExp(
|
||||
"((?:" + patterns.join("|") + "))(?![^\\(]*\\))",
|
||||
"ig"
|
||||
return function(text) {
|
||||
text = text.replace(censorRegexp, (fullMatch, ...groupMatches) => {
|
||||
const stringMatch = groupMatches.find(g => typeof g === "string");
|
||||
return fullMatch.replace(
|
||||
stringMatch,
|
||||
new Array(stringMatch.length + 1).join(replacementLetter)
|
||||
);
|
||||
} else {
|
||||
censorRegexp = createCensorRegexp(patterns);
|
||||
}
|
||||
});
|
||||
|
||||
if (censorRegexp) {
|
||||
return function(text) {
|
||||
let original = text;
|
||||
|
||||
try {
|
||||
let m = censorRegexp.exec(text);
|
||||
const fourCharReplacement = new Array(5).join(replacementLetter);
|
||||
|
||||
while (m && m[0]) {
|
||||
if (m[0].length > original.length) {
|
||||
return original;
|
||||
} // regex is dangerous
|
||||
if (watchedWordsRegularExpressions) {
|
||||
text = text.replace(censorRegexp, fourCharReplacement);
|
||||
} else {
|
||||
const replacement = new Array(m[0].length + 1).join(
|
||||
replacementLetter
|
||||
);
|
||||
text = text.replace(
|
||||
createCensorRegexp([escapeRegexp(m[0])]),
|
||||
replacement
|
||||
);
|
||||
}
|
||||
m = censorRegexp.exec(text);
|
||||
}
|
||||
|
||||
return text;
|
||||
} catch (e) {
|
||||
return original;
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
// fall through
|
||||
}
|
||||
return text;
|
||||
};
|
||||
}
|
||||
|
||||
return function(t) {
|
||||
@@ -77,6 +21,6 @@ export function censorFn(
|
||||
};
|
||||
}
|
||||
|
||||
export function censor(text, censoredWords, replacementLetter) {
|
||||
return censorFn(censoredWords, replacementLetter)(text);
|
||||
export function censor(text, censoredRegexp, replacementLetter) {
|
||||
return censorFn(censoredRegexp, replacementLetter)(text);
|
||||
}
|
||||
|
||||
@@ -29,15 +29,11 @@ export function setup(helper) {
|
||||
});
|
||||
|
||||
helper.registerPlugin(md => {
|
||||
const words = md.options.discourse.censoredWords;
|
||||
const censoredRegexp = md.options.discourse.censoredRegexp;
|
||||
|
||||
if (words && words.length > 0) {
|
||||
if (censoredRegexp) {
|
||||
const replacement = String.fromCharCode(9632);
|
||||
const censor = censorFn(
|
||||
words,
|
||||
replacement,
|
||||
md.options.discourse.watchedWordsRegularExpressions
|
||||
);
|
||||
const censor = censorFn(censoredRegexp, replacement);
|
||||
md.core.ruler.push("censored", state => censorTree(state, censor));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ export function buildOptions(state) {
|
||||
lookupUploadUrls,
|
||||
previewing,
|
||||
linkify,
|
||||
censoredWords,
|
||||
censoredRegexp,
|
||||
disableEmojis
|
||||
} = state;
|
||||
|
||||
@@ -67,7 +67,7 @@ export function buildOptions(state) {
|
||||
formatUsername,
|
||||
emojiUnicodeReplacer,
|
||||
lookupUploadUrls,
|
||||
censoredWords,
|
||||
censoredRegexp,
|
||||
allowedHrefSchemes: siteSettings.allowed_href_schemes
|
||||
? siteSettings.allowed_href_schemes.split("|")
|
||||
: null,
|
||||
|
||||
Reference in New Issue
Block a user