|
213 | 213 | function proxyHistoryURL() { return activeProxyPath + activeProxyFragment; } |
214 | 214 | function isHTTPURL(raw) { try { const u = new URL(String(raw), baseURL); return u.protocol === 'http:' || u.protocol === 'https:'; } catch { return false; } } |
215 | 215 | function hasExecutableURLScheme(raw) { return /^(?:javascript|data|vbscript):/i.test(String(raw).trim()); } |
| 216 | + function hasDangerousURLScheme(raw) { return /^(?:javascript|vbscript):/i.test(String(raw).trim()); } |
| 217 | + function shouldBlockURLAttribute(el, key, raw) { |
| 218 | + const tag = el && el.localName; |
| 219 | + const localKey = attrLocalName(key); |
| 220 | + const strict = localKey === 'src' && tag === 'script' || localKey === 'src' && (tag === 'iframe' || tag === 'frame') || usesRawURLAttribute(el, key); |
| 221 | + return strict ? hasExecutableURLScheme(raw) : hasDangerousURLScheme(raw); |
| 222 | + } |
216 | 223 | function blockedURLValue(el, key) { const tag = el && el.localName; return key === 'src' && (tag === 'iframe' || tag === 'frame') ? 'about:blank' : key === 'src' && tag === 'script' ? ZP.errorPath('POLICY_BLOCKED') : '#'; } |
217 | 224 | function blockExecutableURL(el, key, raw) { urlMeta.delete(el); Native.setAttribute.call(el, 'data-zp-target-url', ''); Native.setAttribute.call(el, 'data-zp-blocked-url', String(raw).trim()); Native.setAttribute.call(el, key, blockedURLValue(el, key)); if (key === 'src' && (el.localName === 'iframe' || el.localName === 'frame')) instrumentIframe(el); } |
218 | 225 | function isIntegrityBearing(el) { const tag = el && el.localName; return tag === 'script' || tag === 'link'; } |
|
1347 | 1354 | return; |
1348 | 1355 | } |
1349 | 1356 | if (rel && Native.removeAttribute) Native.removeAttribute.call(el, 'data-zp-blocked-rel'); |
1350 | | - if (hasSuppressedBlockedLinkRel(el)) blockLinkURL(el, Native.getAttribute.call(el, 'href') || ''); |
| 1357 | + if (hasSuppressedBlockedLinkRel(el)) { |
| 1358 | + blockLinkURL(el, Native.getAttribute.call(el, 'href') || ''); |
| 1359 | + return; |
| 1360 | + } |
| 1361 | + const href = Native.getAttribute.call(el, 'href') || ''; |
| 1362 | + if (href && isHTTPURL(href) && !String(href).startsWith(proxyOrigin)) { |
| 1363 | + const target = targetURL(href); |
| 1364 | + const alreadyMapped = urlMeta.get(el) === target && Native.getAttribute.call(el, 'data-zp-target-url') === target && Native.getAttribute.call(el, 'href') === target; |
| 1365 | + urlMeta.set(el, target); |
| 1366 | + if (Native.getAttribute.call(el, 'data-zp-target-url') !== target) Native.setAttribute.call(el, 'data-zp-target-url', target); |
| 1367 | + if (!alreadyMapped && Native.getAttribute.call(el, 'href') !== target) Native.setAttribute.call(el, 'href', target); |
| 1368 | + } |
1351 | 1369 | } |
1352 | 1370 | function sanitizeSerializedHTML(html) { |
1353 | 1371 | const parserDoc = Native.createHTMLDocument ? Native.createHTMLDocument('') : document.implementation.createHTMLDocument(''); |
|
1414 | 1432 | return null; |
1415 | 1433 | }; |
1416 | 1434 | if (prop === Symbol.iterator) return function*(){ for (let i = 0; i < length(); i++) yield nth(i); }; |
1417 | | - if (/^(?:0|[1-9]\\d*)$/.test(String(prop))) return nth(Number(prop)); |
| 1435 | + if (/^(?:0|[1-9]\d*)$/.test(String(prop))) { |
| 1436 | + const index = Number(prop); |
| 1437 | + return index < length() ? nth(index) : undefined; |
| 1438 | + } |
1418 | 1439 | const value = raw && raw[prop]; |
1419 | 1440 | return typeof value === 'function' ? value.bind(raw) : value; |
1420 | 1441 | }, |
|
1509 | 1530 | } |
1510 | 1531 | if (this.localName === 'script' && (localKey === 'src' || localKey === 'href')) return setScriptSource(this, v); |
1511 | 1532 | if (isURLBearing(this, key)) { |
1512 | | - if (hasExecutableURLScheme(v)) return blockExecutableURL(this, localKey, v); |
| 1533 | + if (shouldBlockURLAttribute(this, localKey, v)) return blockExecutableURL(this, localKey, v); |
1513 | 1534 | if (isHTTPURL(v)) { |
1514 | 1535 | const t = targetURL(v); |
1515 | 1536 | urlMeta.set(this, t); |
|
1531 | 1552 | if (key === 'integrity' && isIntegrityBearing(this)) return setBackedIntegrity(this, v); |
1532 | 1553 | if (this.localName === 'script' && (localKey === 'src' || localKey === 'href')) return setScriptSource(this, v); |
1533 | 1554 | if (isURLBearing(this, key)) { |
1534 | | - if (hasExecutableURLScheme(v)) return blockExecutableURL(this, localKey, v); |
| 1555 | + if (shouldBlockURLAttribute(this, localKey, v)) return blockExecutableURL(this, localKey, v); |
1535 | 1556 | if (isHTTPURL(v)) { |
1536 | 1557 | const t = targetURL(v); |
1537 | 1558 | urlMeta.set(this, t); |
|
1665 | 1686 | set(v) { |
1666 | 1687 | if (isBlockedLink(this) || hasSuppressedBlockedLinkRel(this)) return blockLinkURL(this, v); |
1667 | 1688 | const value = String(v); |
1668 | | - if (hasExecutableURLScheme(value)) return blockExecutableURL(this, 'href', value); |
| 1689 | + if (shouldBlockURLAttribute(this, 'href', value)) return blockExecutableURL(this, 'href', value); |
1669 | 1690 | if (isHTTPURL(value)) { |
1670 | 1691 | const t = targetURL(value); |
1671 | 1692 | urlMeta.set(this, t); |
1672 | 1693 | Native.setAttribute.call(this, 'data-zp-target-url', t); |
| 1694 | + return hrefDescriptor.set ? hrefDescriptor.set.call(this, t) : Native.setAttribute.call(this, 'href', t); |
1673 | 1695 | } |
1674 | 1696 | return hrefDescriptor.set ? hrefDescriptor.set.call(this, value) : Native.setAttribute.call(this, 'href', value); |
1675 | 1697 | }, |
|
1729 | 1751 | } |
1730 | 1752 | function instrumentScriptElement(el) { prepareScriptElement(el); } |
1731 | 1753 | function isSVGURLBearing(el, key) { return el && el.namespaceURI === 'http://www.w3.org/2000/svg' && attrLocalName(key) === 'href' && /^(a|image|use|script)$/.test(el.localName || ''); } |
1732 | | - function isURLBearing(el, key) { const tag = el.localName; const localKey = attrLocalName(key); return localKey === 'href' && (tag === 'a' || tag === 'area' || isSVGURLBearing(el, key)) || localKey === 'action' && tag === 'form' || localKey === 'formaction' && (tag === 'input' || tag === 'button') || localKey === 'src' && (tag === 'iframe' || tag === 'frame' || tag === 'script'); } |
| 1754 | + function isURLBearing(el, key) { const tag = el.localName; const localKey = attrLocalName(key); return localKey === 'href' && (tag === 'a' || tag === 'area' || tag === 'link' || isSVGURLBearing(el, key)) || localKey === 'action' && tag === 'form' || localKey === 'formaction' && (tag === 'input' || tag === 'button') || localKey === 'src' && (tag === 'iframe' || tag === 'frame' || tag === 'script' || tag === 'img' || tag === 'source' || tag === 'audio' || tag === 'video' || tag === 'track' || tag === 'input') || localKey === 'poster' && tag === 'video'; } |
1733 | 1755 | function executableScriptDataType(el) { |
1734 | 1756 | const kind = executableScriptKindForElement(el); |
1735 | 1757 | if (kind) return kind; |
|
1815 | 1837 | Native.setAttribute.call(node, 'data-zp-blocked-' + lowerAttr, val); |
1816 | 1838 | Native.setAttribute.call(node, attrName, rewriteEventAttribute(val)); |
1817 | 1839 | } |
| 1840 | + if (isURLBearing(node, lowerAttr)) enforceObservedAttribute(node, lowerAttr); |
1818 | 1841 | } |
1819 | 1842 | } |
1820 | 1843 | } |
|
1838 | 1861 | if (r.type === 'attributes') enforceObservedAttribute(r.target, String(r.attributeName || '').toLowerCase()); |
1839 | 1862 | else for (const n of r.addedNodes || []) { syncBaseElement(n); enforceSubtreePolicies(n); instrumentDescendantIframes(n); } |
1840 | 1863 | } |
1841 | | - }).observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['href', 'xlink:href', 'src', 'srcdoc', 'action', 'formaction', 'integrity', 'type', 'rel', 'target'] }); |
| 1864 | + }).observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['href', 'xlink:href', 'src', 'srcdoc', 'action', 'formaction', 'poster', 'integrity', 'type', 'rel', 'target'] }); |
1842 | 1865 | } catch {} |
1843 | 1866 | } |
1844 | 1867 | function enforceObservedAttribute(el, key) { |
|
1868 | 1891 | } |
1869 | 1892 | if (!isURLBearing(el, key)) return; |
1870 | 1893 | const raw = Native.getAttribute.call(el, key); |
1871 | | - if (hasExecutableURLScheme(raw)) { blockExecutableURL(el, localKey, raw); return; } |
| 1894 | + if (shouldBlockURLAttribute(el, localKey, raw)) { blockExecutableURL(el, localKey, raw); return; } |
1872 | 1895 | if (!raw || !isHTTPURL(raw) || String(raw).startsWith(proxyOrigin)) return; |
1873 | 1896 | let target; |
1874 | 1897 | try { target = targetURL(raw); } catch { return; } |
|
1888 | 1911 | if (!node || typeof node !== 'object') return; |
1889 | 1912 | if (node.nodeType === 1) { |
1890 | 1913 | enforceElementPolicy(node); |
1891 | | - if (node.querySelectorAll) node.querySelectorAll('script,link,iframe,frame,a,area,form,input,button,svg a,svg image,svg use').forEach(enforceElementPolicy); |
| 1914 | + if (node.querySelectorAll) node.querySelectorAll('script,link,iframe,frame,a,area,form,input,button,img,source,audio,video,track,svg a,svg image,svg use').forEach(enforceElementPolicy); |
1892 | 1915 | } |
1893 | 1916 | } |
1894 | 1917 | function enforceElementPolicy(el) { |
|
0 commit comments