Add regex error checking, and display a message to the user when their regular expression has a syntax error. Additionally, use a non-capturing group to surround the user input when `Match whole word` is enabled in case alternates are being used. Finally, add a safety check to highlighting to avoid an infinite loop when zero-length matches happen.

This commit is contained in:
Khloe Leclair 2022-04-16 13:55:54 -04:00
parent e7fd95aafd
commit 446205c7bd
3 changed files with 40 additions and 6 deletions

View File

@ -406,6 +406,12 @@ else if (log?.IsValid == true)
<span role="button" v-bind:class="{ active: !filterInsensitive }" v-on:click="toggleFilterInsensitive" title="Match exact capitalization only.">aA</span> <span role="button" v-bind:class="{ active: !filterInsensitive }" v-on:click="toggleFilterInsensitive" title="Match exact capitalization only.">aA</span>
<span role="button" v-bind:class="{ active: filterUseWord, 'whole-word': true }" v-on:click="toggleFilterWord" title="Match whole word only."><i>“ ”</i></span> <span role="button" v-bind:class="{ active: filterUseWord, 'whole-word': true }" v-on:click="toggleFilterWord" title="Match whole word only."><i>“ ”</i></span>
<span role="button" v-bind:class="{ active: shouldHighlight }" v-on:click="toggleHighlight" title="Highlight matches in the log text.">HL</span> <span role="button" v-bind:class="{ active: shouldHighlight }" v-on:click="toggleHighlight" title="Highlight matches in the log text.">HL</span>
<div
v-if="filterError"
class="filter-error"
>
{{ filterError }}
</div>
</div> </div>
<filter-stats <filter-stats
v-bind:start="start" v-bind:start="start"

View File

@ -187,6 +187,11 @@ table caption {
margin-top: 0.5em; margin-top: 0.5em;
} }
#filters .filter-error {
color: #880000;
}
#filters .filter-error,
#filters .stats { #filters .stats {
margin-top: 0.5em; margin-top: 0.5em;
font-size: 0.75em; font-size: 0.75em;
@ -210,7 +215,7 @@ table caption {
@media (max-width: 1019px) { @media (max-width: 1019px) {
#filters:not(.sticky) { #filters:not(.sticky) {
width: calc(100vw - 3em); width: calc(100vw - 5em);
} }
#filters { #filters {

View File

@ -247,7 +247,8 @@ smapi.logParser = function (state) {
// add the properties we're passing to Vue // add the properties we're passing to Vue
state.totalMessages = state.messages.length; state.totalMessages = state.messages.length;
state.filterText = ""; state.filterText = "";
state.filterRegex = ""; state.filterRegex = null;
state.filterError = null;
state.showContentPacks = true; state.showContentPacks = true;
state.useHighlight = true; state.useHighlight = true;
state.useRegex = false; state.useRegex = false;
@ -506,6 +507,12 @@ smapi.logParser = function (state) {
} }
index = match.index + match[0].length; index = match.index + match[0].length;
// In the event of a zero-length match, forcibly increment
// the last index of the regular expression to ensure we
// aren't stuck in an infinite loop.
if (match[0].length == 0)
filter.lastIndex++;
} }
// Add any trailing text after the last match was found. // Add any trailing text after the last match was found.
@ -857,14 +864,30 @@ smapi.logParser = function (state) {
if (!text || !text.length) { if (!text || !text.length) {
this.filterText = ""; this.filterText = "";
this.filterRegex = null; this.filterRegex = null;
this.filterError = null;
} }
else { else {
if (!state.useRegex) if (!state.useRegex)
text = helpers.escapeRegex(text); text = helpers.escapeRegex(text);
this.filterRegex = new RegExp(
state.useWord ? `\\b${text}\\b` : text, const flags = state.useInsensitive ? "ig" : "g";
state.useInsensitive ? "ig" : "g"
); this.filterError = null;
let regex;
try {
regex = new RegExp(text, flags);
} catch (err) {
regex = null;
this.filterError = err.message;
}
if (regex)
this.filterRegex = state.useWord ?
new RegExp(`\\b(?:${text})\\b`, flags) :
regex;
else
this.filterRegex = null;
} }
this.updateUrl(); this.updateUrl();