Clean up incorrect use of regular expressions in CategoryInputWidget

getLookupMenuItemsFromData() constructed a regex from user input
without escaping. I don't *think* there are any injection
vulnerabilities here but at the very least it triggers exceptions
when the input is, say, a backslash. Instead, use .lastIndexOf() which
allows us to efficiently check whether a string starts with a certain
prefix.

getLookupCacheItemFromData() was stripping out the Category: prefix
using a regex that hardcoded Category: (so failed to detect localized
prefixes) and used global replacement, which meant that strings with
multiple occurrences of 'Category:' were handled incorrectly. Instead,
use mw.Title to strip the prefix. Also move away from .map() because we
may need to drop a result if it doesn't pass mw.Title validation.

this.categoryPrefix still has a few legitimate uses left, so keep it
around but set it to the localized namespace prefix rather than Category:

Change-Id: I6547f9df2e94fe81f6aefb9286e547425137344b
This commit is contained in:
Roan Kattouw 2013-06-11 11:54:39 -07:00
parent a61adfea45
commit 514039b2ba

View file

@ -33,7 +33,7 @@ ve.ui.MWCategoryInputWidget = function VeUiMWCategoryInputWidget( categoryWidget
// Properties
this.categoryWidget = categoryWidget;
this.forceCapitalization = mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( 14 ) === -1;
this.categoryPrefix = 'Category:';
this.categoryPrefix = mw.config.get( 'wgFormattedNamespaces' )['14'] + ':';
// Initialization
this.$.addClass( 've-ui-mwCategoryInputWidget' );
@ -74,11 +74,17 @@ ve.ui.MWCategoryInputWidget.prototype.getLookupRequest = function () {
* @param {Mixed} data Response from server
*/
ve.ui.MWCategoryInputWidget.prototype.getLookupCacheItemFromData = function ( data ) {
return ve.isArray( data ) && data.length ?
data[1].map( ve.bind( function ( item ) {
return item.replace( new RegExp( this.categoryPrefix, 'gi' ), '' );
}, this ) ) :
[];
var i, len, title, result = [];
if ( ve.isArray( data ) && data.length ) {
for ( i = 0, len = data[1].length; i < len; i++ ) {
try {
title = new mw.Title( data[1][i] );
result.push( title.getNameText() );
} catch ( e ) { }
// If the received title isn't valid, just ignore it
}
}
return result;
};
/**
@ -97,13 +103,13 @@ ve.ui.MWCategoryInputWidget.prototype.getLookupMenuItemsFromData = function ( da
menu$$ = this.lookupMenu.$$,
category = this.getCategoryItemFromValue( this.value ),
existingCategories = this.categoryWidget.getCategories(),
matchingCategories = data || [],
pattern = new RegExp( '^' + category.value );
matchingCategories = data || [];
// Existing categories
for ( i = 0, len = existingCategories.length; i < len; i++ ) {
item = existingCategories[i];
if ( item.match( pattern ) ) {
// Verify that item starts with category.value
if ( item.lastIndexOf( category.value, 0 ) === 0 ) {
if ( item === category.value ) {
exactMatch = true;
}
@ -115,7 +121,7 @@ ve.ui.MWCategoryInputWidget.prototype.getLookupMenuItemsFromData = function ( da
item = matchingCategories[i];
if (
existingCategoryItems.indexOf( item ) === -1 &&
item.match( pattern )
item.lastIndexOf( category.value, 0 ) === 0
) {
if ( item === category.value ) {
exactMatch = true;