|
1226 | 1226 | return false; |
1227 | 1227 | } |
1228 | 1228 | function isBlockedLink(el) { return el && el.localName === 'link' && isBlockedLinkRelValue(Native.getAttribute.call(el, 'rel') || ''); } |
| 1229 | + function hasSuppressedBlockedLinkRel(el) { return el && el.localName === 'link' && isBlockedLinkRelValue(Native.getAttribute.call(el, 'data-zp-blocked-rel') || ''); } |
| 1230 | + function suppressBlockedLinkRel(el, rawRel) { |
| 1231 | + Native.setAttribute.call(el, 'data-zp-blocked-rel', String(rawRel)); |
| 1232 | + if (Native.removeAttribute) Native.removeAttribute.call(el, 'rel'); |
| 1233 | + blockLinkURL(el, Native.getAttribute.call(el, 'href') || ''); |
| 1234 | + } |
1229 | 1235 | function blockLinkURL(el, raw) { |
1230 | 1236 | if (raw != null && String(raw) !== '') Native.setAttribute.call(el, 'data-zp-blocked-url', String(raw)); |
1231 | 1237 | urlMeta.delete(el); |
1232 | 1238 | if (Native.removeAttribute) Native.removeAttribute.call(el, 'href'); |
1233 | 1239 | } |
1234 | 1240 | function enforceLinkPolicy(el) { |
1235 | | - if (!isBlockedLink(el)) return; |
1236 | | - blockLinkURL(el, Native.getAttribute.call(el, 'href') || ''); |
1237 | | - Native.setAttribute.call(el, 'data-zp-blocked-rel', Native.getAttribute.call(el, 'rel') || ''); |
| 1241 | + if (!el || el.localName !== 'link') return; |
| 1242 | + const rel = Native.getAttribute.call(el, 'rel') || ''; |
| 1243 | + if (isBlockedLinkRelValue(rel)) { |
| 1244 | + suppressBlockedLinkRel(el, rel); |
| 1245 | + return; |
| 1246 | + } |
| 1247 | + if (rel && Native.removeAttribute) Native.removeAttribute.call(el, 'data-zp-blocked-rel'); |
| 1248 | + if (hasSuppressedBlockedLinkRel(el)) blockLinkURL(el, Native.getAttribute.call(el, 'href') || ''); |
1238 | 1249 | } |
1239 | 1250 | function isNavigationTargetElement(el) { |
1240 | 1251 | const tag = el && el.localName; |
|
1251 | 1262 | const localKey = attrLocalName(key); |
1252 | 1263 | if (key === 'integrity' && isIntegrityBearing(this)) return setBackedIntegrity(this, v); |
1253 | 1264 | if (localKey === 'target' && isNavigationTargetElement(this)) return setSafeNavigationTarget(this, k, v); |
1254 | | - if (this.localName === 'link' && localKey === 'rel') { const ret = Native.setAttribute.call(this, k, v); enforceLinkPolicy(this); return ret; } |
1255 | | - if (this.localName === 'link' && localKey === 'href' && isBlockedLink(this)) return blockLinkURL(this, v); |
| 1265 | + if (this.localName === 'link' && localKey === 'rel') { |
| 1266 | + const value = String(v); |
| 1267 | + if (isBlockedLinkRelValue(value)) return suppressBlockedLinkRel(this, value); |
| 1268 | + if (Native.removeAttribute) Native.removeAttribute.call(this, 'data-zp-blocked-rel'); |
| 1269 | + return Native.setAttribute.call(this, k, v); |
| 1270 | + } |
| 1271 | + if (this.localName === 'link' && localKey === 'href' && (isBlockedLink(this) || hasSuppressedBlockedLinkRel(this))) return blockLinkURL(this, v); |
1256 | 1272 | if (key.startsWith('on') && key.length > 2) return Native.setAttribute.call(this, k, rewriteEventAttribute(String(v))); |
1257 | 1273 | if (this.localName === 'base' && localKey === 'href') { |
1258 | 1274 | updateVirtualBase(v); |
|
1392 | 1408 | } |
1393 | 1409 | function installLinkProp(proto) { |
1394 | 1410 | if (!proto) return; |
1395 | | - const d = propertyDescriptor(proto, 'href'); |
1396 | | - if (!d || !d.get) return; |
1397 | | - try { |
| 1411 | + const hrefDescriptor = propertyDescriptor(proto, 'href'); |
| 1412 | + if (hrefDescriptor && hrefDescriptor.get) try { |
1398 | 1413 | Object.defineProperty(proto, 'href', { |
1399 | | - get() { return urlMeta.get(this) || Native.getAttribute.call(this, 'data-zp-target-url') || d.get.call(this); }, |
| 1414 | + get() { return urlMeta.get(this) || Native.getAttribute.call(this, 'data-zp-target-url') || hrefDescriptor.get.call(this); }, |
1400 | 1415 | set(v) { |
1401 | | - if (isBlockedLink(this)) return blockLinkURL(this, v); |
| 1416 | + if (isBlockedLink(this) || hasSuppressedBlockedLinkRel(this)) return blockLinkURL(this, v); |
1402 | 1417 | const value = String(v); |
1403 | 1418 | if (hasExecutableURLScheme(value)) return blockExecutableURL(this, 'href', value); |
1404 | 1419 | if (isHTTPURL(value)) { |
1405 | 1420 | const t = targetURL(value); |
1406 | 1421 | urlMeta.set(this, t); |
1407 | 1422 | Native.setAttribute.call(this, 'data-zp-target-url', t); |
1408 | 1423 | } |
1409 | | - return d.set.call(this, value); |
| 1424 | + return hrefDescriptor.set ? hrefDescriptor.set.call(this, value) : Native.setAttribute.call(this, 'href', value); |
| 1425 | + }, |
| 1426 | + configurable: false |
| 1427 | + }); |
| 1428 | + } catch {} |
| 1429 | + const relDescriptor = propertyDescriptor(proto, 'rel'); |
| 1430 | + if (relDescriptor && relDescriptor.get) try { |
| 1431 | + Object.defineProperty(proto, 'rel', { |
| 1432 | + get() { return relDescriptor.get.call(this); }, |
| 1433 | + set(v) { |
| 1434 | + const value = String(v); |
| 1435 | + if (isBlockedLinkRelValue(value)) return suppressBlockedLinkRel(this, value); |
| 1436 | + if (Native.removeAttribute) Native.removeAttribute.call(this, 'data-zp-blocked-rel'); |
| 1437 | + return relDescriptor.set ? relDescriptor.set.call(this, value) : Native.setAttribute.call(this, 'rel', value); |
1410 | 1438 | }, |
1411 | 1439 | configurable: false |
1412 | 1440 | }); |
|
1419 | 1447 | } |
1420 | 1448 | function isSVGURLBearing(el, key) { return el && el.namespaceURI === 'http://www.w3.org/2000/svg' && attrLocalName(key) === 'href' && /^(a|image|use|script)$/.test(el.localName || ''); } |
1421 | 1449 | 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'); } |
1422 | | - function transformHTML(s) { return String(s).replace(/<base\b[^>]*\shref=(["'])([\s\S]*?)\1[^>]*>/ig, (_, q, href) => baseSyncScript(href)).replace(/<link\b(?=[^>]*\brel\s*=\s*(?:"[^"]*(?:modulepreload|preload|prefetch|preconnect|dns-prefetch|prerender|manifest)[^"]*"|'[^']*(?:modulepreload|preload|prefetch|preconnect|dns-prefetch|prerender|manifest)[^']*'|[^\s>]*(?:modulepreload|preload|prefetch|preconnect|dns-prefetch|prerender|manifest)[^\s>]*))[^>]*>/ig, '').replace(/(<iframe\b[^>]*\ssrcdoc=["'])([\s\S]*?)(["'])/ig, (_, p, h, q) => p + injectSrcdoc(h).replace(/"/g,'"') + q).replace(/<script\b/ig, '<script type="application/x-zeroproxy-blocked" data-zp-blocked-script').replace(/\sintegrity\s*=\s*(\"[^\"]*\"|'[^']*'|[^\s>]+)/ig, (_, value) => ' ' + integrityBackupAttr + '=' + value).replace(/\s(on[a-z0-9_:-]+)\s*=\s*(\"[^\"]*\"|'[^']*'|[^\s>]+)/ig, (_, name, value) => ' data-zp-blocked-' + name.toLowerCase() + '=' + value); } |
| 1450 | + const blockedLinkTagRE = /<link\b(?=[^>]*\brel\s*=\s*(?:"[^"]*(?:modulepreload|preload|prefetch|preconnect|dns-prefetch|prerender|manifest)[^"]*"|'[^']*(?:modulepreload|preload|prefetch|preconnect|dns-prefetch|prerender|manifest)[^']*'|[^\s>]*(?:modulepreload|preload|prefetch|preconnect|dns-prefetch|prerender|manifest)[^\s>]*))[^>]*>/ig; |
| 1451 | + function escapeHTMLAttrValue(value) { |
| 1452 | + return String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); |
| 1453 | + } |
| 1454 | + function tagAttrValue(tag, name) { |
| 1455 | + const m = new RegExp("(?:^|\\s)" + name + "\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'>`=]+))", 'i').exec(tag); |
| 1456 | + return m ? m[1] ?? m[2] ?? m[3] ?? '' : ''; |
| 1457 | + } |
| 1458 | + function rewriteBlockedLinkTag(tag) { |
| 1459 | + const href = tagAttrValue(tag, 'href'); |
| 1460 | + const rel = tagAttrValue(tag, 'rel'); |
| 1461 | + let out = tag.replace(/\s(?:href|rel|data-zp-blocked-url|data-zp-blocked-rel)\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>`=]+)/ig, ''); |
| 1462 | + out = out.replace(/\s*\/?>$/, ''); |
| 1463 | + if (href) out += ' data-zp-blocked-url="' + escapeHTMLAttrValue(href) + '"'; |
| 1464 | + if (rel) out += ' data-zp-blocked-rel="' + escapeHTMLAttrValue(rel) + '"'; |
| 1465 | + return out + '>'; |
| 1466 | + } |
| 1467 | + function transformHTML(s) { |
| 1468 | + return String(s) |
| 1469 | + .replace(/<base\b[^>]*\shref=(["'])([\s\S]*?)\1[^>]*>/ig, (_, q, href) => baseSyncScript(href)) |
| 1470 | + .replace(blockedLinkTagRE, rewriteBlockedLinkTag) |
| 1471 | + .replace(/(<iframe\b[^>]*\ssrcdoc=["'])([\s\S]*?)(["'])/ig, (_, p, h, q) => p + injectSrcdoc(h).replace(/"/g,'"') + q) |
| 1472 | + .replace(/<script\b/ig, '<script type="application/x-zeroproxy-blocked" data-zp-blocked-script') |
| 1473 | + .replace(/\sintegrity\s*=\s*(\"[^\"]*\"|'[^']*'|[^\s>]+)/ig, (_, value) => ' ' + integrityBackupAttr + '=' + value) |
| 1474 | + .replace(/\s(on[a-z0-9_:-]+)\s*=\s*(\"[^\"]*\"|'[^']*'|[^\s>]+)/ig, (_, name, value) => ' data-zp-blocked-' + name.toLowerCase() + '=' + value); |
| 1475 | + } |
1423 | 1476 | function injectSrcdoc(s) { return '<script src="/__zp/zp-core.js"><\/script><script id="__zp-boot" type="application/json">' + bootJSON() + '<\/script><script src="/__zp/runtime-prelude.js"><\/script>' + transformHTML(String(s)); } |
1424 | 1477 | function bootJSON() { return JSON.stringify(boot).replace(/[<>&]/g, c => c === '<' ? '\\u003c' : c === '>' ? '\\u003e' : '\\u0026'); } |
1425 | 1478 | function baseSyncScript(raw) { return '<script>window.__ZP_SET_BASE&&window.__ZP_SET_BASE(' + JSON.stringify(String(raw)).replace(/</g,'\\u003c') + ');<\/script>'; } |
|
1549 | 1602 | return parsed.href; |
1550 | 1603 | } |
1551 | 1604 | if (parsed.protocol === 'data:') return dataWorkerURL(parsed.href); |
1552 | | - return '/__zp/worker-bootstrap.js#u=' + encodeURIComponent(targetURL(raw)) + '&tab=' + encodeURIComponent(boot.tabId); |
| 1605 | + return '/__zp/worker-bootstrap.js#u=' + encodeURIComponent(requestTargetURL(raw)) + '&tab=' + encodeURIComponent(boot.tabId); |
1553 | 1606 | } |
1554 | 1607 | function dataWorkerURL(raw) { |
1555 | 1608 | const comma = raw.indexOf(','); |
|
0 commit comments