Skip to content

Commit

Permalink
fix(get-selector): do not URL encode or token escape attribute select…
Browse files Browse the repository at this point in the history
…ors (#3215)

* fix(get-selector): do not URL encode or token escape attribute selectors

* escape

* more escape

* tests
  • Loading branch information
straker authored Nov 9, 2021
1 parent b985776 commit 6f7e183
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 16 deletions.
28 changes: 20 additions & 8 deletions lib/core/utils/get-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ const ignoredAttributes = [
'aria-valuenow'
];
const MAXATTRIBUTELENGTH = 31;
const attrCharsRegex = /([\\"])/g;
const newlineChars = /(\r\n|\r|\n)/g;

/**
* Escape an attribute selector string.
* @param {String} str
* @return {String}
*/
function escapeAttribute(str) {
return (
str
// @see https://www.py4u.net/discuss/286669
.replace(attrCharsRegex, '\\$1')
// @see https://stackoverflow.com/a/20354013/2124254
.replace(newlineChars, '\\a ')
);
}

/**
* get the attribute name and value as a string
Expand All @@ -40,21 +57,16 @@ function getAttributeNameValue(node, at) {
if (name.indexOf('href') !== -1 || name.indexOf('src') !== -1) {
const friendly = getFriendlyUriEnd(node.getAttribute(name));
if (friendly) {
const value = encodeURI(friendly);
if (value) {
atnv = escapeSelector(at.name) + '$="' + escapeSelector(value) + '"';
} else {
return;
}
atnv = escapeSelector(at.name) + '$="' + escapeAttribute(friendly) + '"';
} else {
atnv =
escapeSelector(at.name) +
'="' +
escapeSelector(node.getAttribute(name)) +
escapeAttribute(node.getAttribute(name)) +
'"';
}
} else {
atnv = escapeSelector(name) + '="' + escapeSelector(at.value) + '"';
atnv = escapeSelector(name) + '="' + escapeAttribute(at.value) + '"';
}
return atnv;
}
Expand Down
52 changes: 44 additions & 8 deletions test/core/utils/get-selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,8 @@ describe('axe.utils.getSelector', function() {
img2.setAttribute('src', '//deque.com/logo.png');

fixtureSetup([link1, link2, img1, img2]);
assert.equal(axe.utils.getSelector(link2), 'a[href$="about\\/"]');
assert.equal(axe.utils.getSelector(img2), 'img[src$="logo\\.png"]');
assert.equal(axe.utils.getSelector(link2), 'a[href$="about/"]');
assert.equal(axe.utils.getSelector(img2), 'img[src$="logo.png"]');
});

it('should escape href attributes', function() {
Expand All @@ -508,10 +508,49 @@ describe('axe.utils.getSelector', function() {
fixtureSetup([link1, link2]);
assert.equal(
axe.utils.getSelector(link2),
'a[href="\\/\\/deque\\.com\\/child\\/\\ \\a \\a \\a "]'
'a[href="//deque.com/child/ \\a \\a \\a "]'
);
});

it('should not URL encode or token escape href attribute', function() {
var link1 = document.createElement('a');
link1.setAttribute('href', '3 Seater');

var link2 = document.createElement('a');
link2.setAttribute('href', '1 Seater');

var expected = 'a[href$="1 Seater"]';
fixtureSetup([link1, link2]);
assert.equal(axe.utils.getSelector(link2), expected);
assert.isTrue(axe.utils.matchesSelector(link2, expected));
});

it('should escape certain special characters in attribute', function() {
var div1 = document.createElement('div');
div1.setAttribute('data-thing', 'foobar');

var div2 = document.createElement('div');
div2.setAttribute('data-thing', '!@#$%^&*()_+[]\\;\',./{}|:"<>?');

var expected = 'div[data-thing="!@#$%^&*()_+[]\\\\;\',./{}|:\\"<>?"]';
fixtureSetup([div1, div2]);
assert.equal(axe.utils.getSelector(div2), expected);
assert.isTrue(axe.utils.matchesSelector(div2, expected));
});

it('should escape newline characters in attribute', function() {
var div1 = document.createElement('div');
div1.setAttribute('data-thing', 'foobar');

var div2 = document.createElement('div');
div2.setAttribute('data-thing', ' \n\n\n');

var expected = 'div[data-thing=" \\a \\a \\a "]';
fixtureSetup([div1, div2]);
assert.equal(axe.utils.getSelector(div2), expected);
assert.isTrue(axe.utils.matchesSelector(div2, expected));
});

it('should not generate universal selectors', function() {
var node = document.createElement('div');
node.setAttribute('role', 'menuitem');
Expand All @@ -530,11 +569,8 @@ describe('axe.utils.getSelector', function() {
node2.setAttribute('href', href2);
fixtureSetup([node1, node2]);

assert.include(axe.utils.getSelector(node1), 'mars2\\.html\\?a\\=be_bold');
assert.include(
axe.utils.getSelector(node2),
'mars2\\.html\\?a\\=be_italic'
);
assert.include(axe.utils.getSelector(node1), 'mars2.html?a=be_bold');
assert.include(axe.utils.getSelector(node2), 'mars2.html?a=be_italic');
});

// shadow DOM v1 - note: v0 is compatible with this code, so no need
Expand Down

0 comments on commit 6f7e183

Please sign in to comment.