This commit is contained in:
gorhill 2016-12-26 11:35:37 -05:00
parent 251bbe0f43
commit 6e458dca5c
5 changed files with 125 additions and 37 deletions

View File

@ -5,12 +5,36 @@ div > p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
#whitelist { #whitelist {
box-sizing: border-box; border: 1px solid gray;
height: 60vh; height: 60vh;
text-align: left; margin: 0;
padding: 1px;
position: relative;
resize: vertical;
}
#whitelist.invalid {
border-color: red;
}
#whitelist textarea {
border: none;
box-sizing: border-box;
height: 100%;
padding: 0.4em;
resize: none;
text-align: left;
white-space: pre; white-space: pre;
width: 100%; width: 100%;
} }
#whitelist.bad { #whitelist textarea + div {
background-color: #fee; background-color: red;
} bottom: 0;
color: white;
display: none;
padding: 2px 4px;
pointer-events: none;
position: absolute;
right: 0;
}
#whitelist.invalid textarea + div {
display: block;
}

View File

@ -1011,6 +1011,10 @@ var onMessage = function(request, sender, callback) {
response = getRules(); response = getRules();
break; break;
case 'validateWhitelistString':
response = µb.validateWhitelistString(request.raw);
break;
case 'writeHiddenSettings': case 'writeHiddenSettings':
µb.hiddenSettingsFromString(request.content); µb.hiddenSettingsFromString(request.content);
break; break;

View File

@ -194,9 +194,7 @@ var matchBucket = function(url, hostname, bucket, start) {
µBlock.whitelistFromString = function(s) { µBlock.whitelistFromString = function(s) {
var whitelist = Object.create(null), var whitelist = Object.create(null),
reInvalidHostname = /[^a-z0-9.\-\[\]:]/, lineIter = new this.LineIterator(s),
reHostnameExtractor = /([a-z0-9\[][a-z0-9.\-]*[a-z0-9\]])(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/,
lines = s.split(/[\n\r]+/),
line, matches, key, directive, re; line, matches, key, directive, re;
// Comment bucket must always be ready to be used. // Comment bucket must always be ready to be used.
@ -205,8 +203,9 @@ var matchBucket = function(url, hostname, bucket, start) {
// New set of directives, scrap cached data. // New set of directives, scrap cached data.
directiveToRegexpMap.clear(); directiveToRegexpMap.clear();
for ( var i = 0; i < lines.length; i++ ) { while ( !lineIter.eot() ) {
line = lines[i].trim(); line = lineIter.next().trim();
// https://github.com/gorhill/uBlock/issues/171 // https://github.com/gorhill/uBlock/issues/171
// Skip empty lines // Skip empty lines
if ( line === '' ) { if ( line === '' ) {
@ -228,7 +227,7 @@ var matchBucket = function(url, hostname, bucket, start) {
} }
} }
// Regex-based (ensure it is valid) // Regex-based (ensure it is valid)
else if ( line.startsWith('/') && line.endsWith('/') ) { else if ( line.length > 2 && line.startsWith('/') && line.endsWith('/') ) {
key = '//'; key = '//';
directive = line; directive = line;
try { try {
@ -267,6 +266,28 @@ var matchBucket = function(url, hostname, bucket, start) {
return whitelist; return whitelist;
}; };
µBlock.validateWhitelistString = function(s) {
var lineIter = new this.LineIterator(s), line;
while ( !lineIter.eot() ) {
line = lineIter.next().trim();
if ( line === '' ) { continue; }
if ( line.startsWith('#') ) { continue; } // Comment
if ( line.indexOf('/') === -1 ) { // Plain hostname
if ( reInvalidHostname.test(line) ) { return false; }
continue;
}
if ( line.length > 2 && line.startsWith('/') && line.endsWith('/') ) { // Regex-based
try { new RegExp(line.slice(1, -1)); } catch(ex) { return false; }
continue;
}
if ( reHostnameExtractor.test(line) === false ) { return false; } // URL
}
return true;
};
var reInvalidHostname = /[^a-z0-9.\-\[\]:]/,
reHostnameExtractor = /([a-z0-9\[][a-z0-9.\-]*[a-z0-9\]])(?::[\d*]+)?\/(?:[^\x00-\x20\/]|$)[^\x00-\x20]*$/;
/******************************************************************************/ /******************************************************************************/
})(); })();

View File

@ -29,30 +29,68 @@
/******************************************************************************/ /******************************************************************************/
var messaging = vAPI.messaging; var messaging = vAPI.messaging,
var cachedWhitelist = ''; cachedWhitelist = '';
// Could make it more fancy if needed. But speed... It's a compromise.
var reUnwantedChars = /[\x00-\x09\x0b\x0c\x0e-\x1f!"'()<>{}|`~]/;
/******************************************************************************/ /******************************************************************************/
var whitelistChanged = function() { var getTextareaNode = function() {
var textarea = uDom.nodeFromId('whitelist'); var me = getTextareaNode,
var s = textarea.value.trim(); node = me.theNode;
var changed = s === cachedWhitelist; if ( node === undefined ) {
var bad = reUnwantedChars.test(s); node = me.theNode = uDom.nodeFromSelector('#whitelist textarea');
uDom.nodeFromId('whitelistApply').disabled = changed || bad; }
uDom.nodeFromId('whitelistRevert').disabled = changed; return node;
textarea.classList.toggle('bad', bad);
}; };
var setErrorNodeHorizontalOffset = function(px) {
var me = setErrorNodeHorizontalOffset,
offset = me.theOffset || 0;
if ( px === offset ) { return; }
var node = me.theNode;
if ( node === undefined ) {
node = me.theNode = uDom.nodeFromSelector('#whitelist textarea + div');
}
node.style.right = px + 'px';
me.theOffset = px;
};
/******************************************************************************/
var whitelistChanged = (function() {
var changedWhitelist, changed, timer;
var updateUI = function(good) {
uDom.nodeFromId('whitelistApply').disabled = changed || !good;
uDom.nodeFromId('whitelistRevert').disabled = changed;
uDom.nodeFromId('whitelist').classList.toggle('invalid', !good);
};
var validate = function() {
timer = undefined;
messaging.send(
'dashboard',
{ what: 'validateWhitelistString', raw: changedWhitelist },
updateUI
);
};
return function() {
changedWhitelist = getTextareaNode().value.trim();
changed = changedWhitelist === cachedWhitelist;
if ( timer !== undefined ) { clearTimeout(timer); }
timer = vAPI.setTimeout(validate, 251);
var textarea = getTextareaNode();
setErrorNodeHorizontalOffset(textarea.offsetWidth - textarea.clientWidth);
};
})();
/******************************************************************************/ /******************************************************************************/
var renderWhitelist = function() { var renderWhitelist = function() {
var onRead = function(whitelist) { var onRead = function(whitelist) {
cachedWhitelist = whitelist.trim(); cachedWhitelist = whitelist.trim();
uDom.nodeFromId('whitelist').value = cachedWhitelist + '\n'; getTextareaNode().value = cachedWhitelist + '\n';
whitelistChanged(); whitelistChanged();
}; };
messaging.send('dashboard', { what: 'getWhitelist' }, onRead); messaging.send('dashboard', { what: 'getWhitelist' }, onRead);
@ -62,8 +100,8 @@ var renderWhitelist = function() {
var handleImportFilePicker = function() { var handleImportFilePicker = function() {
var fileReaderOnLoadHandler = function() { var fileReaderOnLoadHandler = function() {
var textarea = uDom('#whitelist'); var textarea = getTextareaNode();
textarea.val([textarea.val(), this.result].join('\n').trim()); textarea.value = [textarea.value.trim(), this.result.trim()].join('\n').trim();
whitelistChanged(); whitelistChanged();
}; };
var file = this.files[0]; var file = this.files[0];
@ -92,10 +130,8 @@ var startImportFilePicker = function() {
/******************************************************************************/ /******************************************************************************/
var exportWhitelistToFile = function() { var exportWhitelistToFile = function() {
var val = uDom('#whitelist').val().trim(); var val = getTextareaNode().value.trim();
if ( val === '' ) { if ( val === '' ) { return; }
return;
}
var filename = vAPI.i18n('whitelistExportFilename') var filename = vAPI.i18n('whitelistExportFilename')
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString()) .replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
.replace(/ +/g, '_'); .replace(/ +/g, '_');
@ -108,7 +144,7 @@ var exportWhitelistToFile = function() {
/******************************************************************************/ /******************************************************************************/
var applyChanges = function() { var applyChanges = function() {
cachedWhitelist = uDom.nodeFromId('whitelist').value.trim(); cachedWhitelist = getTextareaNode().value.trim();
var request = { var request = {
what: 'setWhitelist', what: 'setWhitelist',
whitelist: cachedWhitelist whitelist: cachedWhitelist
@ -117,21 +153,21 @@ var applyChanges = function() {
}; };
var revertChanges = function() { var revertChanges = function() {
uDom.nodeFromId('whitelist').value = cachedWhitelist + '\n'; getTextareaNode().value = cachedWhitelist + '\n';
whitelistChanged(); whitelistChanged();
}; };
/******************************************************************************/ /******************************************************************************/
var getCloudData = function() { var getCloudData = function() {
return uDom.nodeFromId('whitelist').value; return getTextareaNode().value;
}; };
var setCloudData = function(data, append) { var setCloudData = function(data, append) {
if ( typeof data !== 'string' ) { if ( typeof data !== 'string' ) {
return; return;
} }
var textarea = uDom.nodeFromId('whitelist'); var textarea = getTextareaNode();
if ( append ) { if ( append ) {
data = uBlockDashboard.mergeNewLines(textarea.value.trim(), data); data = uBlockDashboard.mergeNewLines(textarea.value.trim(), data);
} }
@ -147,7 +183,7 @@ self.cloud.onPull = setCloudData;
uDom('#importWhitelistFromFile').on('click', startImportFilePicker); uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker); uDom('#importFilePicker').on('change', handleImportFilePicker);
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile); uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
uDom('#whitelist').on('input', whitelistChanged); uDom('#whitelist textarea').on('input', whitelistChanged);
uDom('#whitelistApply').on('click', applyChanges); uDom('#whitelistApply').on('click', applyChanges);
uDom('#whitelistRevert').on('click', revertChanges); uDom('#whitelistRevert').on('click', revertChanges);

View File

@ -17,7 +17,10 @@
<p> <p>
<button id="whitelistApply" class="custom important" type="button" disabled="true" data-i18n="whitelistApply"></button>&ensp; <button id="whitelistApply" class="custom important" type="button" disabled="true" data-i18n="whitelistApply"></button>&ensp;
<button id="whitelistRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button> <button id="whitelistRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button>
<p><textarea id="whitelist" dir="auto" spellcheck="false"></textarea> <p><section id="whitelist">
<textarea dir="auto" spellcheck="false"></textarea>
<div>E</div>
</section>
<p> <p>
<button id="importWhitelistFromFile" class="custom" data-i18n="whitelistImport"></button>&ensp; <button id="importWhitelistFromFile" class="custom" data-i18n="whitelistImport"></button>&ensp;
<button id="exportWhitelistToFile" class="custom" data-i18n="whitelistExport"></button> <button id="exportWhitelistToFile" class="custom" data-i18n="whitelistExport"></button>