diff --git a/src/htmx.js b/src/htmx.js index 0e9bb0a97..7709c5640 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -4039,11 +4039,11 @@ var htmx = (function() { /** * @param {XMLHttpRequest} xhr - * @param {RegExp} regexp + * @param {string} name * @return {boolean} */ - function hasHeader(xhr, regexp) { - return regexp.test(xhr.getAllResponseHeaders()) + function hasHeader(xhr, name) { + return xhr.getResponseHeader(name) !== null } /** @@ -4668,13 +4668,13 @@ var htmx = (function() { //= ========================================== let pathFromHeaders = null let typeFromHeaders = null - if (hasHeader(xhr, /HX-Push:/i)) { + if (hasHeader(xhr, 'HX-Push')) { pathFromHeaders = xhr.getResponseHeader('HX-Push') typeFromHeaders = 'push' - } else if (hasHeader(xhr, /HX-Push-Url:/i)) { + } else if (hasHeader(xhr, 'HX-Push-Url')) { pathFromHeaders = xhr.getResponseHeader('HX-Push-Url') typeFromHeaders = 'push' - } else if (hasHeader(xhr, /HX-Replace-Url:/i)) { + } else if (hasHeader(xhr, 'HX-Replace-Url')) { pathFromHeaders = xhr.getResponseHeader('HX-Replace-Url') typeFromHeaders = 'replace' } @@ -4809,11 +4809,11 @@ var htmx = (function() { if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return - if (hasHeader(xhr, /HX-Trigger:/i)) { + if (hasHeader(xhr, 'HX-Trigger')) { handleTriggerHeader(xhr, 'HX-Trigger', elt) } - if (hasHeader(xhr, /HX-Location:/i)) { + if (hasHeader(xhr, 'HX-Location')) { let redirectPath = xhr.getResponseHeader('HX-Location') /** @type {HtmxAjaxHelperContext&{path?:string}} */ var redirectSwapSpec = {} @@ -4828,9 +4828,9 @@ var htmx = (function() { return } - const shouldRefresh = hasHeader(xhr, /HX-Refresh:/i) && xhr.getResponseHeader('HX-Refresh') === 'true' + const shouldRefresh = hasHeader(xhr, 'HX-Refresh') && xhr.getResponseHeader('HX-Refresh') === 'true' - if (hasHeader(xhr, /HX-Redirect:/i)) { + if (hasHeader(xhr, 'HX-Redirect')) { responseInfo.keepIndicators = true htmx.location.href = xhr.getResponseHeader('HX-Redirect') shouldRefresh && htmx.location.reload() @@ -4859,11 +4859,11 @@ var htmx = (function() { } // response headers override response handling config - if (hasHeader(xhr, /HX-Retarget:/i)) { + if (hasHeader(xhr, 'HX-Retarget')) { responseInfo.target = resolveRetarget(elt, xhr.getResponseHeader('HX-Retarget')) } - if (hasHeader(xhr, /HX-Reswap:/i)) { + if (hasHeader(xhr, 'HX-Reswap')) { swapOverride = xhr.getResponseHeader('HX-Reswap') } @@ -4919,7 +4919,7 @@ var htmx = (function() { selectOverride = responseInfoSelect } - if (hasHeader(xhr, /HX-Reselect:/i)) { + if (hasHeader(xhr, 'HX-Reselect')) { selectOverride = xhr.getResponseHeader('HX-Reselect') } @@ -4933,7 +4933,7 @@ var htmx = (function() { anchor: responseInfo.pathInfo.anchor, contextElement: elt, afterSwapCallback: function() { - if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) { + if (hasHeader(xhr, 'HX-Trigger-After-Swap')) { let finalElt = elt if (!bodyContains(elt)) { finalElt = getDocument().body @@ -4942,7 +4942,7 @@ var htmx = (function() { } }, afterSettleCallback: function() { - if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) { + if (hasHeader(xhr, 'HX-Trigger-After-Settle')) { let finalElt = elt if (!bodyContains(elt)) { finalElt = getDocument().body diff --git a/test/core/headers.js b/test/core/headers.js index 5048272c1..056f826c8 100644 --- a/test/core/headers.js +++ b/test/core/headers.js @@ -533,4 +533,20 @@ describe('Core htmx AJAX headers', function() { htmx._('loadHistoryFromServer')('/test') this.server.respond() }) + + // Regression for #527: response headers whose names *contain* an htmx + // header name as a substring (e.g. X-HX-Trigger) must not be treated as + // the htmx header. The previous implementation tested a regex against + // getAllResponseHeaders() and false-positively matched the substring, + // then crashed when getResponseHeader returned null. + it('does not crash or fire trigger when X-HX-Trigger header is present without HX-Trigger', function() { + this.server.respondWith('GET', '/test', [200, { 'X-HX-Trigger': 'foo' }, '']) + + var div = make('
') + var invokedEvent = false + div.addEventListener('foo', function() { invokedEvent = true }) + div.click() + this.server.respond() + invokedEvent.should.equal(false) + }) })