@@ -97,6 +97,15 @@ function getButtons(el) {
9797 return Array . from ( el . querySelectorAll ( 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]' ) ) ;
9898}
9999
100+ /**
101+ * Naive selector escaping. Use with caution.
102+ * @param {string } selector
103+ * @returns {string }
104+ */
105+ function insecureEscapeSelectorPart ( selector ) {
106+ return selector . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ " ] / g, '\\$&' ) ;
107+ }
108+
100109/**
101110 * Get the selector for an element
102111 * @param {HTMLElement } el - The element to get the selector for
@@ -134,7 +143,7 @@ function getSelector(el, specificity) {
134143
135144 if ( specificity . ids ) {
136145 if ( element . id ) {
137- localSelector += `#${ element . id } ` ;
146+ localSelector += `#${ insecureEscapeSelectorPart ( element . id ) } ` ;
138147 } else if ( ! element . hasAttribute ( 'id' ) ) { // do not add it for id attribute without a value
139148 localSelector += `:not([id])` ;
140149 }
@@ -143,15 +152,15 @@ function getSelector(el, specificity) {
143152 if ( specificity . dataAttributes ) {
144153 const dataAttributes = Array . from ( element . attributes ) . filter ( a => a . name . startsWith ( 'data-' ) ) ;
145154 dataAttributes . forEach ( a => {
146- const escapedValue = a . value . replace ( / \\ / g , '\\\\' ) . replace ( / " / g , '\\"' ) ;
155+ const escapedValue = insecureEscapeSelectorPart ( a . value ) ;
147156 localSelector += `[${ a . name } ="${ escapedValue } "]` ;
148157 } ) ;
149158 }
150159
151160 if ( specificity . classes ) {
152161 const classes = Array . from ( element . classList ) ;
153162 if ( classes . length > 0 ) {
154- localSelector += `.${ classes . join ( '.' ) } ` ;
163+ localSelector += `.${ classes . map ( c => insecureEscapeSelectorPart ( c ) ) . join ( '.' ) } ` ;
155164 }
156165 }
157166
0 commit comments