-
Notifications
You must be signed in to change notification settings - Fork 0
fix: stop uppercasing script, style, code, and pre content #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,110 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { describe, it, expect, beforeAll, afterAll } from "vitest"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { unstable_dev, type Unstable_DevWorker } from "wrangler"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { uppercasePreservingEntities } from "./rewriter"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("uppercasePreservingEntities", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("uppercases plain text", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(uppercasePreservingEntities("hello world")).toBe("HELLO WORLD"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("preserves named HTML entities", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(uppercasePreservingEntities("a & b")).toBe("A & B"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("preserves hex entities", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(uppercasePreservingEntities("it's fine")).toBe("IT'S FINE"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("preserves decimal entities", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(uppercasePreservingEntities("100% done")).toBe("100% DONE"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("handles mixed entities and text", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(uppercasePreservingEntities("a < b > c")).toBe("A < B > C"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("returns empty string unchanged", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(uppercasePreservingEntities("")).toBe(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe("HTMLRewriter integration", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let worker: Unstable_DevWorker; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| beforeAll(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| worker = await unstable_dev("src/index.ts", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| experimental: { disableExperimentalWarning: true }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| afterAll(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await worker?.stop(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("uppercases regular text in proxied HTML", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const resp = await worker.fetch("/browse/https://httpbin.org/html"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (resp.status !== 200) return; // skip if httpbin is down | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const html = await resp.text(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // httpbin /html returns a page with "Herman Melville" — should be uppercased | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(html).toContain("HERMAN MELVILLE"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("preserves inline script content in body", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const resp = await worker.fetch("/browse/https://www.wikipedia.org"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (resp.status !== 200) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const html = await resp.text(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // wikipedia has inline scripts with 'var' — should NOT be uppercased | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(html).toContain("var "); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(html).not.toMatch(/\bVAR rtlLangs\b/); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("preserves inline style content in body", async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const resp = await worker.fetch("/browse/https://www.wikipedia.org"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (resp.status !== 200) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const html = await resp.text(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // wikipedia has inline styles — should NOT be uppercased | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(html).toMatch(/display:\s*block/i); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(html).not.toMatch(/DISPLAY:\s*BLOCK/); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+52
to
+67
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it("preserves inline script content in body", async () => { | |
| const resp = await worker.fetch("/browse/https://www.wikipedia.org"); | |
| if (resp.status !== 200) return; | |
| const html = await resp.text(); | |
| // wikipedia has inline scripts with 'var' — should NOT be uppercased | |
| expect(html).toContain("var "); | |
| expect(html).not.toMatch(/\bVAR rtlLangs\b/); | |
| }); | |
| it("preserves inline style content in body", async () => { | |
| const resp = await worker.fetch("/browse/https://www.wikipedia.org"); | |
| if (resp.status !== 200) return; | |
| const html = await resp.text(); | |
| // wikipedia has inline styles — should NOT be uppercased | |
| expect(html).toMatch(/display:\s*block/i); | |
| expect(html).not.toMatch(/DISPLAY:\s*BLOCK/); | |
| it("preserves inline script content in body", () => { | |
| const html = ` | |
| <html> | |
| <head></head> | |
| <body> | |
| <p>some body text</p> | |
| <script> | |
| var rtlLangs = ["ar", "he"]; | |
| </script> | |
| </body> | |
| </html> | |
| `; | |
| const transformed = uppercasePreservingEntities(html); | |
| // inline script contents with 'var' should NOT be uppercased | |
| expect(transformed).toContain("var rtlLangs"); | |
| expect(transformed).not.toMatch(/\bVAR rtlLangs\b/); | |
| }); | |
| it("preserves inline style content in body", () => { | |
| const html = ` | |
| <html> | |
| <head></head> | |
| <body> | |
| <p>some other body text</p> | |
| <style> | |
| .example { | |
| display: block; | |
| } | |
| </style> | |
| </body> | |
| </html> | |
| `; | |
| const transformed = uppercasePreservingEntities(html); | |
| // inline style contents should NOT be uppercased | |
| expect(transformed).toMatch(/display:\s*block/i); | |
| expect(transformed).not.toMatch(/DISPLAY:\s*BLOCK/); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -4,15 +4,29 @@ import { uppercaseScript } from "./uppercase-script"; | |||||
| // split on html entities, uppercase only the non-entity segments | ||||||
| const ENTITY_PATTERN = /(&(?:#(?:x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z][a-zA-Z0-9]*);)/g; | ||||||
|
|
||||||
| function uppercasePreservingEntities(raw: string): string { | ||||||
| export function uppercasePreservingEntities(raw: string): string { | ||||||
| return raw | ||||||
| .split(ENTITY_PATTERN) | ||||||
| .map((part, i) => (i % 2 === 0 ? part.toUpperCase() : part)) | ||||||
| .join(""); | ||||||
| } | ||||||
|
|
||||||
| class SimpleTextUppercaser implements HTMLRewriterElementContentHandlers { | ||||||
| class SkipElementTracker implements HTMLRewriterElementContentHandlers { | ||||||
| constructor(private uppercaser: TextUppercaser) {} | ||||||
|
|
||||||
| element(el: Element) { | ||||||
| this.uppercaser.skipDepth++; | ||||||
| el.onEndTag(() => { | ||||||
| this.uppercaser.skipDepth--; | ||||||
| }); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| class TextUppercaser implements HTMLRewriterElementContentHandlers { | ||||||
| skipDepth = 0; | ||||||
|
|
||||||
| text(text: Text) { | ||||||
| if (this.skipDepth > 0) return; | ||||||
| if (text.text) { | ||||||
| text.replace(uppercasePreservingEntities(text.text), { html: true }); | ||||||
| } | ||||||
|
|
@@ -71,7 +85,7 @@ class HeadInjector implements HTMLRewriterElementContentHandlers { | |||||
| // no <base> tag — it would redirect /browse/... paths to the target origin | ||||||
| // URLRewriter already resolves all relative URLs to absolute proxy paths | ||||||
| el.append( | ||||||
| `<style>*:not(input):not(textarea):not(select):not(code):not(pre):not(script):not(style) { text-transform: uppercase !important; }</style>`, | ||||||
| `<style>*:not(input):not(textarea):not(select):not(code):not(pre):not(script):not(style) { text-transform: uppercase !important; } code, pre, textarea, svg { text-transform: none !important; }</style>`, | ||||||
|
||||||
| `<style>*:not(input):not(textarea):not(select):not(code):not(pre):not(script):not(style) { text-transform: uppercase !important; } code, pre, textarea, svg { text-transform: none !important; }</style>`, | |
| `<style>*:not(input):not(textarea):not(select):not(code):not(pre):not(script):not(style):not(noscript) { text-transform: uppercase !important; } code, pre, textarea, svg, noscript { text-transform: none !important; }</style>`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Several integration tests
returnearly when the proxied fetch doesn’t yield a 200 (e.g. if the upstream site is down or outbound network is blocked). That makes the test pass without asserting anything, which can hide regressions. Prefer making these tests deterministic (fixture HTML + direct HTMLRewriter transform, or mockingfetchin the worker), or explicitly skipping with a clear condition (e.g.it.skip/it.skipIf) rather than returning mid-test.