diff --git a/vlib/encoding/html/escape.v b/vlib/encoding/html/escape.v new file mode 100644 index 0000000000..5931adc4ae --- /dev/null +++ b/vlib/encoding/html/escape.v @@ -0,0 +1,20 @@ +module html + +[params] +pub struct EscapeConfig { + quote bool = true +} + +// escape converts special characters in the input, specifically "<", ">", and "&" +// to HTML-safe sequences. If `quote` is set to true (which is default), quotes in +// HTML will also be translated. Both double and single quotes will be affected. +// **Note:** escape() supports funky accents by doing nothing about them. V's UTF-8 +// support through `string` is robust enough to deal with these cases. +pub fn escape(input string, config EscapeConfig) string { + tag_free_input := input.replace_each(['&', '&', '<', '<', '>', '>']) + return if config.quote { + tag_free_input.replace_each(['"', '"', "'", ''']) + } else { + tag_free_input + } +} diff --git a/vlib/encoding/html/escape_test.v b/vlib/encoding/html/escape_test.v new file mode 100644 index 0000000000..2b8ffbe13c --- /dev/null +++ b/vlib/encoding/html/escape_test.v @@ -0,0 +1,22 @@ +import encoding.html + +fn test_escape_html() { + assert html.escape('<>&') == '<>&' + assert html.escape('No change') == 'No change' + assert html.escape('Bold text') == '<b>Bold text</b>' + assert html.escape('') == '<img />' + assert html.escape("' onmouseover='alert(1)'") == '' onmouseover='alert(1)'' + assert html.escape("link") == '<a href='http://www.example.com'>link</a>' + assert html.escape("") == '<script>alert('hello');</script>' + // Cases obtained from: + // https://github.com/apache/commons-lang/blob/master/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java + assert html.escape('plain text') == 'plain text' + assert html.escape('') == '' + assert html.escape('bread & butter') == 'bread & butter' + assert html.escape('"bread" & butter') == '"bread" & butter' + assert html.escape('greater than >') == 'greater than >' + assert html.escape('< less than') == '< less than' + // Leave accents as-is + assert html.escape('café') == 'café' + assert html.escape('

façade

') == '<p>façade</p>' +} diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index a8dd3b3e69..c544606033 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -10,6 +10,7 @@ import net.http import net.urllib import time import json +import encoding.html // A type which don't get filtered inside templates pub type RawHtml = string @@ -725,12 +726,5 @@ fn send_string(mut conn net.TcpConn, s string) ! { // It used by `vlib/v/gen/c/str_intp.v:130` for string interpolation inside vweb templates // TODO: move it to template render fn filter(s string) string { - return s.replace_each([ - '<', - '<', - '"', - '"', - '&', - '&', - ]) + return html.escape(s) }