Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent getSelector from returning URLs as selector #32586

Merged
14 changes: 13 additions & 1 deletion js/src/util/index.js
Expand Up @@ -36,7 +36,19 @@ const getSelector = element => {
let selector = element.getAttribute('data-bs-target')

if (!selector || selector === '#') {
const hrefAttr = element.getAttribute('href')
let hrefAttr = element.getAttribute('href')

// The only valid content that could double as a selector are ids, so everything starting with . or #
// If a 'real' url is used as the selector, document.querySelector will rightfully
// complain it is invalid. (see twbs#32273)
if (!hrefAttr || (!hrefAttr.includes('#') && !hrefAttr.startsWith('.'))) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It restricts the user to use only the class and id selectors in the href attribute. This restriction was not before 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see a problem there. I did not find any mention in the documentation, that anything other than IDs in the href are even supported.

Considering that that freedom for using invalid hrefs as selectors was exactly what caused problems and lead to this PR (and this is the big jump to v5), I think this would be the last opportunity to make such a change before possibly a myriad of users starts misusing the href in that way.

Besides, that's what the data-bs-target attribute is for, right? (which is encouraged everywhere in the docs)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this would fix the issue we have. I guess it's a good solution for v5. We can revisit in v6.

return null
}

// just in case some cms put out a full url with the anchor appended
if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) {
hrefAttr = '#' + hrefAttr.split('#')[1]
}

selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null
}
Expand Down
22 changes: 22 additions & 0 deletions js/tests/unit/util/index.spec.js
Expand Up @@ -57,6 +57,28 @@ describe('Util', () => {
expect(Util.getSelectorFromElement(testEl)).toEqual('.target')
})

it('should return null if a selector from a href is a url without an anchor', () => {
fixtureEl.innerHTML = [
'<a id="test" data-bs-target="#" href="foo/bar.html"></a>',
'<div class="target"></div>'
].join('')

const testEl = fixtureEl.querySelector('#test')

expect(Util.getSelectorFromElement(testEl)).toBeNull()
})

it('should return the anchor if a selector from a href is a url', () => {
fixtureEl.innerHTML = [
'<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>',
'<div id="target"></div>'
].join('')

const testEl = fixtureEl.querySelector('#test')

expect(Util.getSelectorFromElement(testEl)).toEqual('#target')
})

it('should return null if selector not found', () => {
fixtureEl.innerHTML = '<a id="test" href=".target"></a>'

Expand Down