PageSourceSearch

https://www.radnorhunt.org/CMSScripts/CHO/JS/Custom/cookie-consent-blocker.js

js radnorhunt.org collected 2026-09-26 04:17:02 UTC 12,698 bytes, 281 lines download raw bytes

1/*
2 * Prior-blocking cookie-consent script blocker (PRD 10.8 / 11).
3 *
4 * Emitted FIRST in the <head> of public pages by PortalTemplate.aspx.extension.cs,
5 * and only when the consent banner is enabled (HasCookieNotification ON) AND the
6 * visitor has not yet accepted. Neutralizes cross-domain, non-allow-listed
7 * <script> elements until the visitor clicks Accept, at which point the consent
8 * banner calls window.chReactivateBlockedScripts().
9 *
10 * A dynamically created external script is only fetched by the browser once it is
11 * inserted into the document, so the primary, reliable choke point is DOM
12 * insertion (appendChild / insertBefore / replaceChild / append / prepend). We
13 * neutralize the node there, BEFORE it enters the DOM, regardless of how its src
14 * was assigned (element.src = ..., setAttribute('src', ...), or createElement).
15 * The createElement setter, setAttribute patch, MutationObserver and document.write
16 * patch are additional layers of defense.
17 *
18 * IMPORTANT (PRD 11 / 14): the allow-list below is a starting point and MUST be
19 * verified against a live CHO public-site network trace before this blocker is
20 * relied upon. In particular, add the real payment-gateway domains so checkout
21 * and booking are never blocked.
22 *
23 * Manual validation harness (run before consent, with Network tools open):
24 *   var s = document.createElement('script');
25 *   s.setAttribute('src', 'https://example-third-party.invalid/x.js');
26 *   document.head.appendChild(s);
27 *   // Expect: NO network request; s has type="javascript/blocked" and
28 *   //         data-mf-blocked-src set. After Accept, the request fires.
29 */
30(function () {
31    "use strict";
32
33    var ACCEPTED = "CLUBPOLICY=ClubPrivacyPolicy";
34
35    // If the visitor already accepted, do not install the blocker at all.
36    if (document.cookie.indexOf(ACCEPTED) !== -1) {
37        return;
38    }
39
40    // Essential hosts that must always load, regardless of consent.
41    // Matched by exact host or as a parent domain (".example.com").
42    var ALLOW_LIST = [
43        window.location.host.toLowerCase(),
44        "code.jquery.com",
45        "ajax.googleapis.com",
46        "cdnjs.cloudflare.com",
47        "maxcdn.bootstrapcdn.com",
48        "cdn.jsdelivr.net",
49        "use.typekit.net",
50        "www.google.com", // reCAPTCHA loader (captcha behavior gated separately, PRD 10.7)
51        // Payment gateways
52        "api.shift4test.com/api/rest/v1/",
53        "api.shift4api.net/api/rest/v1/",
54        "access.shift4test.com",
55        "access.i4go.com",
56        "i4m.shift4test.com/js/jquery.i4goTrueToken.js",
57        "i4m.i4go.com/js/jquery.i4goTrueToken.js",
58        "shift4-test.jonasprocessing.com",
59        "shift4.jonasprocessing.com",
60        "staging-pyxisapi.csipay.com",
61        "pyxisapi.csipay.com",
62        "js.stripe.com",
63        "m.stripe.com",
64        "m.stripe.network",
65        "r.stripe.com",
66    ];
67
68    function hostOf(src) {
69        if (!src) return "";
70        var s = String(src);
71        if (s.indexOf("//") === 0) {
72            s = window.location.protocol + s;
73        }
74        var m = s.match(/^[a-z]+:\/\/([^\/?#]+)/i);
75        // No host => relative/inline script => treated as first-party (allowed).
76        return m ? m[1].toLowerCase() : "";
77    }
78
79    function isAllowed(src) {
80        var host = hostOf(src);
81        if (!host) return true;
82        for (var i = 0; i < ALLOW_LIST.length; i++) {
83            var allowed = ALLOW_LIST[i];
84            if (!allowed) continue;
85            if (host === allowed) return true;
86            // parent-domain match: host ends with "." + allowed
87            if (host.length > allowed.length &&
88                host.lastIndexOf("." + allowed) === host.length - allowed.length - 1) {
89                return true;
90            }
91        }
92        return false;
93    }
94
95    var blocked = [];
96
97    function rememberBlocked(node) {
98        if (node && blocked.indexOf(node) === -1) {
99            blocked.push(node);
100        }
101    }
102
103    // Move a non-allow-listed script's src into data-mf-blocked-src so the browser
104    // never fetches it. Idempotent: once neutralized there is no src to re-process.
105    function neutralizeScriptNode(node) {
106        if (!node || node.nodeType !== 1 || node.tagName !== "SCRIPT") return;
107        // Read via getAttribute so we see the value no matter how 
107it was assigned.
108        var src = origGetAttribute ? origGetAttribute.call(node, "src") : node.getAttribute("src");
109        if (!src || isAllowed(src)) return;
110        // Use the original setAttribute/removeAttribute so we don't re-enter our patch.
111        (origSetAttribute || node.setAttribute).call(node, "data-mf-blocked-src", src);
112        node.type = "javascript/blocked";
113        (origRemoveAttribute || node.removeAttribute).call(node, "src");
114        rememberBlocked(node);
115    }
116
117    // Neutralize any script(s) about to be inserted, including inside a fragment.
118    function neutralizeBeforeInsert(node) {
119        if (!node || node.nodeType == null) return;
120        if (node.nodeType === 1) {
121            if (node.tagName === "SCRIPT") {
122                neutralizeScriptNode(node);
123            } else if (node.getElementsByTagName) {
124                var inner = node.getElementsByTagName("script");
125                for (var i = 0; i < inner.length; i++) neutralizeScriptNode(inner[i]);
126            }
127        } else if (node.nodeType === 11 && node.querySelectorAll) { // DocumentFragment
128            var scripts = node.querySelectorAll("script");
129            for (var j = 0; j < scripts.length; j++) neutralizeScriptNode(scripts[j]);
130        }
131    }
132
133    // ---- Capture originals up front so patched methods can call through safely. ----
134    var elProto = (typeof Element !== "undefined" && Element.prototype) ? Element.prototype : null;
135    var nodeProto = (typeof Node !== "undefined" && Node.prototype) ? Node.prototype : null;
136
137    var origCreateElement = document.createElement;
138    var origSetAttribute = elProto ? elProto.setAttribute : null;
139    var origGetAttribute = elProto ? elProto.getAttribute : null;
140    var origRemoveAttribute = elProto ? elProto.removeAttribute : null;
141    var origAppendChild = nodeProto ? nodeProto.appendChild : null;
142    var origInsertBefore = nodeProto ? nodeProto.insertBefore : null;
143    var origReplaceChild = nodeProto ? nodeProto.replaceChild : null;
144    var origAppend = elProto ? elProto.append : null;
145    var origPrepend = elProto ? elProto.prepend : null;
146    var origWrite = document.write;
147
148    // 1) Intercept dynamically created <script> elements' src property assignment.
149    document.createElement = function (tagName) {
150        var el = origCreateElement.apply(document, arguments);
151        if (typeof tagName === "string" && tagName.toLowerCase() === "script") {
152            try {
153                Object.defineProperty(el, "src", {
154                    configurable: true,
155                    enumerable: true,
156                    get: function () { return origGetAttribute.call(el, "src") || ""; },
157                    set: function (value) {
158                        if (isAllowed(value)) {
159                            origSetAttribute.call(el, "src", value);
160                        } else {
161                            origSetAttribute.call(el, "data-mf-blocked-src", value);
162                            el.type = "javascript/blocked";
163                            rememberBlocked(el);
164                        }
165                    }
166                });
167            } catch (e) { /* fall back to setAttribute / insertion patches */ }
168        }
169        return el;
170    };
171    document.createElement.__chOriginal = origCreateElement;
172
173    // 2) Intercept setAttribute('src', ...) on <script> (bypasses the .src setter).
174    if (elProto && origSetAttribute) {
175        elProto.setAttribute = function (name, value) {
176            if (this && this.tagName === "SCRIPT" &&
177                typeof name === "string" && name.toLowerCase() === "src" &&
178                !isAllowed(value)) {
179                origSetAttribute.call(this, "data-mf-blocked-src", value);
180                origSetAttribute.call(this, "type", "javascript/blocked");
181                rememberBlocked(this);
182                return;
183            }
184            return origSetAttribute.apply(this, arguments);
185        };
186    }
187
188    // 3) Primary choke point: neutralize scripts at DOM-insertion time. This catches
189    //    script.setAttribute('src', url); head.appendChild(script); and every other
190    //    dynamic path, because the browser only fetches once the node is inserted.
191    if (nodeProto && origAppendChild) {
192        nodeProto.appendChild = function (node) {
193            neutralizeBeforeInsert(node);
194            return origAppendChild.call(this, node);
195        };
196    }
197    if (nodeProto && origInsertBefore) {
198        nodeProto.insertBefore = function (node, ref) {
199            neutralizeBeforeInsert(node);
200            return origInsertBefore.call(this, node, ref);
201        };
202    }
203    if (nodeProto && origReplaceChild) {
204        nodeProto.replaceChild = function (newNode, oldNode) {
205            neutralizeBeforeInsert(newNode);
206            return origReplaceChild.call(this, newNode, oldNode);
207        };
208    }
209    if (elProto && origAppend) {
210        elProto.append = function () {
211            for (var i = 0; i < arguments.length; i++) neutralizeBeforeInsert(arguments[i]);
212            return origAppend.apply(this, arguments);
213        };
214    }
215    if (elProto && origPrepend) {
216        elProto.prepend = function () {
217            for (var i = 0; i < arguments.length; i++) neutralizeBeforeInsert(arguments[i]);
218            return origPrepend.apply(this, arguments);
219        };
220    }
221
222    // 4) Backstop for parser-inserted / otherwise-missed scripts.
223    var observer = null;
224    if (typeof MutationObserver !== "undefined") {
225        observer = new MutationObserver(function (mutations) {
226            for (var i = 0; i < mutations.length; i++) {
227                var added = mutations[i].addedNodes;
228                for (var j = 0; j < added.length; j++) neutralizeBeforeInsert(added[j]);
229            }
230        });
231        try {
232            observer.observe(document.documentElement, { childList: true, subtree: true });
233        } catch (e) { /* no-op */ }
234    }
235
236    // 5) Strip non-allow-listed external scripts written via document.write.
237    document.write = function (markup) {
238        if (typeof markup === "string" && /<script/i.test(markup)) {
239            markup = markup.replace(
240                /<script\b[^>]*\bsrc\s*=\s*(['"])(.*?)\1[^>]*>(\s*<\/script>)?/gi,
241                function (full, quote, src) {
242                    return isAllowed(src) ? full : "<!-- mf-blocked: " + src + " -->";
243                }
244            );
245        }
246        return origWrite.call(document, markup);
247    };
248
249    // 6) Re-activation hook, called by the consent banner on Accept.
250    window.chReactivateBlockedScripts = function () {
251        // Restore all patched APIs FIRST so re-inserting scripts is not re-blocked.
252        try { document.createElement = origCreateElement; } catch (e) {}
253        try { document.write = origWrite; } catch (e) {}
254        if (elProto) {
255            try { if (origSetAttribute) elProto.setAttribute = origSetAttribute; } catch (e) {}
256            try { if (origAppend) elProto.append = origAppend; } catch (e) {}
257            try { if (origPrepend) elProto.prepend = origPrepend; } catch (e) {}
258        }
259        if (nodeProto) {
260            try { if (origAppendChild) nodeProto.appendChild = origAppendChild; } catch (e) {}
261            try { if (origInsertBefore) nodeProto.insertBefore = origInsertBefore; } catch (e) {}
262            try { if (origReplaceChild) nodeProto.replaceChild = origReplaceChild; } catch (e) {}
263        }
264        if (observer) { try { observer.disconnect(); } catch (e) {} }
265
266        var pending = blocked;
267        blocked = [];
268        for (var i = 0; i < pending.length; i++) {
269            var old = pending[i];
270            var src = origGetAttribute
271                ? origGetAttribute.call(old, "data-mf-blocked-src")
272                : old.getAttribute("data-mf-blocked-src");
273            if (!src) continue;
274            var s = origCreateElement.call(document, "script");
275            s.async = true;
276            s.src = src;
277            var parent = old.parentNode || document.head || document.documentElement;
278            origAppendChild.call(parent, s);
279        }
280    };
281})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.