PageSourceSearch

https://lorikeetsecurity.com/js/csp-shim.js?v=20260709

js lorikeetsecurity.com collected 2026-09-28 10:38:58 UTC 17,168 bytes, 366 lines download raw bytes

1/**
2 * CSP-safe action shim.
3 * Replaces inline onclick handlers (blocked by strict-dynamic CSP) with
4 * data-attribute dispatch.
5 *
6 * NAMED-ACTION dispatch (preferred for page-specific behaviors):
7 *   window.CSPActions.register('foo', function(el, ev) { ... });
8 *   <button data-csp-action="foo">...</button>
9 *
10 * GENERIC FUNCTION-CALL dispatch (replaces inline onclick="fn(args)"):
11 *   <button data-csp-fn="fn">                  -> fn(el, ev)
12 *   <button data-csp-fn="fn" data-csp-args='[1,"x"]'> -> fn(1, "x", el, ev)
13 *   The string "$el" anywhere in args is replaced with the element itself,
14 *   so onclick="pickOne(1,'x',this)" -> data-csp-fn="pickOne" data-csp-args='[1,"x","$el"]'.
15 *   data-csp-stop="true" stops event propagation (replaces event.stopPropagation()).
16 *
17 * COMMON-PATTERN helpers:
18 *   data-toggle-active="self|parent|.css"    classList.toggle('active' or data-toggle-class)
19 *   data-scroll-to="#id"                     smooth scroll to element
20 *   data-href="url"                          window.location.href = url
21 *   data-href="url" data-href-target="_blank" window.open(url, '_blank')
22 *   data-csp-modal-close="#modalId"          close a modal on backdrop click
23 *   data-csp-show="#id" / data-csp-hide="#id"  set display='block' / 'none'
24 *   data-csp-click-trigger="#id"             relay click to another element
25 */
26(function () {
27    var registry = {};
28    window.CSPActions = {
29        register: function (name, fn) { registry[name] = fn; },
30        run:      function (name, el, ev) { if (registry[name]) registry[name](el, ev); }
31    };
32
33    // Submit handler: replaces onsubmit="return fnName(event)" or onsubmit="fn(event)".
34    document.addEventListener('submit', function (e) {
35        var form = e.target.closest('form[data-csp-form-handler]');
36        if (form) {
37            var fn = window[form.dataset.cspFormHandler];
38            if (typeof fn === 'function') {
39                var ret = fn(e);
40                if (ret === false) e.preventDefault();
41            }
42        }
43        // Generic submit-fn dispatch (data-csp-submit-fn="fn", optional data-csp-prevent="true").
44        var subFnEl = e.target.closest('[data-csp-submit-fn]');
45        if (subFnEl) dispatchFn(subFnEl, e, subFnEl.dataset.cspSubmitFn);
46        // Confirm-on-submit
47        var confirmForm = e.target.closest('form[data-csp-confirm-form]');
48        if (confirmForm && !window.confirm(confirmForm.dataset.cspConfirmForm || 'Submit?')) {
49            e.preventDefault();
50        }
51    }, true);
52
53    // Shared dispatcher: parse args, substitute placeholders, call window[fnName].
54    // Supports dotted paths e.g. "FindingModal._applyKB" -> window.FindingModal._applyKB
55    // bound to its parent object so `this` is correct inside the method.
56    function resolveFn(fnName) {
57        var fn = window[fnName];
58        if (typeof fn === 'function') return { fn: fn, ctx: window };
59        if (fnName && fnName.indexOf('.') !== -1) {
60            var parts = fnName.split('.');
61            var obj = window, ctx = window;
62            for (var i = 0; i < parts.length; i++) {
63                ctx = obj;
64                obj = obj ? obj[parts[i]] : undefined;
65            }
66            if (typeof obj === 'function') return { fn: obj, ctx: ctx };
67        }
68        return null;
69    }
70
71    function dispatchFn(el, e, fnName) {
72        var resolved = resolveFn(fnName);
73        if (!resolved) return;
74        var fn = resolved.fn, ctx = resolved.ctx;
75        if (el.dataset.cspStop === 'true') e.stopPropagation();
76        if (el.dataset.cspPrevent === 'true') e.preventDefault();
77        var args = [];
78        var raw = el.dataset.cspArgs;
79        if (raw) { try { args = JSON.parse(raw); } catch (err) { args = []; } }
80        args = args.map(function (a) {
81            if (a === '$el') return el;
82            if (a === '$ev') return e;
83            if (a === '$elvalue') return el.value;
84            if (a === '$elchecked') return el.checked;
85            if (a === '$eldataset') return el.dataset;
86            return a;
87        });
88        args.push(el, e);
89        try { fn.apply(ctx, args); } catch (err) { console.error('[csp-shim] "' + fnName + '" threw:', err); }
90    }
91
92    // Input handler: live mirror of value into a target element's textContent.
93    document.addEventListener('input', function (e) {
94        var mirror = e.target.closest('[data-csp-mirror-text]');
95        if (mirror) {
96            var tgt = document.getElementById(mirror.dataset.cspMirrorText);
97            if (tgt) tgt.textContent = e.target.value;
98        }
99        var fnEl = e.target.closest('[data-csp-input-fn]');
100        if (fnEl) dispatchFn(fnEl, e, fnEl.dataset.cspInputFn);
101    }, true);
102
103    // Change handler: toggle a class on closest ancestor based on `this.checked`,
104    // plus generic data-csp-change-fn dispatch.
105    document.addEventListener('change', function (e) {
106        var t = e.target.closest('[data-csp-toggle-closest]');
107        if (t) {
108            var ancestor = t.closest(t.dataset.cspToggleClosest);
109            if (ancestor) {
110                var c = t.dataset.cspToggleClassOnChange || 'selected';
111                ancestor.classList.toggle(c, !!t.checked);
112            }
113        }
114        var fnEl = e.target.closest('[data-csp-change-fn]');
115        if (fnEl) dispatchFn(fnEl, e, fnEl.dataset.cspChangeFn);
116    }, true);
117
118    // Drag-and-drop generic dispatch.
119    ['dragover', 'dragleave', 'drop', 'dragstart', 'dragend'].forEach(function (evt) {
120        var attr = 'data-csp-' + evt + '-fn';
121        var prop = 'csp' + evt.charAt(0).toUpperCase() + evt.slice(1) + 'Fn';
122        document.addEventListener(evt, function (e) {
123            var t = e.target.closest('[' + attr + ']');
124            if (t) dispatchFn(t, e, t.dataset[prop]);
125        }, true);
126    });
127
128    // Keydown Enter -> dispatch a window function. `data-csp-on-enter="fnName"`.
129    document.addEventListener('keydown', function (e) {
130        if (e.key !== 'Enter') return;
131        var t = e.target.closest('[data-csp-on-enter]');
132        if (!t) return;
133        var fn = window[t.dataset.cspOnEnter];
134        if (typeof fn === 'function') fn();
135    });
136
137    // img onerror fallback via data-csp-img-fallback="url" or generic data-csp-error-fn.
138    document.addEventListener('error', function (e) {
139        var tgt = e.target;
140        if (tgt && tgt.tagName === 'IMG' && tgt.dataset && tgt.dataset.cspImgFallback) {
141            var fb = tgt.dataset.cspImgFallback;
142            tgt.removeAttribute('data-csp-img-fallback');
143            tgt.src = fb;
144        }
145        if (tgt && tgt.dataset && tgt.dataset.cspErrorFn) {
146            dispatchFn(tgt, e, tgt.dataset.cspErrorFn);
147        }
148        // Hide-on-error: data-csp-error-hide="true" sets display:none on the element.
149        if (tgt && tgt.dataset && tgt.dataset.cspErrorHide === 'true') {
150            tgt.style.display = 'none';
151        }
152        // Remove-on-error: data-csp-error-remove="true" removes the element from DOM.
153        if (tgt && tgt.dataset && tgt.dataset.cspErrorRemove === 'true') {
154            tgt.remove();
155        }
156    }, true);
157
158    // Built-in helper functions exposed on window for data-csp-fn dispatch.
159    window.cspMulti = function (fnA, argA, fnB, argB) {
160        if (typeof window[fnA] === 'function') window[fnA](argA);
161        if (typeof window[fnB] === 'function') window[fnB](argB);
162    };
163    window.cspToggleSelfWithChecked = function (cls, innerSel) {
164        // For onclick on a wrapper element where inner checkbox state drives toggle.
165        // dispatcher passes (el, ev) appended; the first 2 args are cls, innerSel.
166        // The element is the last-but-one arg, but our dispatcher uses placeholders:
167        // we receive args + el + ev. Detect el from arguments.
168        var el = arguments[arguments.length - 2];
169        if (el && el.querySelector) {
170            var inner = el.querySelector(innerSel);
171            el.classList.toggle(cls, !!(inner && inner.checked));
172        }
173    };
174    window.cspClipCopy = function (val, toast) {
175        if (navigator.clipboard) navigator.clipboard.writeText(val);
176        if (window.toastr && window.toastr.success) window.toastr.success(toast || 'Copied');
177    };
178    window.cspSubmitForm = function (formId) {
179        var f = document.getElementById(formId);
180        if (f && typeof f.submit === 'function') f.submit();
181    };
182    window.cspSpOpenThread = function (partner) {
183        if (window.SP && typeof window.SP.openThread === 'function') window.SP.openThread(partner);
184    };
185    window.cspSpDial = function (phone) {
186        if (window.SP && typeof window.SP.dial === 'function') window.SP.dial(phone);
187    };
188    window.cspHistoryBack = function () { if (window.history && window.history.length > 1) window.history.back(); };
189    // No-op helper. Used with data-csp-prevent="true" to gate default link nav.
190    window.cspNoop = function () {};
191    // window.print helper for data-csp-fn dispatch.
192    window.cspWindowPrint = function () { window.print(); };
193    // location.reload() helper for data-csp-fn dispatch.
194    window.cspReload = function () { window.location.reload(); };
195    // Hide self and show next sibling inline. For broken img with a fallback span.
196    window.cspImgErrShowSibling = function () {
197        var el = arguments[arguments.length - 2];
198        if (!el) return;
199        el.style.display = 'none';
200        if (el.nextElementSibling) el.nextElementSibling.style.display = 'inline';
201    };
202    // Remove a class from parent then remove self. Used for blog hero images.
203    window.cspImgErrRemoveWithParentCls = function (cls) {
204        var el = arguments[arguments.length - 2];
205        if (!el) return;
206        if (cls && el.parentElement) el.parentElement.classList.remove(cls);
207        el.remove();
208    };
209
210    document.addEventListener('click', function (e) {
211        // Named handler dispatch
212        var actionEl = e.target.closest('[data-csp-action]');
213        if (actionEl) {
214            var name = actionEl.dataset.cspAction;
215            if (registry[name]) {
216                registry[name](actionEl, e);
217            }
218        }
219
220        // Generic: toggle a class on self / parent / closest / selector
221        // data-toggle-active="self|parent|closest:.sel|.global-sel" toggles 'active' (or
222        // data-toggle-class) on the matching element.
223        var toggleEl = e.target.closest('[data-toggle-active]');
224        if (toggleEl) {
225            var sel = toggleEl.dataset.toggleActive;
226            var cls = toggleEl.dataset.toggleClass || 'active';
227            if (sel === 'self') {
228                toggleEl.classList.toggle(cls);
229            } else if (sel === 'parent') {
230                if (toggleEl.parentElement) toggleEl.parentElement.classList.toggle(cls);
231            } else if (sel.indexOf('closest:') === 0) {
232                var closestTarget = toggleEl.closest(sel.slice('closest:'.length));
233                if (closestTarget) closestTarget.classList.toggle(cls);
234            } else if (sel) {
235                var t = document.querySelector(sel);
236                if (t) t.classList.toggle(cls);
237            }
238        }
239
240        // Generic: smooth-scroll to element by selector
241        var scrollEl = e.target.closest('[data-scroll-to]');
242        if (scrollEl) {
243            var target = document.querySelector(scrollEl.dataset.scrollTo);
244            if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth' }); }
245        }
246
247        // Generic: navigate to URL on click (replaces onclick="window.location.href=...").
248        // Use data-href-target="_blank" to open in new tab.
249        var hrefEl = e.target.closest('[data-href]');
250        if (hrefEl) {
251            var url = hrefEl.dataset.href;
252            if (url) {
253                if (hrefEl.dataset.hrefTarget === '_blank') window.open(url, '_blank');
254                else window.location.href = url;
255            }
256        }
257
258        // Generic: show/hide an element by selector.
259        var showEl = e.target.closest('[data-csp-show]');
260        if (showEl) {
261            var s = document.querySelector(showEl.dataset.cspShow);
262            if (s) s.style.display = showEl.dataset.cspShowAs || 'block';
263        }
264        var hideEl = e.target.closest('[data-csp-hide]');
265        if (hideEl) {
266            var h = document.querySelector(hideEl.dataset.cspHide);
267            if (h) h.style.display = 'none';
268        }
269
270        // Generic: relay click to another element.
271        var trigEl = e.target.closest('[data-csp-click-trigger]');
272        if (trigEl) {
273            var tgt = document.querySelector(trigEl.dataset.cspClickTrigger);
274            if (tgt) tgt.click();
275        }
276
277        // Generic: remove a CSS class from an element by selector.
278        var rmEl = e.target.closest('[data-csp-remove-class-from]');
279        if (rmEl) {
280            var rmTgt = document.querySelector(rmEl.dataset.cspRemoveClassFrom);
281            if (rmTgt) rmTgt.classList.remove(rmEl.dataset.cspRemoveClass || '');
282        }
283        // Symmetric add-class.
284        var addEl = e.target.closest('[data-csp-add-class-to]');
285        if (addEl) {
286            var addTgt = document.querySelector(addEl.dataset.cspAddClassTo);
287            if (addTgt) addTgt.classList.add(addEl.dataset.cspAddClass || '');
288        }
289
290        // (form handlers are wired up via submit listener below, not here)
291        // FAQ accordion (open parent + measure next-sibling height).
292        // Replaces onclick="this.parentElement.classList.toggle('open');var a=this.nextElementSibling;a.style.maxHeight=a.style.maxHeight?null:a.scrollHeight+'px'"
293        var faqEl = e.target.closest('[data-csp-faq-toggle]');
294        if (faqEl && faqEl.parentElement) {
295            faqEl.parentElement.classList.toggle('open');
296            var ans = faqEl.nextElementSibling;
297            if (ans) ans.style.maxHeight = ans.style.maxHeight ? null : ans.scrollHeight + 'px';
298        }
299
300        // Confirm-then-action helper. data-csp-confirm="msg" + data-csp-fn=... still
301        // dispatches via the named-fn block above only if confirmed. If no fn, just
302        // gates the default form submit.
303        var confirmEl = e.target.closest('[data-csp-confirm]');
304        if (confirmEl) {
305            if (!window.confirm(confirmEl.dataset.cspConfirm || 'Are you sure?')) {
306                e.preventDefault();
307                e.stopPropagation();
308                return; // prevent further dispatchers
309            }
310        }
311
312        // Bootstrap modal show (replaces inline `new bootstrap.Modal(...).show()`).
313        var bsModalEl = e.target.closest('[data-csp-bs-modal-show]');
314        if (bsModalEl && window.bootstrap && window.bootstrap.Modal) {
315            var modal = document.querySelector(bsModalEl.dataset.cspBsModalShow);
316            if (modal) new window.bootstrap.Modal(modal).show();
317        }
318
319        // Toggle display:block <-> none on a target by selector.
320        var toggleDispEl = e.target.closest('[data-csp-toggle-display]');
321        if (toggleDispEl) {
322            var dispTgt = document.querySelector(toggleDispEl.dataset.cspToggleDisplay);
323            if (dispTgt) {
324                dispTgt.style.display = (dispTgt.style.display === 'block') ? 'none' : 'block';
325            }
326        }
327
328        // Generic: submit the closest ancestor matching selector.
329        var subEl = e.target.closest('[data-csp-submit-closest]');
330        if (subEl) {
331            var form = subEl.closest(subEl.dataset.cspSubmitClosest);
332            if (form && typeof form.submit === 'function') form.submit();
333        }
334
335        // Generic: set a global variable then dispatch a fn. Used for paginators
336        // like `currentPage=N; renderPosts()` -> data-csp-set="currentPage" data-csp-set-value="N" data-csp-fn="renderPosts"
337        var setEl = e.target.closest('[data-csp-set]');
338        if (setEl) {
339            var k = setEl.dataset.cspSet;
340            var v = setEl.dataset.cspSetValue;
341            try {
342                if (v === 'inc') { window[k] = (window[k] || 0) + 1; }
343                else if (v === 'dec') { window[k] = (window[k] || 0) - 1; }
344                else {
345                    var parsed = (v !== undefined && v.match && v.match(/^-?\d+(?:\.\d+)?$/)) ? Number(v) : v;
346                    window[k] = parsed;
347                }
348            } catch (err) { /* ignore */ }
349        }
350
351        // Modal-backdrop close: replaces onclick="if(event.target===this){...}"
352        var modalEl = e.target.closest('[data-csp-modal-close]');
353        if (modalEl && e.target === modalEl) {
354            var mc = modalEl.dataset.cspModalClose;
355            // Either close a target by selector, or hide self when no selector given.
356            var closeTarget = mc ? document.querySelector(mc) : modalEl;
357            if (closeTarget) closeTarget.style.display = 'none';
358        }
359
360        // Generic function-call dispatch (replaces onclick="fn(args)").
361        // window.<fn> is invoked with parsed args; "$el" placeholders are
362        // substituted with the clicked element, "$ev" with the event itself.
363        var fnEl = e.target.closest('[data-csp-fn]');
364        if (fnEl) dispatchFn(fnEl, e, fnEl.dataset.cspFn);
365    });
366})();

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.