Escape input when creating html
This commit is contained in:
parent
d22b740289
commit
71242ac400
|
@ -344,11 +344,11 @@ isvalid(r::Response, s::Survey) =
|
||||||
# General htmlrenderer
|
# General htmlrenderer
|
||||||
# ---------------------
|
# ---------------------
|
||||||
|
|
||||||
htmlcontent(::FormField, value) = ""
|
html_content(::FormField, value) = ""
|
||||||
htmlelement(::FormField) = "?"
|
html_element(::FormField) = "?"
|
||||||
htmlattrs(::FormField, value) = []
|
html_attrs(::FormField, value) = []
|
||||||
htmlvoidelem(::FormField) = false
|
html_voidelem(::FormField) = false
|
||||||
htmlpostprocess(::FormField, ::Symbol) = identity
|
html_postprocess(::FormField, ::Symbol) = identity
|
||||||
|
|
||||||
elem(e::AbstractString, content::AbstractString="", attrs::Pair{Symbol,<:Any}...) =
|
elem(e::AbstractString, content::AbstractString="", attrs::Pair{Symbol,<:Any}...) =
|
||||||
Html.normal_element(content, e, [], attrs...)
|
Html.normal_element(content, e, [], attrs...)
|
||||||
|
@ -357,20 +357,28 @@ elem(e::AbstractString, attrs::Pair{Symbol,<:Any}...) = elem(e, "", attrs...)
|
||||||
velem(e::AbstractString, attrs::Pair{Symbol,<:Any}...) =
|
velem(e::AbstractString, attrs::Pair{Symbol,<:Any}...) =
|
||||||
Html.void_element(e, [], Vector{Pair{Symbol,Any}}(collect(attrs)))
|
Html.void_element(e, [], Vector{Pair{Symbol,Any}}(collect(attrs)))
|
||||||
|
|
||||||
|
const html_escape_characters =
|
||||||
|
Dict('"' => """,
|
||||||
|
'&' => "&",
|
||||||
|
'<' => "<",
|
||||||
|
'>' => ">")
|
||||||
|
html_escape(s::String) = replace(s, r"\"|&|<|>" => c -> html_escape_characters[c[1]])
|
||||||
|
html_escape(::Missing) = ""
|
||||||
|
|
||||||
function htmlrender(field::FormField, value::Any, id, mandatory, invalid)
|
function htmlrender(field::FormField, value::Any, id, mandatory, invalid)
|
||||||
element = htmlelement(field)
|
element = html_element(field)
|
||||||
attrs = vcat(htmlattrs(field, value),
|
attrs = vcat(html_attrs(field, value),
|
||||||
[:id => string("qn-", id),
|
[:id => string("qn-", id),
|
||||||
:name => id,
|
:name => id,
|
||||||
Symbol("aria-invalid") => invalid,
|
Symbol("aria-invalid") => invalid,
|
||||||
:required => mandatory && !isa(field, Checkbox)])
|
:required => mandatory && !isa(field, Checkbox)])
|
||||||
if htmlvoidelem(field)
|
if html_voidelem(field)
|
||||||
velem(element, attrs...)
|
velem(element, attrs...)
|
||||||
else
|
else
|
||||||
content = htmlcontent(field, value)
|
content = html_content(field, value)
|
||||||
elem(element, if ismissing(content) "" else string(content) end,
|
elem(element, if ismissing(content) "" else string(content) end,
|
||||||
attrs...)
|
attrs...)
|
||||||
end |> htmlpostprocess(field, id)
|
end |> html_postprocess(field, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
|
@ -388,28 +396,28 @@ struct FormInput{T} <: FormField{T}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
htmlelement(::FormInput) = "input"
|
html_element(::FormInput) = "input"
|
||||||
htmlvoidelem(::FormInput) = true
|
html_voidelem(::FormInput) = true
|
||||||
htmlattrs(::FormInput, value) =
|
html_attrs(::FormInput, value) =
|
||||||
[:value => if ismissing(value) false else string(value) end]
|
[:value => if ismissing(value) false else html_escape(string(value)) end]
|
||||||
|
|
||||||
# <input type="checkbox">
|
# <input type="checkbox">
|
||||||
|
|
||||||
interpret(::FormInput{Bool}, value::AbstractString) = value == "yes"
|
interpret(::FormInput{Bool}, value::AbstractString) = value == "yes"
|
||||||
htmlattrs(::FormInput{Bool}, value::Union{Bool, Missing}) =
|
html_attrs(::FormInput{Bool}, value::Union{Bool, Missing}) =
|
||||||
[:type => "checkbox", :value => "yes", :checked => !ismissing(value) && value === true]
|
[:type => "checkbox", :value => "yes", :checked => !ismissing(value) && value === true]
|
||||||
htmlpostprocess(::FormInput{Bool}, id::Symbol) =
|
html_postprocess(::FormInput{Bool}, id::Symbol) =
|
||||||
s -> string(input(type="hidden", name=id, value="no"), s)
|
s -> string(input(type="hidden", name=id, value="no"), s)
|
||||||
|
|
||||||
# <input type="date|number|text">
|
# <input type="date|number|text">
|
||||||
|
|
||||||
function htmlattrs(::FormInput{T}, value) where {T <: Union{Date, Number, Integer, String}}
|
function html_attrs(::FormInput{T}, value) where {T <: Union{Date, Number, Integer, String}}
|
||||||
type = Dict(Date => "date",
|
type = Dict(Date => "date",
|
||||||
Number => "number",
|
Number => "number",
|
||||||
Integer => "number",
|
Integer => "number",
|
||||||
String => "text")[T]
|
String => "text")[T]
|
||||||
[:type => type,
|
[:type => type,
|
||||||
:value => if ismissing(value) false else string(value) end]
|
:value => if ismissing(value) false else html_escape(string(value)) end]
|
||||||
end
|
end
|
||||||
|
|
||||||
const Checkbox = FormInput{Bool}
|
const Checkbox = FormInput{Bool}
|
||||||
|
@ -426,8 +434,8 @@ struct TextArea <: FormField{String} end
|
||||||
|
|
||||||
default_validators(::TextArea) = Function[wordlimit(500), charlimit(2500)]
|
default_validators(::TextArea) = Function[wordlimit(500), charlimit(2500)]
|
||||||
|
|
||||||
htmlelement(::TextArea) = "textarea"
|
html_element(::TextArea) = "textarea"
|
||||||
htmlcontent(::TextArea, value) = value
|
html_content(::TextArea, value) = html_escape(value)
|
||||||
|
|
||||||
# <select>
|
# <select>
|
||||||
|
|
||||||
|
@ -450,8 +458,8 @@ Dropdown(opts::Union{Vector{String}, Vector{Pair{String, String}}}) =
|
||||||
Dropdown(gopts::Vector{Pair{String, Vector}}) =
|
Dropdown(gopts::Vector{Pair{String, Vector}}) =
|
||||||
Dropdown([OptGroup(gopt) for gopt in gopts])
|
Dropdown([OptGroup(gopt) for gopt in gopts])
|
||||||
|
|
||||||
htmlelement(::Dropdown) = "select"
|
html_element(::Dropdown) = "select"
|
||||||
function htmlcontent(d::Dropdown{Options}, value)
|
function html_content(d::Dropdown{Options}, value)
|
||||||
string(option("Select one", value="", selected=ismissing(value),
|
string(option("Select one", value="", selected=ismissing(value),
|
||||||
disabled=true, hidden=true), '\n',
|
disabled=true, hidden=true), '\n',
|
||||||
map(opt -> option(opt.second, value=opt.first,
|
map(opt -> option(opt.second, value=opt.first,
|
||||||
|
@ -459,7 +467,7 @@ function htmlcontent(d::Dropdown{Options}, value)
|
||||||
d.options.options)...)
|
d.options.options)...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function htmlcontent(d::Dropdown{Vector{OptGroup}}, value)
|
function html_content(d::Dropdown{Vector{OptGroup}}, value)
|
||||||
string(option("Select one", value="", selected=ismissing(value),
|
string(option("Select one", value="", selected=ismissing(value),
|
||||||
disabled=true, hidden=true),
|
disabled=true, hidden=true),
|
||||||
map(d.options) do optgroup
|
map(d.options) do optgroup
|
||||||
|
@ -540,7 +548,7 @@ function htmlrender(q::Union{<:Question{RadioSelect}, Question{MultiSelect}},
|
||||||
id = string("qn-", q.id, "-", opt.second)
|
id = string("qn-", q.id, "-", opt.second)
|
||||||
elem("label",
|
elem("label",
|
||||||
elem("input", :type => type, :id => id,
|
elem("input", :type => type, :id => id,
|
||||||
:name => string(q.id, "[]"), :value => opt.second,
|
:name => string(q.id, "[]"), :value => html_escape(opt.second),
|
||||||
:checked => (!ismissing(value) && opt.second ∈ value),
|
:checked => (!ismissing(value) && opt.second ∈ value),
|
||||||
if type == "radio" && q.field.other
|
if type == "radio" && q.field.other
|
||||||
[:oninput => "document.getElementById('$(string("qn-", q.id, "--other-input"))').value = ''"]
|
[:oninput => "document.getElementById('$(string("qn-", q.id, "--other-input"))').value = ''"]
|
||||||
|
@ -566,7 +574,7 @@ function htmlrender(q::Union{<:Question{RadioSelect}, Question{MultiSelect}},
|
||||||
elem("input", :type => "text",
|
elem("input", :type => "text",
|
||||||
:id => string("qn-", q.id, "--other-input"),
|
:id => string("qn-", q.id, "--other-input"),
|
||||||
:class => "other", :placeholder => "Other",
|
:class => "other", :placeholder => "Other",
|
||||||
:name => string(q.id, "[]"), :value => join(othervals, ", "),
|
:name => string(q.id, "[]"), :value => html_escape(join(othervals, ", ")),
|
||||||
:oninput => "document.getElementById('$(string("qn-", q.id, "--other"))').checked = this.value.length > 0"
|
:oninput => "document.getElementById('$(string("qn-", q.id, "--other"))').checked = this.value.length > 0"
|
||||||
)),
|
)),
|
||||||
:for => string("qn-", q.id, "--other"))
|
:for => string("qn-", q.id, "--other"))
|
||||||
|
@ -581,15 +589,15 @@ end
|
||||||
|
|
||||||
RangeSelect(r::UnitRange{<:Number}) = RangeSelect(StepRange(r.start, 1, r.stop))
|
RangeSelect(r::UnitRange{<:Number}) = RangeSelect(StepRange(r.start, 1, r.stop))
|
||||||
|
|
||||||
htmlelement(::RangeSelect) = "input"
|
html_element(::RangeSelect) = "input"
|
||||||
htmlvoidelem(::RangeSelect) = true
|
html_voidelem(::RangeSelect) = true
|
||||||
htmlattrs(r::RangeSelect, value) =
|
html_attrs(r::RangeSelect, value) =
|
||||||
[:type => "range",
|
[:type => "range",
|
||||||
:min => r.values.start, :max => r.values.stop,
|
:min => r.values.start, :max => r.values.stop,
|
||||||
:step => r.values.step, :style => "--stops: $(length(r.values)-1); width: calc(100% - 3em)",
|
:step => r.values.step, :style => "--stops: $(length(r.values)-1); width: calc(100% - 3em)",
|
||||||
:oninput => "results$(hash(r)).value = this.value",
|
:oninput => "results$(hash(r)).value = this.value",
|
||||||
:value => if ismissing(value) false else string(value) end]
|
:value => if ismissing(value) false else html_escape(string(value)) end]
|
||||||
htmlpostprocess(r::RangeSelect, id::Symbol) =
|
html_postprocess(r::RangeSelect, id::Symbol) =
|
||||||
s -> string(s, '\n', output(name="results$(hash(r))"), '\n',
|
s -> string(s, '\n', output(name="results$(hash(r))"), '\n',
|
||||||
script("""let r = document.getElementById("qn-$id")
|
script("""let r = document.getElementById("qn-$id")
|
||||||
let o = document.getElementsByName("results$(hash(r))")[0]
|
let o = document.getElementsByName("results$(hash(r))")[0]
|
||||||
|
@ -679,7 +687,7 @@ end
|
||||||
show(io::IO, qa::Pair{<:Question, <:Answer}) = show(io, MIME("text/plain"), qa)
|
show(io::IO, qa::Pair{<:Question, <:Answer}) = show(io, MIME("text/plain"), qa)
|
||||||
|
|
||||||
function show(io::IO, m::MIME"text/plain", part::SurveyPart)
|
function show(io::IO, m::MIME"text/plain", part::SurveyPart)
|
||||||
printstyled(" -- ", if isnothing(part.label)
|
printstyled(io, " -- ", if isnothing(part.label)
|
||||||
"Unlabeled part"
|
"Unlabeled part"
|
||||||
else part.label end, " --\n", color=:yellow)
|
else part.label end, " --\n", color=:yellow)
|
||||||
for q in part.questions
|
for q in part.questions
|
||||||
|
@ -690,7 +698,7 @@ end
|
||||||
|
|
||||||
function show(io::IO, m::MIME"text/plain", pr::Pair{SurveyPart, Response})
|
function show(io::IO, m::MIME"text/plain", pr::Pair{SurveyPart, Response})
|
||||||
part, response = pr
|
part, response = pr
|
||||||
printstyled(" -- ", if isnothing(part.label)
|
printstyled(io, " -- ", if isnothing(part.label)
|
||||||
"Unlabeled part"
|
"Unlabeled part"
|
||||||
else part.label end, " --\n", color=:yellow)
|
else part.label end, " --\n", color=:yellow)
|
||||||
foreach(part.questions) do q
|
foreach(part.questions) do q
|
||||||
|
|
Loading…
Reference in New Issue