Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

docs(errorDisplay): encode < and > in error messages #14033

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions docs/app/src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ angular.module('errors', ['ngSanitize'])
};

return function (text, target) {
var targetHtml = target ? ' target="' + target + '"' : '';

if (!text) return text;

var targetHtml = target ? ' target="' + target + '"' : '';

return $sanitize(text.replace(LINKY_URL_REGEXP, function (url) {
if (STACK_TRACE_REGEXP.test(url)) {
return url;
Expand All @@ -34,6 +34,10 @@ angular.module('errors', ['ngSanitize'])


.directive('errorDisplay', ['$location', 'errorLinkFilter', function ($location, errorLinkFilter) {
var encodeAngleBrackets = function (text) {
return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
};

var interpolate = function (formatString) {
var formatArgs = arguments;
return formatString.replace(/\{\d+\}/g, function (match) {
Expand All @@ -51,12 +55,15 @@ angular.module('errors', ['ngSanitize'])
link: function (scope, element, attrs) {
var search = $location.search(),
formatArgs = [attrs.errorDisplay],
formattedText,
i;

for (i = 0; angular.isDefined(search['p'+i]); i++) {
formatArgs.push(search['p'+i]);
}
element.html(errorLinkFilter(interpolate.apply(null, formatArgs), '_blank'));

formattedText = encodeAngleBrackets(interpolate.apply(null, formatArgs));
element.html(errorLinkFilter(formattedText, '_blank'));
}
};
}]);
25 changes: 25 additions & 0 deletions docs/app/test/.jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"extends": "../../../.jshintrc-base",
"browser": true,
"globals": {
// AngularJS
"angular": false,

// ngMocks
"module": false,
"inject": true,

// Jasmine
"jasmine": false,
"describe": false,
"ddescribe": false,
"xdescribe": false,
"it": false,
"iit": false,
"xit": false,
"beforeEach": false,
"afterEach": false,
"spyOn": false,
"expect": false
}
}
166 changes: 166 additions & 0 deletions docs/app/test/errorsSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
'use strict';

describe('errors', function() {
// Mock `ngSanitize` module
angular.
module('ngSanitize', []).
value('$sanitize', jasmine.createSpy('$sanitize').andCallFake(angular.identity));

beforeEach(module('errors'));


describe('errorDisplay', function() {
var $sanitize;
var errorLinkFilter;

beforeEach(inject(function(_$sanitize_, _errorLinkFilter_) {
$sanitize = _$sanitize_;
errorLinkFilter = _errorLinkFilter_;
}));


it('should return empty input unchanged', function() {
var inputs = [undefined, null, false, 0, ''];
var remaining = inputs.length;

inputs.forEach(function(falsyValue) {
expect(errorLinkFilter(falsyValue)).toBe(falsyValue);
remaining--;
});

expect(remaining).toBe(0);
});


it('should recognize URLs and convert them to `<a>`', function() {
var urls = [
['ftp://foo/bar?baz#qux'],
['http://foo/bar?baz#qux'],
['https://foo/bar?baz#qux'],
['mailto:[email protected]', null, '[email protected]'],
['[email protected]', 'mailto:[email protected]', '[email protected]']
];
var remaining = urls.length;

urls.forEach(function(values) {
var actualUrl = values[0];
var expectedUrl = values[1] || actualUrl;
var expectedText = values[2] || expectedUrl;
var anchor = '<a href="' + expectedUrl + '">' + expectedText + '</a>';

var input = 'start ' + actualUrl + ' end';
var output = 'start ' + anchor + ' end';

expect(errorLinkFilter(input)).toBe(output);
remaining--;
});

expect(remaining).toBe(0);
});


it('should not recognize stack-traces as URLs', function() {
var urls = [
'ftp://foo/bar?baz#qux:4:2',
'http://foo/bar?baz#qux:4:2',
'https://foo/bar?baz#qux:4:2',
'mailto:[email protected]:4:2',
'[email protected]:4:2'
];
var remaining = urls.length;

urls.forEach(function(url) {
var input = 'start ' + url + ' end';

expect(errorLinkFilter(input)).toBe(input);
remaining--;
});

expect(remaining).toBe(0);
});


it('should should set `[target]` if specified', function() {
var url = 'https://foo/bar?baz#qux';
var target = '_blank';
var outputWithoutTarget = '<a href="' + url + '">' + url + '</a>';
var outputWithTarget = '<a target="' + target + '" href="' + url + '">' + url + '</a>';

expect(errorLinkFilter(url)).toBe(outputWithoutTarget);
expect(errorLinkFilter(url, target)).toBe(outputWithTarget);
});


it('should truncate the contents of the generated `<a>` to 60 characters', function() {
var looongUrl = 'https://foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo';
var truncatedUrl = 'https://foooooooooooooooooooooooooooooooooooooooooooooooo...';
var output = '<a href="' + looongUrl + '">' + truncatedUrl + '</a>';

expect(looongUrl.length).toBeGreaterThan(60);
expect(truncatedUrl.length).toBe(60);
expect(errorLinkFilter(looongUrl)).toBe(output);
});


it('should pass the final string through `$sanitize`', function() {
$sanitize.reset();

var input = 'start https://foo/bar?baz#qux end';
var output = errorLinkFilter(input);

expect($sanitize.callCount).toBe(1);
expect($sanitize).toHaveBeenCalledWith(output);
});
});


describe('errorDisplay', function() {
var $compile;
var $location;
var $rootScope;
var errorLinkFilter;

beforeEach(module(function($provide) {
$provide.decorator('errorLinkFilter', function() {
errorLinkFilter = jasmine.createSpy('errorLinkFilter');
errorLinkFilter.andCallFake(angular.identity);

return errorLinkFilter;
});
}));
beforeEach(inject(function(_$compile_, _$location_, _$rootScope_) {
$compile = _$compile_;
$location = _$location_;
$rootScope = _$rootScope_;
}));


it('should set the element\s HTML', function() {
var elem = $compile('<span error-display="bar">foo</span>')($rootScope);
expect(elem.html()).toBe('bar');
});


it('should interpolate the contents against `$location.search()`', function() {
spyOn($location, 'search').andReturn({p0: 'foo', p1: 'bar'});

var elem = $compile('<span error-display="foo = {0}, bar = {1}"></span>')($rootScope);
expect(elem.html()).toBe('foo = foo, bar = bar');
});


it('should pass the interpolated text through `errorLinkFilter`', function() {
$location.search = jasmine.createSpy('search').andReturn({p0: 'foo'});

var elem = $compile('<span error-display="foo = {0}"></span>')($rootScope);
expect(errorLinkFilter.callCount).toBe(1);
expect(errorLinkFilter).toHaveBeenCalledWith('foo = foo', '_blank');
});


it('should encode `<` and `>`', function() {
var elem = $compile('<span error-display="&lt;xyz&gt;"></span>')($rootScope);
expect(elem.text()).toBe('<xyz>');
});
});
});