mirror of
https://github.com/DavidAnson/markdownlint.git
synced 2025-12-16 14:00:13 +01:00
Replace helpers.urlRe with helpers.urlFe to address "innefficient regular expression" CodeQL alert, introduce function expressions as an alternative, test more inputs.
This commit is contained in:
parent
485c63c579
commit
8d6e0b5419
5 changed files with 277 additions and 35 deletions
|
|
@ -23,9 +23,6 @@ const htmlElementRe = /<(([A-Za-z][A-Za-z0-9-]*)(?:\s[^`>]*)?)\/?>/g;
|
|||
module.exports.htmlElementRe = htmlElementRe;
|
||||
|
||||
// Regular expressions for range matching
|
||||
module.exports.urlRe =
|
||||
// eslint-disable-next-line max-len
|
||||
/(?:http|ftp)s?:\/\/(?:[^\s()<>\]"'`]|\([^\s<>\]"'`]*\))*\b(?:[-#/]|\([^\s<>\]"'`]*\))*/ig;
|
||||
module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/;
|
||||
module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/;
|
||||
|
||||
|
|
@ -1187,3 +1184,110 @@ function expandTildePath(file, os) {
|
|||
return homedir ? file.replace(/^~($|\/|\\)/, `${homedir}$1`) : file;
|
||||
}
|
||||
module.exports.expandTildePath = expandTildePath;
|
||||
|
||||
/**
|
||||
* RegExp.exec-style implementation of function expressions.
|
||||
*
|
||||
* @param {Function} funcExp Function that takes string and returns
|
||||
* [index, length] or null.
|
||||
* @param {string} input String to search.
|
||||
* @returns {string[] | null} RegExp.exec-style [match] with an index property.
|
||||
*/
|
||||
function funcExpExec(funcExp, input) {
|
||||
// Start or resume match
|
||||
// @ts-ignore
|
||||
const lastIndex = funcExp.lastIndex || 0;
|
||||
const result = funcExp(input.slice(lastIndex));
|
||||
if (result) {
|
||||
// Update lastIndex and return match
|
||||
const [ subIndex, length ] = result;
|
||||
const index = lastIndex + subIndex;
|
||||
// @ts-ignore
|
||||
funcExp.lastIndex = index + length;
|
||||
const match = [ input.slice(index, index + length) ];
|
||||
// @ts-ignore
|
||||
match.index = index;
|
||||
return match;
|
||||
}
|
||||
// Reset lastIndex and return no match
|
||||
// @ts-ignore
|
||||
funcExp.lastIndex = 0;
|
||||
return null;
|
||||
}
|
||||
module.exports.funcExpExec = funcExpExec;
|
||||
|
||||
const urlFeProtocolRe = /(?:http|ftp)s?:\/\//i;
|
||||
const urlFeAutolinkTerminalsRe = / |$/;
|
||||
const urlFeBareTerminalsRe = /[ ,!`'"\]]|$/;
|
||||
const urlFeNonTerminalsRe = "-#/";
|
||||
const urlFePunctuationRe = /\p{Punctuation}/u;
|
||||
const urlFePrefixToPostfix = new Map([
|
||||
[ " ", " " ],
|
||||
[ "`", "`" ],
|
||||
[ "'", "'" ],
|
||||
[ "\"", "\"" ],
|
||||
[ "‘", "’" ],
|
||||
[ "“", "”" ],
|
||||
[ "«", "»" ],
|
||||
[ "*", "*" ],
|
||||
[ "_", "_" ],
|
||||
[ "(", ")" ],
|
||||
[ "[", "]" ],
|
||||
[ "{", "}" ],
|
||||
[ "<", ">" ],
|
||||
[ ">", "<" ]
|
||||
]);
|
||||
|
||||
/**
|
||||
* Function expression that matches URLs.
|
||||
*
|
||||
* @param {string} input Substring to search for a URL.
|
||||
* @returns {Array | null} [index, length] of URL or null.
|
||||
*/
|
||||
function urlFe(input) {
|
||||
// Find start of URL by searching for protocol
|
||||
const match = input.match(urlFeProtocolRe);
|
||||
if (match) {
|
||||
// Look for matching pre/postfix characters (ex: <...>)
|
||||
const start = match.index || 0;
|
||||
const length = match[0].length;
|
||||
const prefix = input[start - 1] || " ";
|
||||
const postfix = urlFePrefixToPostfix.get(prefix);
|
||||
// @ts-ignore
|
||||
let endPostfix = input.indexOf(postfix, start + length);
|
||||
if (endPostfix === -1) {
|
||||
endPostfix = input.length;
|
||||
}
|
||||
// Look for characters that terminate a URL
|
||||
const terminalsRe =
|
||||
(prefix === "<") ? urlFeAutolinkTerminalsRe : urlFeBareTerminalsRe;
|
||||
const endTerminal = start + input.slice(start).search(terminalsRe);
|
||||
// Determine tentative end of URL
|
||||
let end = Math.min(endPostfix, endTerminal);
|
||||
if (prefix === " ") {
|
||||
// If the URL used " " as pre/postfix characters, trim the end
|
||||
if (input[end - 1] === ")") {
|
||||
// Trim any ")" beyond the last "(...)" pair
|
||||
const lastOpenParen = input.lastIndexOf("(", end - 2);
|
||||
if (lastOpenParen <= start) {
|
||||
end--;
|
||||
} else {
|
||||
const nextCloseParen = input.indexOf(")", lastOpenParen + 1);
|
||||
end = nextCloseParen + 1;
|
||||
}
|
||||
} else {
|
||||
// Trim unwanted punctuation
|
||||
while (
|
||||
!urlFeNonTerminalsRe.includes(input[end - 1]) &&
|
||||
urlFePunctuationRe.test(input[end - 1])
|
||||
) {
|
||||
end--;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [ start, end - start ];
|
||||
}
|
||||
// No match
|
||||
return null;
|
||||
}
|
||||
module.exports.urlFe = urlFe;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue