From 25c4efe4b692b6bc085abaf6bcac1fac8f3a1806 Mon Sep 17 00:00:00 2001 From: zaaack Date: Wed, 28 Feb 2018 09:48:32 +0800 Subject: [PATCH] Add escapeHtml --- src/Fable.React/Fable.Helpers.React.fs | 5 +- src/Fable.React/Fable.Helpers.ReactServer.fs | 66 ++++++++++++++++++-- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/Fable.React/Fable.Helpers.React.fs b/src/Fable.React/Fable.Helpers.React.fs index 78fae10b..a7843a09 100644 --- a/src/Fable.React/Fable.Helpers.React.fs +++ b/src/Fable.React/Fable.Helpers.React.fs @@ -747,6 +747,7 @@ open Fable.Import.React [] type HTMLNode = | Text of string +| RawText of string | Node of string * IProp seq * ReactElement seq | List of ReactElement seq with interface ReactElement @@ -846,10 +847,10 @@ let inline ofOption (o: ReactElement option): ReactElement = let inline opt (o: ReactElement option): ReactElement = ofOption o /// Cast an int to a React element (erased in runtime) -let inline ofInt (i: int): ReactElement = str (string i) +let inline ofInt (i: int): ReactElement = HTMLNode.RawText (string i) :> ReactElement /// Cast a float to a React element (erased in runtime) -let inline ofFloat (f: float): ReactElement = str (string f) +let inline ofFloat (f: float): ReactElement = HTMLNode.RawText (string f) :> ReactElement /// Returns a list **from .render() method** let inline ofList (els: ReactElement list): ReactElement = HTMLNode.List els :> ReactElement diff --git a/src/Fable.React/Fable.Helpers.ReactServer.fs b/src/Fable.React/Fable.Helpers.ReactServer.fs index 635b4de4..8e7f564f 100644 --- a/src/Fable.React/Fable.Helpers.ReactServer.fs +++ b/src/Fable.React/Fable.Helpers.ReactServer.fs @@ -1,14 +1,62 @@ module Fable.Helpers.ReactServer +open System open System.Text +open System.Text.RegularExpressions open Fable.Import.React open Fable.Helpers.React open Fable.Helpers.React.Props // Adapted from https://github.com/emotion-js/emotion/blob/182e34bab2b2028c96d513b67ed86faee1b642b2/packages/emotion-utils/src/index.js#L13 -let unitlessCssProps = set [ "animation-iteration-count"; "border-image-outset"; "border-image-slice"; "border-image-width"; "box-flex"; "box-flex-group"; "box-ordinal-group"; "column-count"; "columns"; "flex"; "flex-grow"; "flex-positive"; "flex-shrink"; "flex-negative"; "flex-order"; "grid-row"; "grid-row-end"; "grid-row-span"; "grid-row-start"; "grid-column"; "grid-column-end"; "grid-column-span"; "grid-column-start"; "font-weight"; "line-height"; "opacity"; "order"; "orphans"; "tab-size"; "widows"; "z-index"; "zoom"; "-webkit-line-clamp"; "fill-opacity"; "flood-opacity"; "stop-opacity"; "stroke-dasharray"; "stroke-dashoffset"; "stroke-miterlimit"; "stroke-opacity"; "stroke-width" ] +let private unitlessCssProps = set [ "animation-iteration-count"; "border-image-outset"; "border-image-slice"; "border-image-width"; "box-flex"; "box-flex-group"; "box-ordinal-group"; "column-count"; "columns"; "flex"; "flex-grow"; "flex-positive"; "flex-shrink"; "flex-negative"; "flex-order"; "grid-row"; "grid-row-end"; "grid-row-span"; "grid-row-start"; "grid-column"; "grid-column-end"; "grid-column-span"; "grid-column-start"; "font-weight"; "line-height"; "opacity"; "order"; "orphans"; "tab-size"; "widows"; "z-index"; "zoom"; "-webkit-line-clamp"; "fill-opacity"; "flood-opacity"; "stop-opacity"; "stroke-dasharray"; "stroke-dashoffset"; "stroke-miterlimit"; "stroke-opacity"; "stroke-width" ] +let escapeHtml (str: string) = + let escaped = StringBuilder() + let splits = str.Split('"', '\'', '&', '<', '>') + let mutable charIndex = -1 + for i = 0 to splits.Length - 2 do + let part = splits.[i] + escaped.Append(part) |> ignore + charIndex <- charIndex + part.Length + 1 + let char = str.[charIndex] + ignore ( + match char with + | '"' -> escaped.Append(""") + | '&' -> escaped.Append("&") + | ''' -> escaped.Append("'") // modified from escape-html; used to be ''' + | '<' -> escaped.Append("<") + | '>' -> escaped.Append(">") + | c -> escaped.Append(c) + ) + escaped.Append(Array.last splits) |> ignore + escaped.ToString() +// case 38: // & +// escape = '&'; +// break; +// case 39: // ' +// escape = '''; // modified from escape-html; used to be ''' +// break; +// case 60: // < +// escape = '<'; +// break; +// case 62: // > +// escape = '>'; +// break; +// default: +// continue; +// } + +// if (lastIndex !== index) { +// html += str.substring(lastIndex, index); +// } + +// lastIndex = index + 1; +// html += escape; +// } + +// return lastIndex !== index ? html + str.substring(lastIndex, index) : html; +// } let inline private addUnit (key: string) (value: string) = if unitlessCssProps |> Set.contains key |> not then value + "px" @@ -23,6 +71,8 @@ let private cssProp (key: string) (value: obj) = key + ": " + value + ";" +let private cssPropRegex = Regex("([A-Z])") + let private renderCssProp (prop: CSSProp): string = match prop with | AlignContent v -> cssProp "align-content" v @@ -430,10 +480,10 @@ let private renderCssProp (prop: CSSProp): string = | WritingMode v -> cssProp "writing-mode" v | ZIndex v -> cssProp "z-index" v | Zoom v -> cssProp "zoom" v - | CSSProp.Custom (key, value) -> cssProp key value + | CSSProp.Custom (key, value) -> cssProp key (cssPropRegex.Replace(string value, "-$1").ToLower()) let private renderHtmlAttr (attr: HTMLAttr): string = - let inline pair (key: string) (value: string) = key + "=\"" + value + "\"" + let inline pair (key: string) (value: string) = key + "=\"" + (escapeHtml value) + "\"" let inline boolAttr (key: string) (value: bool) = if value then key else "" match attr with | DefaultChecked v | Checked v -> boolAttr "checked" v @@ -590,7 +640,7 @@ let private renderHtmlAttr (attr: HTMLAttr): string = | Data (key, value) -> pair ("data-" + key) (string value) let private renderSVGAttr (attr: SVGAttr): string = - let inline pair (key: string) (value: obj) = key + "=\"" + (string value) + "\"" + let inline pair (key: string) (value: obj) = key + "=\"" + (value |> string |> escapeHtml) + "\"" match attr with | SVGAttr.ClipPath v -> pair "clip-path" v | SVGAttr.Cx v -> pair "cx" v @@ -651,7 +701,7 @@ let private renderSVGAttr (attr: SVGAttr): string = | SVGAttr.Y v -> pair "y" v | SVGAttr.Custom (key, value) -> pair key value -let private renderAttrs (attrs: IHTMLProp seq) = +let private renderAttrs (attrs: IProp seq) = let html = StringBuilder() let mutable childHtml = None for attr in attrs do @@ -664,6 +714,9 @@ let private renderAttrs (attrs: IHTMLProp seq) = | :? HTMLAttr as attr -> html.Append(renderHtmlAttr attr) |> ignore html.Append(" ") |> ignore + | :? SVGAttr as attr -> + html.Append(renderSVGAttr attr) |> ignore + html.Append(" ") |> ignore | _ -> () html.ToString().Trim(), childHtml @@ -678,7 +731,8 @@ let renderToString (htmlNode: ReactElement): string = html.ToString() match htmlNode :?> HTMLNode with - | HTMLNode.Text str -> str + | HTMLNode.Text str -> escapeHtml str + | HTMLNode.RawText str -> str | HTMLNode.Node (tag, attrs, children) -> let attrs, child = renderAttrs (attrs |> Seq.cast) let child =