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 // Properties
this.categoryWidget = categoryWidget; this.categoryWidget = categoryWidget;
this.forceCapitalization = mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( 14 ) === -1; this.forceCapitalization = mw.config.get( 'wgCaseSensitiveNamespaces' ).indexOf( 14 ) === -1;
this.categoryPrefix = 'Category:'; this.categoryPrefix = mw.config.get( 'wgFormattedNamespaces' )['14'] + ':';
// Initialization // Initialization
this.$.addClass( 've-ui-mwCategoryInputWidget' ); this.$.addClass( 've-ui-mwCategoryInputWidget' );
@ -74,11 +74,17 @@ ve.ui.MWCategoryInputWidget.prototype.getLookupRequest = function () {
* @param {Mixed} data Response from server * @param {Mixed} data Response from server
*/ */
ve.ui.MWCategoryInputWidget.prototype.getLookupCacheItemFromData = function ( data ) { ve.ui.MWCategoryInputWidget.prototype.getLookupCacheItemFromData = function ( data ) {
return ve.isArray( data ) && data.length ? var i, len, title, result = [];
data[1].map( ve.bind( function ( item ) { if ( ve.isArray( data ) && data.length ) {
return item.replace( new RegExp( this.categoryPrefix, 'gi' ), '' ); for ( i = 0, len = data[1].length; i < len; i++ ) {
}, this ) ) : 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.$$, menu$$ = this.lookupMenu.$$,
category = this.getCategoryItemFromValue( this.value ), category = this.getCategoryItemFromValue( this.value ),
existingCategories = this.categoryWidget.getCategories(), existingCategories = this.categoryWidget.getCategories(),
matchingCategories = data || [], matchingCategories = data || [];
pattern = new RegExp( '^' + category.value );
// Existing categories // Existing categories
for ( i = 0, len = existingCategories.length; i < len; i++ ) { for ( i = 0, len = existingCategories.length; i < len; i++ ) {
item = existingCategories[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 ) { if ( item === category.value ) {
exactMatch = true; exactMatch = true;
} }
@ -115,7 +121,7 @@ ve.ui.MWCategoryInputWidget.prototype.getLookupMenuItemsFromData = function ( da
item = matchingCategories[i]; item = matchingCategories[i];
if ( if (
existingCategoryItems.indexOf( item ) === -1 && existingCategoryItems.indexOf( item ) === -1 &&
item.match( pattern ) item.lastIndexOf( category.value, 0 ) === 0
) { ) {
if ( item === category.value ) { if ( item === category.value ) {
exactMatch = true; exactMatch = true;