Skip to content

dont escape prop and attribute values #9

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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

srghma
Copy link
Contributor

@srghma srghma commented Jun 16, 2020

No description provided.

@srghma srghma changed the title fix: it renders <script async="" type="application&#x2F;javascript" src="chunks&#x2F;defaultVendors~main-063769ad5d827b791041.js"></script> but should <script async="" type="application/javascript" src="chunks/defaultVendors~main-063769ad5d827b791041.js"></script> dont escape prop and attribute values Jun 16, 2020
@srghma
Copy link
Contributor Author

srghma commented Apr 18, 2025

@thomashoneyman can I be added to org?

@flip111
Copy link

flip111 commented Apr 18, 2025

Add srghma to org please, he makes good code 👍

@garyb
Copy link
Member

garyb commented Apr 18, 2025

It might not be escaping correctly just now but they definitely need to be escaped too, otherwise the result will be syntactically invalid when there's a " in the value, for example.

@srghma
Copy link
Contributor Author

srghma commented Apr 19, 2025

  1. deku is using jsdom innerHTML to algo

https://github.com/mikesol/purescript-deku/blob/4d18f6ccebb4a59cdd2a6d2d9e2466349bf8df20/deku-ssr/src/Deku/SSR.purs#L211

https://github.com/mikesol/deku-documentation/blob/63adc7ff7d378fbd4cd264d49a40eaa720f7e25e/renderer/%2BonRenderHtml.js#L22

  1. elm is using
escapeHtml : String -> String
escapeHtml rawText =
    {- https://github.com/elm/virtual-dom/blob/5a5bcf48720bc7d53461b3cd42a9f19f119c5503/src/Elm/Kernel/VirtualDom.server.js#L8-L26 -}
    rawText
        |> String.replace "&" "&amp;"
        |> String.replace "<" "&lt;"
        |> String.replace ">" "&gt;"
        |> String.replace "\"" "&quot;"
        |> String.replace "'" "&#039;"    

so, probably suffers from same issue too

https://github.com/dillonkearns/elm-pages/blob/451c2c03903c34422f165a847d38909b0717096d/src/HtmlPrinter.elm#L14

https://github.com/dillonkearns/elm-pages/blob/451c2c03903c34422f165a847d38909b0717096d/src/Test/Html/Internal/ElmHtml/ToString.elm#L130C57-L130C67

@srghma
Copy link
Contributor Author

srghma commented Apr 19, 2025

@garyb
Proposal 1:

escapeRegex  Regex
escapeRegex = unsafeRegex "[\\\"\\\'/&<>]" global

escapeChar  String  String
escapeChar = case _ of
  "/""&#x2F;"
  ch   → escapeCharExceptSlash ch

escape  String  String
escape = replace' escapeRegex (const <<< escapeChar)
----------
escapeRegexExceptSlash  Regex
escapeRegexExceptSlash = unsafeRegex "[\\\"\\\'&<>]" global

escapeCharExceptSlash  String  String
escapeCharExceptSlash = case _ of
  "\"""&quot;"
  "'""&#39;"
  "&""&amp;"
  "<""&lt;"
  ">""&gt;"
  ch   → ch

escapeExceptSlash  String  String
escapeExceptSlash = replace' escapeRegexExceptSlash (const <<< escapeCharExceptSlash)
----------

IF node == "script" && prop = "type" THEN escapeAllExceptSlash value ELSE escapeHTML value

Proposal 2: always use escapeExceptSlash

srghma added 5 commits April 19, 2025 11:31
…src="chunks&#x2F;defaultVendors~main-063769ad5d827b791041.js"></script>` but should `<script async="" type="application/javascript" src="chunks/defaultVendors~main-063769ad5d827b791041.js"></script>`
…on>` -> dont render prop at all if it is a boolean and is false (this commit matches purescript-halogen/purescript-halogen-vdom@cfd471e)
@srghma
Copy link
Contributor Author

srghma commented Apr 19, 2025

      config =
        [ { char: "\"", charName: "double quotes", escapedChar: "&quot;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "'", charName: "single quotes", escapedChar: "&#39;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "/", charName: "slash", escapedChar: "&#x2F;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "\\", charName: "backslash", escapedChar: "", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "&", charName: "ampersand", escapedChar: "&amp;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "<", charName: "less than", escapedChar: "&lt;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: ">", charName: "more than", escapedChar: "&gt;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "\t", charName: "tab", escapedChar: "&#x9;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "\n", charName: "newline", escapedChar: "&#xA;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "\r", charName: "r", escapedChar: "&#xD;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        , { char: "", charName: "", escapedChar: "&#xD;", elementName: true, attrName: true, attrValue: true, propName: true, propValue: true }
        ]

      config.forEach(x => { try { document.createElement(div${x.char}) } catch (e) { console.error(e)} }) // all are invalid names

VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div"') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div'') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div/') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div\') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div&') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div<') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div>') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div	') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div
') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div
') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14
overrideMethod @ hook.js:608
(anonymous) @ VM291:15
(anonymous) @ VM291:15Understand this errorAI
VM291:15 InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('div') is not a valid name.
    at <anonymous>:15:44
    at Array.forEach (<anonymous>)
    at <anonymous>:15:14

so, we need to add https://github.com/jsdom/xml-name-validator/blob/836f307eec81279d2b1655587892e38a1effe039/lib/xml-name-validator.js#L4 to
"<" <> name <> (if S.null as' then "" else " ") <> as' <>

P.S.

xnv.name(potentialName)

This checks if the string is a valid XML Name, as per the Name production in XML 1.0:

Name ::= NameStartChar (NameChar)*
  • Cannot contain :

  • Used for validating:

    • localName (e.g. <foo>, foo="bar")
    • Non-namespaced names
  • Example valid names:

    • foo
    • _myName
    • número
  • Example invalid:

    • 1foo
    • foo bar
    • foo:bar: is not allowed here!

xnv.qname(potentialQname)

This checks if the string is a valid [Qualified Name (QName)](https://www.w3.org/TR/xml-names/#NT-QName) — which can include a colon:

QName ::= PrefixedName | UnprefixedName
PrefixedName ::= Prefix ':' LocalPart
  • Used where namespace prefixes are allowed

  • Example valid:

    • x:tag
    • svg:circle
    • foo_bar:baz99
    • Also just name is valid (unprefixed QName)
  • Example invalid:

    • :foo (prefix missing)
    • foo: (local name missing)
    • foo:bar:baz (too many colons)

🔧 Usage in w3c-xmlserializer

if (
  requireWellFormed &&
  (node.localName.includes(":") || !xnv.name(node.localName))
)
  • Rejects colons in localName — that's correct, because colons belong in qualifiedName, not localName.

srghma added 2 commits April 19, 2025 14:54
…attr name is invlaid - we ignore invalid chars) (NOTE: we could just use he npm package to escape all, no ignoring/cleaning)
@srghma
Copy link
Contributor Author

srghma commented Apr 19, 2025

I have added more functionality, but escapeHtmlEntity function doesnt escape everything possible (we could use he package to escape all),

and instead of cleanAndEscapeTextNode - we could use he package too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants