/******************************************************************************* uBlock Origin - a comprehensive, efficient content blocker Copyright (C) 2020-present Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see {http://www.gnu.org/licenses/}. Home: https://github.com/gorhill/uBlock */ /******************************************************************************/ export class ArglistParser { constructor(separatorChar = ',', mustQuote = false) { this.separatorChar = this.actualSeparatorChar = separatorChar; this.separatorCode = this.actualSeparatorCode = separatorChar.charCodeAt(0); this.mustQuote = mustQuote; this.quoteBeg = 0; this.quoteEnd = 0; this.argBeg = 0; this.argEnd = 0; this.separatorBeg = 0; this.separatorEnd = 0; this.transform = false; this.failed = false; this.reWhitespaceStart = /^\s+/; this.reWhitespaceEnd = /\s+$/; this.reOddTrailingEscape = /(?:^|[^\\])(?:\\\\)*\\$/; this.reTrailingEscapeChars = /\\+$/; } nextArg(pattern, beg = 0) { const len = pattern.length; this.quoteBeg = beg + this.leftWhitespaceCount(pattern.slice(beg)); this.failed = false; const qc = pattern.charCodeAt(this.quoteBeg); if ( qc === 0x22 /* " */ || qc === 0x27 /* ' */ || qc === 0x60 /* ` */ ) { this.indexOfNextArgSeparator(pattern, qc); if ( this.argEnd !== len ) { this.quoteEnd = this.argEnd + 1; this.separatorBeg = this.separatorEnd = this.quoteEnd; this.separatorEnd += this.leftWhitespaceCount(pattern.slice(this.quoteEnd)); if ( this.separatorEnd === len ) { return this; } if ( pattern.charCodeAt(this.separatorEnd) === this.separatorCode ) { this.separatorEnd += 1; return this; } } } this.indexOfNextArgSeparator(pattern, this.separatorCode); this.separatorBeg = this.separatorEnd = this.argEnd; if ( this.separatorBeg < len ) { this.separatorEnd += 1; } this.argEnd -= this.rightWhitespaceCount(pattern.slice(0, this.separatorBeg)); this.quoteEnd = this.argEnd; if ( this.mustQuote ) { this.failed = true; } return this; } normalizeArg(s, char = '') { if ( char === '' ) { char = this.actualSeparatorChar; } let out = ''; let pos = 0; while ( (pos = s.lastIndexOf(char)) !== -1 ) { out = s.slice(pos) + out; s = s.slice(0, pos); const match = this.reTrailingEscapeChars.exec(s); if ( match === null ) { continue; } const tail = (match[0].length & 1) !== 0 ? match[0].slice(0, -1) : match[0]; out = tail + out; s = s.slice(0, -match[0].length); } if ( out === '' ) { return s; } return s + out; } leftWhitespaceCount(s) { const match = this.reWhitespaceStart.exec(s); return match === null ? 0 : match[0].length; } rightWhitespaceCount(s) { const match = this.reWhitespaceEnd.exec(s); return match === null ? 0 : match[0].length; } indexOfNextArgSeparator(pattern, separatorCode) { this.argBeg = this.argEnd = separatorCode !== this.separatorCode ? this.quoteBeg + 1 : this.quoteBeg; this.transform = false; if ( separatorCode !== this.actualSeparatorCode ) { this.actualSeparatorCode = separatorCode; this.actualSeparatorChar = String.fromCharCode(separatorCode); } while ( this.argEnd < pattern.length ) { const pos = pattern.indexOf(this.actualSeparatorChar, this.argEnd); if ( pos === -1 ) { return (this.argEnd = pattern.length); } if ( this.reOddTrailingEscape.test(pattern.slice(0, pos)) === false ) { return (this.argEnd = pos); } this.transform = true; this.argEnd = pos + 1; } } }