Skip to content
This repository has been archived by the owner on Nov 22, 2021. It is now read-only.

Commit

Permalink
fix(autocomplete): Encode HTML chars in suggestion list
Browse files Browse the repository at this point in the history
Suggestions containing HTML chars (<, > and &) aren't properly encoded
and those suggestions don't play nice with $sce.trustAsHtml.

Closes #34.
  • Loading branch information
mbenford committed Dec 15, 2013
1 parent 36a80da commit 6e4f7c7
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 12 deletions.
19 changes: 14 additions & 5 deletions build/ng-tags-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,14 +337,20 @@ tagsInput.directive('autoComplete', ["$document","$timeout","$sce","tagsInputCon
return self;
}

function encodeHTML(value) {
return value.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}

return {
restrict: 'E',
require: '?^tagsInput',
scope: { source: '&' },
templateUrl: 'ngTagsInput/auto-complete.html',
link: function(scope, element, attrs, tagsInputCtrl) {
var hotkeys = [KEYS.enter, KEYS.tab, KEYS.escape, KEYS.up, KEYS.down],
suggestionList, tagsInput, highlight;
suggestionList, tagsInput, markdown;

tagsInputConfig.load(scope, attrs, {
debounceDelay: { type: Number, defaultValue: 100 },
Expand All @@ -357,13 +363,13 @@ tagsInput.directive('autoComplete', ["$document","$timeout","$sce","tagsInputCon
suggestionList = new SuggestionList(scope.source, scope.options);

if (scope.options.highlightMatchedText) {
highlight = function(item, text) {
markdown = function(item, text) {
var expression = new RegExp(text, 'gi');
return item.replace(expression, '<em>$&</em>');
return item.replace(expression, '**$&**');
};
}
else {
highlight = function(item) {
markdown = function(item) {
return item;
};
}
Expand All @@ -384,7 +390,10 @@ tagsInput.directive('autoComplete', ["$document","$timeout","$sce","tagsInputCon
};

scope.highlight = function(item) {
return $sce.trustAsHtml(highlight(item, suggestionList.query));
item = markdown(item, suggestionList.query);
item = encodeHTML(item);
item = item.replace(/\*\*(.+?)\*\*/g, '<em>$1</em>');
return $sce.trustAsHtml(item);
};

tagsInput
Expand Down
Binary file modified build/ng-tags-input.min.zip
Binary file not shown.
Binary file modified build/ng-tags-input.zip
Binary file not shown.
19 changes: 14 additions & 5 deletions src/auto-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,20 @@ tagsInput.directive('autoComplete', function($document, $timeout, $sce, tagsInpu
return self;
}

function encodeHTML(value) {
return value.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}

return {
restrict: 'E',
require: '?^tagsInput',
scope: { source: '&' },
templateUrl: 'ngTagsInput/auto-complete.html',
link: function(scope, element, attrs, tagsInputCtrl) {
var hotkeys = [KEYS.enter, KEYS.tab, KEYS.escape, KEYS.up, KEYS.down],
suggestionList, tagsInput, highlight;
suggestionList, tagsInput, markdown;

tagsInputConfig.load(scope, attrs, {
debounceDelay: { type: Number, defaultValue: 100 },
Expand All @@ -109,13 +115,13 @@ tagsInput.directive('autoComplete', function($document, $timeout, $sce, tagsInpu
suggestionList = new SuggestionList(scope.source, scope.options);

if (scope.options.highlightMatchedText) {
highlight = function(item, text) {
markdown = function(item, text) {
var expression = new RegExp(text, 'gi');
return item.replace(expression, '<em>$&</em>');
return item.replace(expression, '**$&**');
};
}
else {
highlight = function(item) {
markdown = function(item) {
return item;
};
}
Expand All @@ -136,7 +142,10 @@ tagsInput.directive('autoComplete', function($document, $timeout, $sce, tagsInpu
};

scope.highlight = function(item) {
return $sce.trustAsHtml(highlight(item, suggestionList.query));
item = markdown(item, suggestionList.query);
item = encodeHTML(item);
item = item.replace(/\*\*(.+?)\*\*/g, '<em>$1</em>');
return $sce.trustAsHtml(item);
};

tagsInput
Expand Down
24 changes: 24 additions & 0 deletions test/auto-complete.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,30 @@ describe('autocomplete-directive', function() {
expect(getSuggestionText(3)).toBe('aba');
expect(getSuggestionText(4)).toBe('bab');
});

it('encodes HTML characters in suggestions list', function() {
// Act
loadSuggestions(['<Item 1>', 'Item <2>', 'Item &3']);

// Assert
expect(getSuggestionText(0)).toBe('&lt;Item 1&gt;');
expect(getSuggestionText(1)).toBe('Item &lt;2&gt;');
expect(getSuggestionText(2)).toBe('Item &amp;3');
});

it('highlights encoded HTML characters in suggestions list', function() {
// Arrange
compile('highlight-matched-text="true"', 'min-length="1"');

// Act
loadSuggestions(['<Item 1>', 'Item <2>', 'Item &3'], '>');

// Assert
expect(getSuggestionText(0)).toBe('&lt;Item 1<em>&gt;</em>');
expect(getSuggestionText(1)).toBe('Item &lt;2<em>&gt;</em>');
expect(getSuggestionText(2)).toBe('Item &amp;3');
});

});

describe('max-results-to-show option', function() {
Expand Down
5 changes: 3 additions & 2 deletions test/test-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
<tags-input ng-model="tags"
placeholder="{{ placeholder.value }}"
replace-spaces-with-dashes="false"
add-on-blur="true">
add-on-blur="true"
allowed-tags-pattern="^[a-zA-Z0-9\s<>@\.]+$">
<auto-complete source="loadItems($query)"
debounce-delay="0"
min-length="1"
Expand All @@ -29,7 +30,7 @@
$scope.loadItems = function(query) {
console.log(query);
var deferred = $q.defer();
deferred.resolve(['Batman', 'Superman', 'Flash', 'Iron Man', 'Hulk', 'Wolverine', "Green Lantern", "Green Arrow", "Spiderman"]);
deferred.resolve(['Batman <bruce@wayne.com>', 'Superman', 'Flash', 'Iron Man', 'Hulk', 'Wolverine', "Green Lantern", "Green Arrow", "Spiderman"]);
return deferred.promise;
};
});
Expand Down

0 comments on commit 6e4f7c7

Please sign in to comment.