This commit is contained in:
Koen J
2026-02-16 11:35:29 +01:00
parent 13aa49726a
commit fa1954ceef
3 changed files with 26 additions and 7 deletions
@@ -140,7 +140,12 @@ class PackageBrowser: V8Package {
val charset = charsetFromContentType(contentType) ?: Charsets.UTF_8 val charset = charsetFromContentType(contentType) ?: Charsets.UTF_8
val html = bodyBytes.toString(charset) val html = bodyBytes.toString(charset)
val injected = injectIntoHead(html, scripts.joinToString("\n")) val cspHeader = resp.header("Content-Security-Policy")
?: resp.header("Content-Security-Policy-Report-Only")
val nonce = extractNonceFromCsp(cspHeader) ?: extractNonceFromHtml(html)
val injected = injectIntoHead(html, scripts.joinToString("\n"), nonce)
val outBytes = injected.toByteArray(charset) val outBytes = injected.toByteArray(charset)
val headers = resp.headers.toMultimap() val headers = resp.headers.toMultimap()
.mapValues { it.value.joinToString(",") } .mapValues { it.value.joinToString(",") }
@@ -300,7 +305,7 @@ class PackageBrowser: V8Package {
private fun releaseReadyIfCurrent(url: String?) { private fun releaseReadyIfCurrent(url: String?) {
if (url == null) return if (url == null) return
val expected = _expectedMainUrl val expected = _expectedMainUrl
if (url.toUri().host != expected?.toUri()?.host) return if (url.trimEnd('/') != expected?.trimEnd('/')) return
_readySemaphore?.release() _readySemaphore?.release()
_readySemaphore = null _readySemaphore = null
@@ -442,8 +447,9 @@ class PackageBrowser: V8Package {
return runCatching { Charset.forName(name) }.getOrNull() return runCatching { Charset.forName(name) }.getOrNull()
} }
private fun injectIntoHead(html: String, js: String): String { private fun injectIntoHead(html: String, js: String, nonce: String?): String {
val tag = "<script>\n$js\n</script>\n" val nonceAttr = nonce?.let { " nonce=\"${escapeHtmlAttr(it)}\"" } ?: ""
val tag = "<script$nonceAttr>\n$js\n</script>\n"
val head = Regex("(?i)<head[^>]*>").find(html) val head = Regex("(?i)<head[^>]*>").find(html)
if (head != null) { if (head != null) {
@@ -455,7 +461,6 @@ class PackageBrowser: V8Package {
append(html, i, html.length) append(html, i, html.length)
} }
} }
return tag + html return tag + html
} }
@@ -467,6 +472,20 @@ class PackageBrowser: V8Package {
} }
} }
private fun extractNonceFromCsp(csp: String?): String? {
if (csp.isNullOrBlank()) return null
val m = Regex("(?i)'nonce-([^'\\s;]+)'").find(csp) ?: return null
return m.groupValues[1]
}
private fun extractNonceFromHtml(html: String): String? {
val m = Regex("(?i)<script[^>]*\\snonce\\s*=\\s*['\"]([^'\"]+)['\"][^>]*>").find(html)
return m?.groupValues?.get(1)
}
private fun escapeHtmlAttr(s: String): String =
s.replace("&", "&amp;").replace("\"", "&quot;")
@Serializable @Serializable
private data class ConsoleBridgeMsg( private data class ConsoleBridgeMsg(
val t: String, val t: String,